Skip to navigation

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:

KindBelongs to a project
Audio assetsYes, and can be moved later
API keys and service accountsYes — a pin fixed when the credential is created
Vault credentials and webhook endpointsOne project, or workspace-wide
Usage and spendAttributed through the calling credential’s pin
Cloned voicesFrom the pin on the creating credential — except a consent-verified clone, which is always workspace-wide
The public voice and model catalogNo — 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.

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.