Projects

Group resources, scope credentials, and attribute spend inside one workspace

A workspace is who you are: it holds your identity, your members, your plan, and one invoice. A project is where work happens inside it. A project groups the resources you create in it and the spend you incur from it, so a workspace can run several environments or several end customers without splitting into several accounts.

Every workspace has an implicit Default project. It is not a row you create or delete: any resource with no project lives there, which is where everything sits until you make your first project.

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 you need one team to be unable to see another team’s data at all, use a separate workspace.

Creating a project

$curl -X POST "https://api.speechify.ai/v1/projects" \
> -H "Authorization: Bearer $SPEECHIFY_API_KEY" \
> -H "Content-Type: application/json" \
> -d '{"name": "Production"}'

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

What a project groups

KindBelongs to a project
Agents, knowledge bases, tools, audio assetsYes, and the project can be changed later
Phone numbers and SIP trunksYes
API keys and service accountsYes, as a pin fixed when the credential is created
Vault credentials and webhook endpointsEither one project, or workspace-wide
Conversations, callers, batch calls, test runs, memoriesYes, frozen at creation and never re-attributed
Usage and spendYes, attributed through the calling credential’s pin
Your cloned voicesYes, taken from the pin on the credential that created the clone, except a consent-verified clone, which is always workspace-wide
The public voice and model catalogNo, it is workspace-wide

A derived record keeps the project it was created in even if its agent later moves, because your usage grouping and your invoice already report it that way.

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 resources that sit in the implicit Default project. On the lists whose rows can be shared across the workspace instead of living in one project - credentials, webhook endpoints and cloned voices - that literal is shared, because an absent project there means workspace-wide rather than Default.

$curl "https://api.speechify.ai/v1/agents?project_id=proj_01jqr8x9zg5k2m3n4p5q6r7s8t" \
> -H "Authorization: Bearer $SPEECHIFY_API_KEY"

Pinning a credential

An API key or a service account can be pinned to one project when you create it. A pinned credential reads and writes only inside that project, its requests are attributed to that project, and it is subject to that project’s limits.

The pin is fixed at creation and cannot be edited afterwards, so re-scoping means minting a new credential and retiring the old one. A credential with no pin is workspace-wide.

What a grant does

By default every workspace member sees the whole workspace. Granting a member a project narrows them to the projects they hold: from their first grant onward, they see and work in those projects only, in the console and through the API.

  • A member with no grants anywhere is workspace-wide. That is the default state, not a gap.
  • Removing a member’s last grant returns them to workspace-wide access. To narrow someone, grant them the projects they should keep rather than revoking everything.
  • Owners and billing admins cannot be scoped: both already hold workspace-wide access that a project grant would contradict without narrowing anything.
  • A pending invite can carry projects, so an invitee is scoped from the moment they accept.
$curl -X POST "https://api.speechify.ai/v1/projects/proj_01jqr8x9zg5k2m3n4p5q6r7s8t/members" \
> -H "Authorization: Bearer $SPEECHIFY_API_KEY" \
> -H "Content-Type: application/json" \
> -d '{"user_id": "user_kb3fim3yjnyto3kxli4g4ysdmyzgiuttjrudswlkgbawk"}'

Limits and capacity

A project can carry its own ceilings, each sitting under the workspace’s own plan limits so one project cannot consume the whole allowance:

SettingEffect when reached
Monthly spend limitNew billable work attributed to the project is refused with 402 project_spend_limit_exceeded, resetting at the calendar-month boundary (UTC)
Max concurrent callsA call over the ceiling is refused with 429 concurrency_limit_reached, while sibling projects keep their headroom
Max requests per minuteA request from a credential pinned to the project is refused with 429 rate_limited, while other projects and unpinned credentials are untouched

A project limit bounds work; it does not split the bill. The invoice stays workspace-level, and per-project cost shows up as a grouping on the workspace usage breakdown.

Archiving a project

Archiving suspends a project without removing anything. From then on nothing new starts or bills inside it: conversation and widget sessions, inbound and outbound calls, batch calls, test runs, knowledge-base imports, and any synthesis or gateway request on a credential pinned to the project are all refused with 409 project_archived. Work already in flight is left to finish.

Everything in the project stays readable and its configuration stays editable. Archiving is idempotent and reversible: unarchive the project and work resumes.

Deleting a project

Deleting runs in one of two modes.

Detach is the default. Only the grouping is removed, and every resource in the project moves to the Default project. It is refused with 409 project_has_scoped_credentials while an API key, service account, vault credential, webhook endpoint, member grant, or pending invite is scoped to the project, because detaching any of those would silently widen it.

Purge removes the project together with its contents in one transaction. It is available only on an archived project, so a teardown always has a state you can sit in and reverse first; a live project is refused with 409 project_not_archived. A purge is recoverable for 30 days, after which the project and its contents are permanently deleted.

$# Detach (the default): the grouping row goes, every resource moves to Default.
$curl -X DELETE "https://api.speechify.ai/v1/projects/proj_01jqr8x9zg5k2m3n4p5q6r7s8t" \
> -H "Authorization: Bearer $SPEECHIFY_API_KEY"
$
$# Purge: archived projects only, and `confirm` must equal the project's exact name.
>curl -X DELETE "https://api.speechify.ai/v1/projects/proj_01jqr8x9zg5k2m3n4p5q6r7s8t" \
> -H "Authorization: Bearer $SPEECHIFY_API_KEY" \
> -H "Content-Type: application/json" \
> -d '{"mode": "purge", "confirm": "Production"}'
Purge removesPurge keeps
Agents with their tests, knowledge bases with their documents, tools, audio assetsConversations, with their frozen attribution
Webhook endpoints and vault credentials scoped to the projectCallers, batch calls, suite runs, and memories, which move to the Default project
API keys and service accounts pinned to the project, which are revoked
Member grants and pending-invite scopes on the project, which are cleared

A purge is refused while a phone number is still attached, while a member’s only grant is this project, or while a live invite carries only this project, since clearing either would widen that person to the whole workspace.

Both modes have a preview that reports what the delete would do without doing it: whether a purge is available right now, every row holding the project open and which mode each refuses, what a purge would remove, and the operational records it would keep.

Restoring a purged project

A purge is recoverable for 30 days. The project disappears from every list and read immediately and its name is freed for reuse, but the project and its resources are kept and permanently deleted only once the window closes.

List what is still recoverable with include_purged=true, the only read that returns a purged project. Each row carries purged_at, and the restore deadline is that stamp plus 30 days; a project past its window is never listed, because a restore would refuse it.

$# What is still recoverable, with the stamp each deadline runs from.
$curl "https://api.speechify.ai/v1/projects?include_purged=true" \
> -H "Authorization: Bearer $SPEECHIFY_API_KEY"
$
$# Bring one back.
$curl -X POST "https://api.speechify.ai/v1/projects/proj_01jqr8x9zg5k2m3n4p5q6r7s8t/restore" \
> -H "Authorization: Bearer $SPEECHIFY_API_KEY"

What comes back: the project and its resources - agents with their tests, knowledge bases, tools, and audio assets - exactly the rows the purge removed. A resource you had deleted yourself before the purge stays deleted. The project returns archived, the state it was purged from, so nothing dispatches or bills inside it until you unarchive it.

What does not come back, on purpose: every credential the purge revoked stays revoked and every grant it cleared stays cleared. API keys and service accounts pinned to the project are not re-issued, vault credentials and webhook endpoints scoped to it are not undeleted, and member grants and pending-invite scopes are not restored. Bringing a credential or a grant back would re-grant access somebody deliberately ended, so the response reports them under still_revoked instead. Re-create the credentials and re-grant the members the project still needs.

A restore is refused with 409 project_not_purged when the project was never purged, 409 project_restore_window_expired once the 30 days have passed, and 409 project_name_taken when another project has taken this one’s name since the purge - rename the project holding it, then restore.

A purge is recoverable for 30 days, but a restore is not a full undo: it returns the project and its resources, while the credentials it revoked and the access it cleared stay that way. Preview a purge first, and read the list of records it keeps: a project teardown that reads as total and is not would be worse than none.

Promoting a project

Promoting copies one project’s configuration into another project in the same workspace: build in staging, promote to production, tear staging down.

Copied: agents with every setting, tool definitions, knowledge bases with their folders, audio assets, and the webhook endpoints scoped to the source. Never copied: vault credentials, API keys and service accounts, phone numbers and SIP trunks, conversations, callers, memories, call and test history, member grants, spend limits, and every secret. A copied item whose credential reference was cleared, whose secret was minted fresh, or whose name collided in the target is listed for you rather than silently half-configured.

What projects scope in SpeechifyAI Build

Build and Agents share one workspace, and projects apply to them differently. The short answer: projects scope what you build with Agents, they scope what everything costs, and in Build they scope the one thing you create there, a cloned voice. The public voice and model catalog stays workspace-wide.

In BuildDoes a project apply
Spend and usage attributionYes, through the calling key’s pin
A project’s monthly spend limitYes, it can refuse a Build request outright with 402 project_spend_limit_exceeded
A project’s requests-per-minute ceilingYes, for credentials pinned to that project
Your cloned voicesYes, a clone is filed under the project of the credential that created it, and the list filters by it. A consent-verified clone is filed under the workspace instead
The public voice catalog and the model catalogNo, they are workspace-wide
Text to speech, Dialogue, and the Waymark gatewayNo resource is created, so there is nothing to group

Two consequences are worth stating plainly, because they are the ones most often misread:

  • A project’s budget can stop a Build request that creates nothing inside the project. A synthesis or gateway call leaves no resource behind and is still attributed to, and bounded by, the project its key is pinned to. Per-project Build cost is a statement about your API traffic, not about your console use.
  • The pin belongs to an API key, so a console session never carries one. Everything a person does in the Build UI is workspace-level spend, whichever project the console is showing, and a voice cloned from the console is shared with the whole workspace rather than filed in a project.

Cloned voices and projects

A voice clone created by a project-pinned credential is filed under that project alongside your workspace, and the project decides who is shown it:

  • A clone with a project is listed for the callers who can reach that project: a credential pinned to it, a member granted it, and anyone whose access is workspace-wide. It is not in anyone else’s voice list.
  • A clone with no project is shared with the whole workspace and listed for everyone in it, a project-pinned credential included. That is every clone made before projects, every clone made by an unpinned credential or from the console, and every consent-verified clone.
  • GET /v1/voices takes a project_id filter: pass a proj_... id for one project’s clones, or the literal shared for the clones no project filed. It is shared rather than default because a clone with no project is shared across the workspace rather than sitting in the Default project, the same way a workspace-wide credential is. The public voice catalog and the model catalog are returned whatever you filter by, because nobody created them in a project.

The scope you are showing changes which clones you see. It does not change where a charge lands: synthesis bills to the project pinned on the credential that made the request, and a console session carries no pin.

Filing a clone to a project scopes who is shown it, not who could ever reach your account. A project is not a security boundary: use a separate workspace when one team must not be able to see another’s data at all.

Managing projects

Projects are managed in the console under Settings → Workspace → Projects: create and rename them, set their limits, grant members access, archive and unarchive, preview and run a delete, and promote one project into another. The scope picker in the top bar chooses the workspace and the project the console is showing.