> 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.2` (English, recommended) 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

# API: Projects — group resources, scope credentials, and attribute spend

The `/v1/projects` endpoints are now in the public API reference. A **project** groups the resources you create inside a workspace and the spend you incur from them, so one workspace can run several environments or several end customers without splitting into separate accounts.

Every workspace has an implicit **Default project**: any resource with no project lives there, and nothing you already send changes — the surface is additive and opt-in.

What a project groups:

| Kind | Belongs to a project |
|---|---|
| Audio assets | Yes, and can be moved later |
| API keys and service accounts | Yes — a pin fixed when the credential is created |
| Vault credentials and webhook endpoints | One project, or workspace-wide |
| Usage and spend | Attributed through the calling credential's pin |
| Cloned voices | From the pin on the creating credential — except a consent-verified clone, which is always workspace-wide |
| The public voice and model catalog | No — workspace-wide |

Manage the lifecycle with `POST/GET/PATCH/DELETE /v1/projects` and `.../{project_id}`, plus `archive`, `unarchive`, `restore`, `teardown`, `stats`, `audit`, `promote`, and the `members` sub-tree (`grant`/`revoke` access).

Filtering: every list endpoint that takes a project accepts a `project_id` query parameter. Omit it to get everything you can reach, pass a `proj_...` id for one project, or pass the literal `default` for the implicit Default project. On lists whose rows can be workspace-wide — credentials, webhook endpoints and cloned voices — that literal is `shared` instead, because an absent project there means workspace-wide rather than Default.

Names are unique per workspace, case-insensitively; a workspace holds at most 100 live projects, and at the cap the create is refused with `409 project_limit_reached`.

A project is a filter and a grouping, **not a security boundary** — it scopes what a credential may reach, what a scoped member sees, and where spend lands. If one team must be unable to see another's data at all, use a separate workspace. See [Projects](/build/guides/concepts/projects).

## Per-project capacity ceilings

A project can now carry a capacity ceiling alongside its spend limit: `max_requests_per_minute`, the most API requests per minute from credentials pinned to the project, across every surface. It is read on the Project, set via `PATCH` merge-patch, and cleared by sending `null`. Setting it requires the `billing.manage` permission.

It must sit at or below the workspace's own plan cap; a value above it is refused with `400 validation_failed` naming the field and the ceiling. A project can only narrow the workspace's capacity, never raise it. It is checked **after** the workspace's own caps: a request over the rate ceiling is refused with `429 rate_limited` from a per-project bucket — the same code the workspace limit already answers. Sibling projects keep their own headroom; console sessions and unpinned keys carry no project and are subject to neither.

## Error codes

- **`404 project_not_found`** — no project with that id, or one your credential cannot reach.
- **`409 project_limit_reached`** — the workspace already holds the maximum live projects its plan allows (up to 100); delete an unused one to free a slot.
- **`409 project_archived`** — work would start or bill inside an archived project (synthesis on a credential pinned to it, for example). Data stays readable and configuration stays editable; unarchive to resume — no balance or ceiling change clears it. The project consulted is the one the work is attributed to, so a workspace-wide key can be refused too.
- **`409 project_not_archived`** — a destructive teardown was asked for on a live project. Archive is the reversible pending-deletion state; archive first, then purge from there.
- **`402 project_spend_limit_exceeded`** — the project's Orb-rated spend this calendar month reached its limit, the middle ceiling between the workspace budget and a key's cap. Raise that project's limit, move the work, or wait for the monthly reset. The project charged is the one the spend is attributed to, so a workspace-wide key can trip it.
- **`409 project_has_scoped_credentials`** — a delete was refused because API keys or service accounts are still pinned to the project; the listed credentials ride `error.details`. Revoke or re-mint them elsewhere first — a pin is never silently widened to the whole workspace.
- **`409 cross_project_reference`** — a move was refused because live references tie the resource to its current project; the blockers ride `error.details.referrers` so you can move them together. Detaching them always unblocks.
- **`409 project_too_large_to_promote`** — the project holds more resources than one synchronous `promote` may copy.
- **`409 project_restore_window_expired`** — a `restore` arrived after the purge retention window closed. Distinct from `project_not_found` on purpose: the project is gone for good, not an id you mistyped.
- **`409 project_not_purged`** — a `restore` was asked for on a project that has not been purged. Not a 404: the project exists and you can still see it.
- **`409 project_name_taken`** — a `restore` cannot reinstate the project because another has taken its name since the purge (a purge frees the name immediately). Rename the holder, then restore again.

## Filtering request logs by project

The request-log and analytics `project_id` filter adds a third literal beyond the `default` and `shared` used on the resource lists above: `unattributed` narrows to workspace-level traffic that names no project — an unpinned key, or a console session. That is **not** the Default project, and a project filter cannot select Default, because a key can only ever be pinned to a project you created.