> This page is for Build.

> Append .md to any page URL for clean Markdown. Index: https://docs.speechify.ai/llms.txt.
>
> Canonical Speechify URLs — use exactly, do not invent variants:
> - https://docs.speechify.ai — this site (API reference, SDKs, quickstarts)
> - https://speechify.ai — marketing + product site
> - https://platform.speechify.ai — customer dashboard, signup, API keys, billing
> - https://api.speechify.ai — API base URL
> - https://github.com/Speechify-AI: GitHub org for the API (cookbook, demos, CLI). `github.com/speechify` does not exist.
> - https://status.speechify.ai — status + incidents
> - https://speechify.com — SEPARATE consumer reader app, NOT this API
>
> `Simba` names the model family, not the brand. Model ids: `simba-3.5` (48 languages under one id, English included; recommended), `simba-3.2` (English) and `simba-3.0` (English, German, Spanish, French, Italian and Portuguese; the default). `simba-english` and `simba-multilingual` are retired: a new workspace that sends either gets `400 model_retired`. `SimbaVoice` / `simbavoice.ai` are retired.
>
> Ask, don't scrape. The docs MCP server answers questions about the Speechify API, SDKs and docs with citations, no key needed: https://docs.speechify.ai/_mcp/server (Streamable HTTP, tool `searchDocs`). Setup: https://docs.speechify.ai/build/guides/get-started/connect-mcp

# Create Project

POST https://api.speechify.ai/v1/projects
Content-Type: application/json

Create a project in the caller's workspace. Names are unique per
workspace (case-insensitive). A workspace holds at most 100 live
projects; at the cap the create refuses with
`409 project_limit_reached` until one is deleted.


Reference: https://docs.speechify.ai/build/api-reference/v1/projects/post

## Authentication

- `Authorization` header (bearer token, required) — Enter your API key with the `Bearer` prefix, e.g. 'Bearer sk_...'.

## Request

### Body (application/json)

This endpoint expects a CreateProjectRequest.

- `name` (string, required) — Project name; unique per workspace (case-insensitive), surrounding whitespace is trimmed.

## Response

### 201

The created project.

- `id` (string, required) — Workspace-scoped project identifier (prefixed external id).
- `name` (string, required) — Human-readable label, unique per workspace (case-insensitive).
- `resource_count` (integer, required) — How many resources this project holds. Records of work that happened are not counted, and never hold a project open. It is the count the unqualified delete refuses on, so a project reading `0` is one `DELETE /v1/projects/{project_id}` accepts with no body. That is what makes an application's own cleanup a single list call rather than a read per project.
- `created_at` (datetime, required)
- `updated_at` (datetime, required)
- `archived_at` (datetime, optional, nullable) — When the project was archived; `null` while it is live. While set, nothing new starts or bills inside the project and every such attempt answers `409 project_archived`. Absent rather than null while a console runs ahead of an API that predates archiving, which is why it is not required: read absent and null alike.
- `purged_at` (datetime, optional) — When this project was purged, present only on the rows returned by `include_purged=true`. A purged project is kept for 30 days from this stamp and permanently deleted after that, so the restore deadline is this value plus the window; it is absent on every project that still exists. Restore it with `POST /v1/projects/{project_id}/restore`.
- `max_requests_per_minute` (integer, optional) — The most API requests per minute credentials pinned to this project may make across every surface, present only when set. Checked after the workspace's own request-rate limit, in one bucket per project: a request over the ceiling is refused with the same `429 rate_limited` the workspace limit answers, while other projects and unpinned credentials are untouched. Never higher than the workspace's widest per-surface rate over a minute: a project can narrow the workspace's capacity, not raise it. Console sessions and unpinned keys carry no project and are never subject to it.
- `monthly_budget` (double, optional) — The project's monthly spend limit in US dollars, present only when one is set. New billable work attributed to this project is refused with the coded `402 project_spend_limit_exceeded` once `monthly_spend` reaches it; the limit resets at the calendar-month boundary (UTC). Spend is attributed the same way it is billed: work from a project-pinned API key counts against that key's project, and work by a resource in a project counts against that project. The implicit Default project cannot carry a limit, since it has no project record, so spend there is bounded by the workspace's `monthly_budget` instead.
- `monthly_spend` (double, optional) — The project's billed month-to-date spend in US dollars, present whenever the billing plane answered - regardless of whether a spend limit is set.
- `monthly_budget_status` (enum, optional) — Where `monthly_spend` stands against `monthly_budget`, present only when both are: `warning` from 80% of the budget, `reached` once the spend has reached it and new billable work attributed to the project is refused with `402 project_spend_limit_exceeded`, `ok` below 80%. These are the thresholds the `project.spend_budget.warning` and `project.spend_budget.reached` webhook events fire at, so polling this field and subscribing to the events give the same answer.
  - Allowed values: `ok`, `warning`, `reached`
- `monthly_budget_remaining` (double, optional) — `monthly_budget` minus `monthly_spend` in US dollars, present only when both are. Negative once the spend has passed the budget, by the amount it overshot: enforcement trails billed usage by a couple of minutes, so work already under way can carry a project past its budget.

## Errors

### 400 Bad Request Error

The request was malformed or failed validation. The response body is the standard `Error` envelope; for validation failures `error.fields` enumerates the offending fields as a `path -> message` map (code = `validation_failed`).

- `error` (ErrorDetail, required)
- `request_id` (string, optional) — Server-side request identifier. Echoes the `Speechify-Request-Id` response header. Stable across the request's lifetime, written to structured logs, and useful when reporting issues.

### 401 Unauthorized Error

Authentication is missing or invalid. The request did not carry a recognised credential (console session token, API key, or worker JWT).

- `error` (ErrorDetail, required)
- `request_id` (string, optional) — Server-side request identifier. Echoes the `Speechify-Request-Id` response header. Stable across the request's lifetime, written to structured logs, and useful when reporting issues.

### 409 Conflict Error

The request conflicts with the current resource state - e.g. duplicate, optimistic-concurrency mismatch, or last-owner guard.

- `error` (ErrorDetail, required)
- `request_id` (string, optional) — Server-side request identifier. Echoes the `Speechify-Request-Id` response header. Stable across the request's lifetime, written to structured logs, and useful when reporting issues.

### 429 Too Many Requests Error

Rate limit or concurrency limit exceeded. `error.code` says which ceiling, and they need different responses: `rate_limited` is the request-rate budget (slow down), `concurrency_limit_reached` is a workspace-wide concurrency ceiling (fewer at once, or raise it), and `conversation_turn_in_progress` is contention over one named conversation (keep one message in flight on it). Every 429 carries `Retry-After` and the request-rate budget headers; the active-call cap also carries `RateLimit-Remaining-Calls: 0`.

- `error` (ErrorDetail, required)
- `request_id` (string, optional) — Server-side request identifier. Echoes the `Speechify-Request-Id` response header. Stable across the request's lifetime, written to structured logs, and useful when reporting issues.

### 500 Internal Server Error

An unexpected server-side error occurred. Safe to retry with exponential backoff for idempotent requests.

- `error` (ErrorDetail, required)
- `request_id` (string, optional) — Server-side request identifier. Echoes the `Speechify-Request-Id` response header. Stable across the request's lifetime, written to structured logs, and useful when reporting issues.

## Types

### ErrorDetail

- `code` (enum, required) — Stable machine-readable error code. Additive only: codes are never renamed, only deprecated. SDKs may map each code to a typed exception class. Status-code semantics: 4xx codes describe caller-fixable issues; 5xx codes describe server-side failures and are safe to retry with backoff for idempotent requests.
  - Allowed values: `endpoint_moved`, `bad_request`, `validation_failed`, `unauthorized`, `payment_required`, `forbidden`, `not_found`, `method_not_allowed`, `conflict`, `idempotency_conflict`, `payload_too_large`, `unsupported_media_type`, `rate_limited`, `concurrency_limit_reached`, `invalid_api_version`, `internal_error`, `upstream_failure`, `service_unavailable`, `caller_not_found`, `contact_not_found`, `contact_identifier_not_found`, `contact_identifier_conflict`, `contact_resolver_not_found`, `credential_not_found`, `credential_in_use`, `agent_not_found`, `agent_in_use`, `agent_run_not_found`, `kb_not_found`, `kb_document_not_found`, `kb_folder_not_found`, `tool_not_found`, `tool_name_taken`, `channel_instance_not_found`, `team_not_found`, `trigger_not_found`, `store_not_found`, `store_document_not_found`, `hosted_api_not_found`, `api_route_not_found`, `consumer_key_not_found`, `skill_not_found`, `skill_version_not_found`, `file_not_found`, `file_path_taken`, `file_storage_limit_reached`, `store_limit_reached`, `store_document_limit_reached`, `store_bytes_limit_reached`, `store_not_configured`, `store_document_version_conflict`, `store_document_deleted`, `entitlement_override_exists`, `hosted_apis_not_in_plan`, `skills_not_in_plan`, `voice_agents_not_in_plan`, `skill_in_use`, `skill_tool_name_conflict`, `skill_limit_reached`, `agent_skill_limit_reached`, `hosted_api_slug_taken`, `api_route_conflict`, `mount_plan_changed`, `route_output_unavailable`, `route_run_timeout`, `route_run_failed`, `route_run_limit_reached`, `route_read_limit_reached`, `route_write_limit_reached`, `hosted_api_public_refused`, `route_tool_not_readable`, `route_tool_unavailable`, `route_upstream_rate_limited`, `route_upstream_error`, `hosted_mcp_not_enabled`, `hosted_api_busy`, `conversation_not_found`, `phone_number_not_found`, `sip_trunk_not_found`, `voice_not_found`, `audio_asset_not_found`, `builtin_not_found`, `batch_not_found`, `agent_test_not_found`, `workspace_not_found`, `invite_not_found`, `project_not_found`, `cross_project_reference`, `project_has_scoped_credentials`, `project_not_empty`, `project_limit_reached`, `agent_limit_reached`, `project_too_large_to_promote`, `call_not_found`, `message_not_found`, `thread_not_found`, `call_not_active`, `relay_displaces_agent`, `brain_not_found`, `brain_in_use`, `custom_model_not_found`, `custom_model_in_use`, `insufficient_scope`, `purchased_numbers_not_included`, `phone_number_quota_reached`, `batch_calls_not_included`, `voice_cloning_not_included`, `voice_cloning_unavailable_in_region`, `consent_challenge_not_found`, `consent_challenge_expired`, `consent_challenge_already_used`, `consent_phrase_mismatch`, `consent_speaker_mismatch`, `consent_recording_unusable`, `consent_verification_unavailable`, `consent_verification_required`, `watermark_audio_unusable`, `watermark_detection_unavailable`, `workspace_last_owner`, `workspace_last_workspace`, `account_deletion_blocked`, `workspace_free_limit`, `workspace_single_owner`, `invite_email_mismatch`, `invite_already_pending`, `service_account_limit_reached`, `service_accounts_not_in_plan`, `speech_marks_unsupported`, `pronunciations_unsupported`, `model_retired`, `too_many_voices`, `content_policy_violation`, `safety_identifier_blocked`, `topup_not_in_plan`, `credit_purchase_unpaid`, `credit_purchase_payment_in_progress`, `tool_config_shared`, `spend_cap_exceeded`, `spend_budget_exceeded`, `project_spend_limit_exceeded`, `project_archived`, `project_not_archived`, `project_not_purged`, `project_restore_window_expired`, `project_name_taken`, `funded_balance_required`, `agent_publish_gate_failed`, `agent_publish_gate_required`, `agent_publish_gate_unavailable`, `agent_publish_gate_tool_unreachable`, `text_channel_not_in_plan`, `channel_not_in_plan`, `text_turn_failed`, `conversation_turn_in_progress`, `conversation_closed`, `conversation_not_reachable`, `conversation_channel_bound`, `conversation_prompt_not_found`, `text_message_quota_exceeded`, `durable_runs_not_in_plan`, `tool_transport_unsupported`, `agent_config_too_large`, `agent_run_not_pending`, `agent_run_action_stale`, `share_link_not_found`, `share_link_exhausted`, `share_link_limit_reached`, `destination_not_allowed`, `international_dialing_not_enabled`, `number_not_sms_capable`, `verification_required`, `intended_use_required`
- `message` (string, required) — Human-readable explanation of this specific occurrence. Safe to surface in UI banners or pass to support. The wording can change between releases; clients should match on `code`, not on the message string.
- `fields` (map from string to string, optional) — Per-field validation errors as `path -> message`. Only present on 400 responses caused by request validation (typically code=`validation_failed`). Keys are field paths in dotted/bracket notation; values are short human explanations safe to inline-surface next to the offending form field.
- `details` (map from string to any, optional) — Structured, endpoint-specific context beyond the flat `fields` map. Present only on the few errors that carry it (e.g. the `used_by` referrer list on a credential delete-conflict); its shape depends on the error `code`. Clients that don't recognise a `details` shape can ignore it - the `code` + `message` contract is unchanged.
- `docs_url` (string, optional) — Link to the documentation that resolves this class of error, when a stable page exists. Rate and concurrency 429s link the API limits reference, which lists each plan's limits and how to raise them.

## Examples

**Request**

```json
{
  "name": "Production"
}
```

**Response**

```json
{
  "id": "proj_01arz3ndektsv4rrffq69g5fav",
  "name": "Production",
  "resource_count": 12,
  "created_at": "2026-08-18T18:03:11Z",
  "updated_at": "2026-08-18T18:03:11Z",
  "archived_at": null,
  "max_requests_per_minute": 600,
  "monthly_budget": 500,
  "monthly_spend": 128.4412,
  "monthly_budget_status": "ok",
  "monthly_budget_remaining": 371.5588
}
```