Skip to main content

Projects

Projects are multi-tenant namespaces; every resource (document, file, actor, conversation) belongs to one. Ids are prefixed proj_.

Overview​

Access is policy-based, with no membership table: the policies attached to the account and their SRN patterns decide. Walkthrough: Chat with an LLM - Step 2 (Create a project).

See the Permissions Reference for the IAM action strings for this module.

Data Model​

FieldTypeDescription
idstringPublic identifier prefixed with proj_
namestringHuman-readable project name
guardrail_idsarrayGuardrails attached at the project scope — the baseline governing every tool call by every agent in the project. See Guardrails — Attachment
default_model_route_idstring | nullModel route inherited by consumers in this project that bind neither model_route_id nor ai_provider_id. null (default) means no default, so every consumer must bind explicitly. Settable/clearable via update-project.
max_concurrent_runsinteger | nullMaximum orchestration runs of this project driven at once. null (default) means unlimited; otherwise an integer ≥ 1. Settable/clearable via update-project.
max_chain_generationsinteger | nullGenerations one continuation chain in this project may hold before the platform stops resuming it. null (default) means no project ceiling, leaving the deployment-wide one; otherwise an integer ≥ 1. The effective budget is the smallest of the deployment's ceiling, this one, and the agent's own maxChainGenerations. Settable/clearable via update-project.
max_orchestration_run_depthinteger | nullloop / sub_orchestration nesting levels a run tree in this project may reach before the engine refuses to start the next child. null (default) means no project bound, leaving the deployment-wide one; otherwise an integer ≥ 1. The effective bound is the smaller of the two. Settable/clearable via update-project.
audit_reads_enabledbooleanOpts the project into read auditing: when true, GET requests naming this project are recorded in the audit log alongside mutations. false by default. Settable via update-project.
require_priced_modelbooleanRefuses a generation whose model carries no price-book row with 409 MODEL_NOT_PRICED. false by default. Settable via update-project. See Priced models.
default_conversation_retrievalstringWhat a conversation that names no retrieval of its own does: embed or none (default).
trace_content_retention_daysinteger | nullDays of trace/generation content retention before the daily sweep purges it. null (default) disables retention; otherwise an integer ≥ 1. Settable/clearable via update-project.
trace_content_modestringfull (default) or none. none is zero-retention: trace and generation content is never written for any agent in the project. Settable via update-project.
paused_atstring | nullWhen the project was paused; null while it runs.
pause_reasonstring | nullThe reason the pause named, up to 256 characters; null while the project runs or when the pause named none.
created_atstringISO 8601 creation timestamp
updated_atstringISO 8601 last-updated timestamp

Key Concepts​

Project Access via Policies​

Access is granted by attaching a Policy to the user (or API key) with an Allow statement covering the project's SRN pattern:

{
"statement": [
{
"effect": "Allow",
"action": ["projects:GetProject", "files:ListFiles", "files:GetFile"],
"resource": ["srn:proj_ABC:*:*"]
}
]
}

Walkthrough: Permissions in Practice - Step 3 (Create the Analytics project).

All projects: a wildcard project segment:

{ "resource": ["srn:*:*:*"] }

Visibility Rules​

  • Admin users see all projects.
  • API key callers scoped to a project see only that project.
  • Regular users see only the projects covered by the SRN patterns in their attached policies.

Authorization is policy-only; a project-scoped grant is honored by every project endpoint, including GET /projects/{id}. See IAM.

Default Model Route​

default_model_route_id names the model route inherited by every consumer binding neither model_route_id nor ai_provider_id, giving agents, chats, and memory completions failover without editing each:

soat update-project --project-id proj_… --default_model_route_id route_…

The route must belong to the project. An explicit binding always wins. Repointing is free; clearing returns 409 PROJECT_DEFAULT_ROUTE_INHERITED while any consumer inherits it, and deleting the route itself returns 409 MODEL_ROUTE_HAS_DEPENDENTS. Governed by projects:UpdateProject.

Priced models​

require_priced_model refuses a generation whose model no price row covers, before the provider is called:

soat update-project --project-id proj_ABC --require_priced_model true

The refusal is 409 MODEL_NOT_PRICED, and error.meta.unpriced_rows names each (provider, model, component) to price. It is raised before the generation record exists, so a refused turn is neither recorded nor metered.

  • Both billable token components are checked, input_tokens and output_tokens: a model priced for one and not the other meters a cost that understates itself. The cache components fall back to the input_tokens rate and need no row of their own.
  • An agent bound to a model route is held to every target, not just the first: a failover bills whichever target answers.
  • Prices resolve through the usual tiers, so a per-provider override or a project rate satisfies the gate — see Usage — Pricing.
  • The check reads the configured model name. A provider that answers under a more specific id meters under that id; the quota_unpriced exception names it.
  • Embeddings are not gated: their rate is deployment configuration, outside every price-book tier.

false (the default) runs the model and meters it at cost_usd: null. A cost_usd quota answers the same gap from the other side: it reads a window the meter already wrote, so it refuses spend that has already happened, where this refuses spend before it starts.

Pausing a project​

POST /api/v1/projects/{project_id}/pause is a kill switch: one call stops everything the project runs. It is fireable by a person or by an automation — it is an IAM action (projects:PauseProject), so a project key granted it can pause its own project on an anomaly.

soat pause-project --project-id proj_ABC --reason "spend anomaly"
soat resume-project --project-id proj_ABC

While paused_at is set:

WhatUnder the pauseOn resume
Agent, session and conversation generations, tool-output continuations, chat completions, direct tool calls409 PROJECT_PAUSEDAccepted again
Orchestration run and eval run starts, manual and webhook trigger fires409 PROJECT_PAUSED, no run or firing writtenAccepted again
Event triggersA failed firing with code PROJECT_PAUSEDFire again
Schedule triggersNot firedFire from their next occurrence after now; a missed one is not fired late
Live orchestration runsPaused: parked at the next checkpoint with required_action.type: paused and the project's pause_reasonResumed in the background
Open tasksAutomation paused: transitions work, state dispatches are suppressedSuppressed dispatches run, as the caller who resumed
Queued eval itemsNot claimed; the run stays runningContinue from where they stopped

A generation, node or eval item already in flight finishes. Reads and writes keep working, so the project can be inspected and fixed while paused — including writes that embed content, such as document ingestion and memory writes, which are not starts. loop / sub_orchestration children started under the pause are born paused.

Pausing is idempotent and keeps the first reason. POST /api/v1/projects/{project_id}/resume (projects:ResumeProject) answers 409 PROJECT_NOT_PAUSED on a running project, and is not offered to agents as a soat tool: deciding to spend again is left to a person or an explicit credential.

The resume hands back only what the pause held. A run or task an operator paused before the project was paused keeps its own pause; resuming one run or task on its own while the project is paused is 409 PROJECT_PAUSED.

Both transitions emit a webhook event: projects.paused and projects.resumed, each carrying the project.

Deletion​

Deleting a project with any dependent resource returns 409 Conflict, code PROJECT_HAS_DEPENDENTS. Every project-scoped resource counts, including those accumulated while running:

?force=true deletes all dependents in one transaction, including billing/usage history and the stored bytes of files.

The audit log is the exception: entries outlive the project with project_id cleared.

Common Errors​

StatusBodyCauseWhat to do
403{ "error": "Forbidden" }Caller isn't the admin role — creating, renaming, and deleting a project are admin-onlyAuthenticate as the admin user, or have an admin perform the operation
403{ "error": "Forbidden" }GET /projects/{id} (or a nested resource route) with a policy/API key that doesn't cover this project's SRN — e.g. a project key created for a different projectCheck the caller's attached policies cover srn:<this-project-id>:*:*, or use a key scoped to this project — see Project Access via Policies
404—The project ID doesn't exist, or the caller can't see it because no policy grants access to it (existence isn't leaked)Verify the ID; if it should exist, confirm a policy grants visibility — see Visibility Rules
409{ "error": { "code": "PROJECT_HAS_DEPENDENTS" } }Deleting a project that still has dependent resourcesPass ?force=true, or delete the dependent resources first — see Deletion
409{ "error": { "code": "PROJECT_PAUSED" } }Starting work — a generation, run, eval run, tool call or trigger fire — or resuming a run or task in a paused projectResume the project once whatever paused it is resolved; error.meta.pause_reason says why — see Pausing a project
409{ "error": { "code": "PROJECT_NOT_PAUSED" } }Resuming a project that is not pausedNothing to do; paused_at is null
409{ "error": { "code": "MODEL_NOT_PRICED" } }A generation on a project with require_priced_model whose model carries no price rowPrice the (provider, model, component) rows in error.meta.unpriced_rows, or set require_priced_model to false — see Priced models
409{ "error": { "code": "PROJECT_DEFAULT_ROUTE_INHERITED" } }Clearing default_model_route_id while consumers that bind nothing inherit it — they would be left with no resolvable modelBind those consumers explicitly (meta.sample names some), or repoint the default to another route, which is always allowed — see Project default route

Examples​

Create a project​

soat create-project --name "My Project"

Get a project​

soat get-project --project-id proj_ABC

Delete a project​

soat delete-project --project-id proj_ABC

# Force-delete a project along with all of its dependent resources
soat delete-project --project-id proj_ABC --force true