#Projects
List projects, retrieve project details, manage partner project lifecycle/profile/assignment state, update draft runtime workspaces, run controlled release operations, manage write-only project secrets, access legacy project billing configuration.
- Production API:
https://api.aidial.ai - Version prefix:
/v1
This page omits environment-specific host names, internal topology, and deployment details.
Protected project routes use shared authentication unless the endpoint states a stricter scope. Shared authentication resolves credentials in this order:
X-API-KeyAuthorization: Bearer <jwt>
Both modes resolve to an AuthContext. Bearer authentication uses an AiDial-issued access token for first-party Portal sessions.
Project metadata, lifecycle state, drafts, published versions, legacy project billing configuration are stored server-side. Project-secret values are handled by server-side secret writers and are not returned by project responses. Project responses are scoped to the authenticated client_id; out-of-scope projects return 404 Not Found to avoid tenant enumeration. Missing or invalid authentication returns 401 Unauthorized.
#Response Shapes
The projects router has these current response families:
- Project list/detail responses use project detail, draft, published version, pagination, and partner error envelopes.
- Partner lifecycle/profile/assignment responses use the lifecycle snapshot shape.
- Workspace subresources return safe domain snapshots or validation findings for draft configuration sections.
- Controlled release routes return validation reports, publish outcomes, safe revision diffs, or runtime drift reports.
- Project-secret routes return metadata and reference previews only; plaintext secret values are write-only.
- Legacy billing-focused endpoints use the older project and billing response models and may return the general
HTTPExceptionerror shape.
#Project Detail
Used by partner/admin project lifecycle endpoints.
| Field | Type | Description |
|---|---|---|
project_id | string | Unique project identifier |
client_id | string | Client identifier |
display_name | string | Project display name |
timezone | string | Project timezone (IANA format) |
lifecycle_state | string | active when is_active=true, otherwise archived |
has_draft | boolean | Whether a current draft exists |
latest_published_version_number | integer or null | Latest published runtime version number |
created_at | string (ISO 8601) | Creation timestamp |
updated_at | string (ISO 8601) | Last update timestamp |
created_by | string | Actor identifier recorded at project creation |
initial_draft_etag | string or null | Nullable model field; current lifecycle create responses use the lifecycle snapshot shape instead |
testing_override_summary | object or null | Testing override production summary when the router attaches it |
#Lifecycle Snapshot
Used by POST /v1/projects, lifecycle/profile routes, archive/restore, and project assignment routes.
| Field | Type | Description |
|---|---|---|
project | object | Lifecycle project metadata |
project.project_id | string | Unique project identifier |
project.client_id | string | Client identifier |
project.display_name | string | Project display name |
project.timezone | string | Project timezone (IANA format) |
project.lifecycle_state | string | setup, active, or archived |
project.revision_token | string | Lifecycle concurrency token |
project.latest_published_version | integer or null | Latest published runtime version |
project.draft_status | string | draft when a draft exists, otherwise none |
project.publish_status | string | published or not_published |
project.created_at | string (ISO 8601) | Creation timestamp |
project.created_by | string | Actor identifier recorded at project creation |
project.updated_at | string (ISO 8601) | Last lifecycle update timestamp |
project.lifecycle_notes | string | Latest lifecycle note, present when one has been recorded |
project.updated_by, project.archived_at, project.archived_by, project.restored_at, project.restored_by | string (ISO 8601) or string | Transition audit fields, each present only once the corresponding action has occurred |
profile | object | Safe project profile fields |
permissions | object | Current caller permissions for lifecycle operations |
assignments | array | Project visibility assignments |
available_members | array or null | Eligible assignment targets when included for partner admins |
updated_at | string (ISO 8601) | Snapshot update timestamp |
#Legacy Project
Used by the legacy project list/detail/update response path.
| Field | Type | Description |
|---|---|---|
project_id | string | Unique project identifier |
client_id | string | Client identifier |
display_name | string or null | Project display name |
rate_per_minute | number | Call rate per minute |
currency | string | Billing currency (ISO 4217) |
billing_model | string | Billing model, such as per_minute, per_call, or tiered |
minimum_billable_seconds | integer | Minimum billable duration per call |
is_active | boolean | Whether the project is active |
timezone | string | Project timezone (IANA format) |
created_at_utc | string (ISO 8601) | Creation timestamp |
updated_at_utc | string (ISO 8601) | Last update timestamp |
#GET /v1/projects
Returns projects for the authenticated client.
Authentication: Required (X-API-Key or Authorization: Bearer <jwt>)
For partner_admin, partner_user, aidial_admin, and aidial_operator scopes, the response is paginated and uses the project detail shape. Partner read scopes are assignment-aware. Internal admin/operator scopes list projects for the resolved AuthContext.client_id. For other authenticated legacy scopes, the response is { "projects": [...] } using the legacy project shape.
#Parameters
| Name | In | Type | Required | Default | Description |
|---|---|---|---|---|---|
include_inactive | query | boolean | No | false | Include inactive projects. For partner/admin list responses this also includes archived projects |
include_archived | query | boolean | No | false | Include archived projects in partner/admin list responses |
data_env | query | string | No | Auth context data environment | Partner assignment data environment. Supported values: prod, staging, dev |
sort_by | query | string | No | created_at_utc | Partner/admin sort field: display_name, created_at_utc, updated_at_utc, or project_id |
sort_order | query | string | No | desc | Partner/admin sort direction: asc or desc |
page | query | integer | No | 1 | Partner/admin page number, minimum 1 |
page_size | query | integer | No | 20 | Partner/admin page size, 1 to 100 |
#Partner/Admin Response
| Field | Type | Description |
|---|---|---|
items | array | Project detail objects |
pagination | object | Pagination metadata |
pagination.page | integer | Current page |
pagination.page_size | integer | Results per page |
pagination.total_records | integer | Total matching records |
pagination.total_pages | integer | Total pages |
#Example
Request:
curl -X GET "https://api.aidial.ai/v1/projects?page=1&page_size=20" \
-H "X-API-Key: sk_live_YOUR_API_KEY_HERE"Response (200 OK):
{
"items": [
{
"project_id": "client_example_001-Reception_AI-a1b2c3d4",
"client_id": "client_example_001",
"display_name": "Reception AI",
"timezone": "Australia/Brisbane",
"lifecycle_state": "active",
"has_draft": true,
"latest_published_version_number": 2,
"created_at": "2026-01-15T00:00:00Z",
"updated_at": "2026-04-01T00:00:00Z",
"created_by": "apikey:42"
}
],
"pagination": {
"page": 1,
"page_size": 20,
"total_records": 1,
"total_pages": 1
}
}#POST /v1/projects
Creates a new project lifecycle row for the authenticated client and creates an initial draft containing display_name and timezone.
Authentication: partner_admin scope required
#Headers
| Name | Required | Description |
|---|---|---|
Idempotency-Key | Conditional | Required unless idempotency_key is supplied in the request body; 1 to 128 characters |
#Request Body
| Field | Type | Required | Description |
|---|---|---|---|
display_name | string | Yes | Project display name, 3 to 120 characters |
timezone | string | Yes | IANA timezone identifier |
managed_client_id | string | Yes | Must match the authenticated client_id |
profile | object | No | Optional lifecycle profile fields |
idempotency_key | string | Conditional | Alternative idempotency-key location when the header is not supplied |
#Response
Returns a lifecycle snapshot. The generated project_id is based on managed_client_id, a sanitized display name, and a short random suffix.
#Example
Request:
curl -X POST "https://api.aidial.ai/v1/projects" \
-H "Authorization: Bearer YOUR_PORTAL_JWT" \
-H "Idempotency-Key: create-reception-ai-001" \
-H "Content-Type: application/json" \
-d '{"display_name": "Reception AI", "timezone": "Australia/Brisbane", "managed_client_id": "client_example_001"}'Response (201 Created):
{
"project": {
"project_id": "client_example_001-Reception_AI-a1b2c3d4",
"client_id": "client_example_001",
"display_name": "Reception AI",
"timezone": "Australia/Brisbane",
"lifecycle_state": "setup",
"revision_token": "1",
"latest_published_version": null,
"draft_status": "draft",
"publish_status": "not_published",
"created_at": "2026-04-17T02:00:00Z",
"created_by": "300000000000000000",
"updated_at": "2026-04-17T02:00:00Z"
},
"profile": {
"tags": []
},
"permissions": {
"can_create": true,
"can_edit": true,
"can_archive": true,
"can_restore": false,
"can_manage_assignments": true,
"read_only_reason": null
},
"assignments": [],
"updated_at": "2026-04-17T02:00:00Z"
}#GET /v1/projects/{project_id}
Returns details for a single project.
Authentication: partner_admin, partner_user, aidial_admin, or aidial_operator scope for the project detail response. Client portal scopes receive 404 Not Found from this route. Other authenticated legacy scopes receive the legacy project shape, matching the legacy branch of GET /v1/projects.
#Parameters
| Name | In | Type | Required | Default | Description |
|---|---|---|---|---|---|
project_id | path | string | Yes | - | Unique project identifier |
#Response
Returns a project detail object for partner/admin read scopes. Out-of-scope projects return 404 Not Found.
#Example
Request:
curl -X GET "https://api.aidial.ai/v1/projects/client_example_001-Reception_AI-a1b2c3d4" \
-H "X-API-Key: sk_live_YOUR_API_KEY_HERE"#GET /v1/projects/{project_id}/lifecycle
Returns the safe lifecycle/profile/assignment snapshot for a project.
Authentication: partner_admin, partner_user, aidial_admin, or aidial_operator scope required
#Response
Returns the lifecycle snapshot shape. Partner-member access is assignment-aware; out-of-scope projects return 404 Not Found.
#PATCH /v1/projects/{project_id}/lifecycle
Updates safe lifecycle/profile metadata using lifecycle revision-token concurrency.
Authentication: partner_admin scope required
#Request Body
At least one mutable field must be provided.
| Field | Type | Required | Description |
|---|---|---|---|
display_name | string | No | Project display name, 3 to 120 characters |
timezone | string | No | IANA timezone identifier |
lifecycle_notes | string | No | Lifecycle note, up to 500 characters |
profile | object | No | Lifecycle profile fields |
revision_token | string | Yes | Current lifecycle revision token |
#Response
Returns the updated lifecycle snapshot. A stale lifecycle revision returns 409 Conflict; archived projects are read-only until restored.
#PATCH /v1/projects/{project_id}
Closed legacy partner metadata route. Current code always returns non-enumerating 404 Not Found; use PATCH /v1/projects/{project_id}/lifecycle for lifecycle/profile metadata updates.
Authentication: Required, but no scope can use this route successfully
#Parameters
| Name | In | Type | Required | Default | Description |
|---|---|---|---|---|---|
project_id | path | string | Yes | - | Unique project identifier |
#Response
Always returns 404 Not Found through the partner error envelope.
#POST /v1/projects/{project_id}/archive
Archives a project by setting lifecycle state to archived and aidial_projects.is_active=false. The operation is idempotent.
Authentication: partner_admin scope required
#Parameters
| Name | In | Type | Required | Default | Description |
|---|---|---|---|---|---|
project_id | path | string | Yes | - | Unique project identifier |
#Headers
| Name | Required | Description |
|---|---|---|
Idempotency-Key | Conditional | Required unless idempotency_key is supplied in the request body |
#Request Body
| Field | Type | Required | Description |
|---|---|---|---|
revision_token | string | Yes | Current lifecycle revision token |
lifecycle_note | string | No | Optional transition note |
idempotency_key | string | Conditional | Alternative idempotency-key location when the header is not supplied |
#Response
Returns the updated lifecycle snapshot.
#POST /v1/projects/{project_id}/restore
Restores an archived project by setting lifecycle state back to setup and aidial_projects.is_active=true. The operation is idempotent.
Authentication: partner_admin scope required
#Parameters
| Name | In | Type | Required | Default | Description |
|---|---|---|---|---|---|
project_id | path | string | Yes | - | Unique project identifier |
#Headers
| Name | Required | Description |
|---|---|---|
Idempotency-Key | Conditional | Required unless idempotency_key is supplied in the request body |
#Request Body
| Field | Type | Required | Description |
|---|---|---|---|
revision_token | string | Yes | Current lifecycle revision token |
lifecycle_note | string | No | Optional transition note |
idempotency_key | string | Conditional | Alternative idempotency-key location when the header is not supplied |
#Response
Returns the updated lifecycle snapshot.
#POST /v1/projects/{project_id}/assignments
Grants safe project visibility to an eligible partner member.
Authentication: partner_admin scope with partner membership context required
#Headers
| Name | Required | Description |
|---|---|---|
Idempotency-Key | Conditional | Required unless idempotency_key is supplied in the request body |
#Request Body
| Field | Type | Required | Description |
|---|---|---|---|
member_id | string | Yes | Eligible partner member identifier |
role_visibility | string | Yes | read or manage |
assignment_note | string | No | Optional assignment note |
revision_token | string | Yes | Current project lifecycle revision token |
idempotency_key | string | Conditional | Alternative idempotency-key location when the header is not supplied |
#Response
Returns the updated lifecycle snapshot.
#DELETE /v1/projects/{project_id}/assignments/{assignment_id}
Removes a project visibility assignment.
Authentication: partner_admin scope with partner membership context required
#Headers
| Name | Required | Description |
|---|---|---|
Idempotency-Key | Conditional | Required unless idempotency_key is supplied in the request body |
#Request Body
| Field | Type | Required | Description |
|---|---|---|---|
assignment_revision_token | string | Yes | Current assignment revision token |
idempotency_key | string | Conditional | Alternative idempotency-key location when the header is not supplied |
#Response
Returns the updated lifecycle snapshot.
#Project Workspace Subresources
Several project configuration areas expose safe draft/published snapshots and save validation results under /v1/projects/{project_id}. These routes update the project draft in the configuration revision store and use the same project scoping and anti-enumeration behaviour as the core project routes.
GET /dashboard-metadata also accepts a unique same-client casing alias for legacy rows whose stored project_id casing drifted from runtime call data. The response uses the requested project_id while snapshot storage remains keyed to the existing project row; ambiguous or cross-client matches still return the normal non-enumerating denial.
| Route | Authentication | Purpose |
|---|---|---|
GET /dashboard-metadata?refresh=false | Client, partner, or internal read scopes | Return a safe dashboard metadata snapshot |
POST /dashboard-metadata/publish | Bearer client_admin | Publish safe API-managed dashboard analytics metadata; requires Idempotency-Key |
GET /governance | Bearer partner_admin or partner_user | Return governance snapshot |
PUT /governance/draft | Bearer partner_admin | Save governance draft; requires Idempotency-Key |
POST /governance/validate | Bearer partner_admin | Validate governance draft; requires Idempotency-Key |
GET /testing-overrides | Bearer partner_admin or partner_user | Return testing override contract snapshot |
PATCH /testing-overrides | Bearer partner_admin | Save or disable testing overrides; Idempotency-Key must be at least 8 characters |
GET /transfer-routing | Partner/internal read scopes | Return safe transfer-routing snapshot; partner draft views include saved customer destinations and customer_destinations_version |
PUT /transfer-routing/draft | partner_admin | Save configured transfer-routing subset; customer_destinations_version required, plus exact If-Match for an existing draft |
POST /transfer-routing/validate | partner_admin | Validate current transfer-routing config; request body must be empty or {} |
POST /transfer-routing/test | partner_admin | Dry-run check of one destination ({"destination_id": "...", "mode": "dry_run"}): no call is placed and no configuration changes; returns status, mode, destination_id and audit_event_id, or 422 findings |
GET /provider-configuration | partner_admin or partner_user | Return provider configuration snapshot |
PUT /provider-configuration | partner_admin | Save provider configuration draft subset with optional If-Match; runtime-native blocks are read-only here |
POST /provider-configuration/validate | partner_admin or partner_user | Validate provider configuration; only partner_admin may supply an override body |
GET /llm-policy | partner_admin or partner_user | Return LLM policy snapshot |
PUT /llm-policy | partner_admin | Save legacy or native primary/secondary/fallback policy; If-Match required when a draft exists, otherwise omit or use *; omitted native routes are retained, null removes, hidden same-provider settings survive; no implicit format migration |
POST /llm-policy/validate | partner_admin | Validate LLM policy |
GET /plugin-configuration | partner_admin or partner_user | Return plugin configuration snapshot |
PUT /plugin-configuration | partner_admin | Save plugin configuration draft subset with If-Match or body revision_token when a draft exists |
POST /plugin-configuration/validate | partner_admin | Validate plugin configuration with the same revision guard |
GET /identity-verification | partner_admin or partner_user | Return identity verification snapshot |
PUT /identity-verification | partner_admin | Save runtime-shaped identity verification draft subset with optional If-Match |
POST /identity-verification/validate | partner_admin | Validate runtime-shaped identity verification |
GET /booking | partner_admin or partner_user | Return booking posture and readiness snapshot |
PUT /booking | partner_admin | Save booking draft subset with optional If-Match |
POST /booking/validate | partner_admin | Validate booking posture and readiness |
GET /email-delivery | partner_admin or partner_user | Return email delivery snapshot |
PUT /email-delivery | partner_admin | Save email delivery draft subset with optional If-Match |
POST /email-delivery/validate | partner_admin | Validate email delivery |
GET /email-sms-policy | partner_admin or partner_user | Return combined email & SMS policy snapshot; draft state and edit controls are included only for partner_admin |
PUT /email-sms-policy | partner_admin | Save email & SMS policy draft subset with optional If-Match |
POST /email-sms-policy/validate | partner_admin | Validate email & SMS policy |
GET /outbound-compliance | Partner/internal read scopes | Return outbound compliance snapshot |
PUT /outbound-compliance | Bearer partner_admin, aidial_admin, or aidial_operator | Save outbound compliance draft subset; If-Match required |
POST /outbound-compliance/validate-recipient | Partner/internal read scopes | Preview recipient-local calling-window decisions |
GET /sms-policy | partner_admin or partner_user | Return SMS policy snapshot |
PUT /sms-policy | partner_admin | Save SMS policy draft subset; If-Match must match body revision_token |
POST /sms-policy/validate | partner_admin | Validate SMS policy; request body required |
Transfer-routing destinations expose phone_type: local | mobile | null and transfer_mode: warm_transfer | cold_transfer | null; null denotes required partner setup and is never a save default. Draft saves require both explicit valid choices and the GET's opaque customer_destinations_version. That token binds saved customer inputs, full partner base and tenant/project/data environment; it is rechecked under the customer-section lock inside the existing draft transaction. An existing draft still requires its exact ETag (missing: 428; stale: 409). Missing customer/base token or incomplete setup returns validation errors. Normal validation/publication uses the saved config without a later merge. New unsupported holiday settings remain blocked until explicitly corrected.
Partner fallback routing is not supported: the runtime retries only through transfer.fallback_chain and operator-managed transfer sequences. A workspace draft that sends top-level fallback_chains, or a destination retry_count or fallback_priority, returns 422 with unsupported_field. GET no longer returns these fields, and a workspace draft save removes any stored copies from the merged config. The runtime also has no reader for a keypad acceptance key or an SMS alert number (M-PP11): a destination warm_transfer.accepted_key or failed_transfer_notification.sms returns 422 with unsupported_field. Failed-transfer alerts in this workspace are email-only. The workspace merge preserves the runtime SMS block in its imported baseline, while importing a new customer revision can still apply the customer's SMS settings; the merge also removes a stored acceptance.accepted_key.
M-PS76 adds these AiDial deploy-gate checks to transfer-routing validation (it does not cover every gate rule). A destination label, and a stored display_name, must be plain text of at most 128 characters, without email addresses, links, phone-number-like digit runs or reserved words such as token or secret; a submitted label that is missing, blank or unsafe makes the draft save return 422 with invalid_destination_label at destinations.<i>.label, and an unsafe stored name is a blocking invalid_destination_label finding. A destination whose stored acceptance.enabled is true must be a warm transfer (acceptance_requires_warm_transfer) and needs transfer.wait_until_answered: true (acceptance_requires_wait_until_answered) and the operator-managed acceptance settings reprompt_message, decline_message_to_caller, no_response_message_to_caller, a string accepted_context_template, timeout_seconds from 5 to 12, max_attempts 1 or 2, and on_decline message_capture, end_call or fallback (which needs transfer.fallback_chain.enabled: true), with only {caller_name} and {reason} placeholders in the prompt and template. Each missing or unsupported value is a blocking acceptance_setting_required finding on save, validate and publish, at transfer.destinations.<id>.acceptance.<key>, or at the workspace field warm_transfer.acceptance_prompt, warm_transfer.transferee_briefing or warm_transfer.acceptance_timeout_seconds for the prompt, template and timeout that partners edit. A destination without ring_timeout_seconds shows transfer.default_ring_timeout_seconds, and a save writes the destination timeout only when it is changed or already set, including across a transfer-mode change; with neither value the destination reports blocking invalid_ring_timeout.
GET, validate and save use the same imported customer baseline. Existing portal_transfer.customer_destinations_import metadata records scoped section revision, customer holiday/phone-type choices and consumed deletion IDs. Reopen preserves partner edits; an unrelated later customer edit does not overwrite a partner phone-type correction or replay an already-consumed deletion. Weekly edits retain the full holiday object when customer intent is unchanged. For custom hours, Follow weekly hours maps to runtime disabled (weekly hours still apply); Closed is preserve-only: under the customer section lock, the effective current customer state (including runtime-initial rows) must already be custom/closed and the authoritative partner draft or published config must still have a representable custom-schedule closure policy. The installed-baseline check also applies. Returning to Closed after Follow weekly hours or after leaving custom availability is rejected before persistence; contact the partner for setup. Available all day is unsupported and rejected. Changing to Follow weekly hours replaces the holiday object with only mode, removing incompatible custom keys. Unrepresentable policies, including holiday-opening exceptions, remain partner-managed read-only summaries with public_holiday_mode: null. Always/inherited availability keeps its existing runtime semantics; irrelevant holiday choices do not prevent publish convergence. Closed and Holiday weekly entries are accepted alongside time ranges.
Operator-managed transfer sequences (transfer.destinations.<source>.cascade, an ordered list of backup destination keys) are set only through the full draft (PUT /draft). The workspace snapshot never returns them, a workspace payload field cascade is unsupported_field, and workspace, customer and publish saves keep existing lists unchanged. When a sequence exists, workspace save, validate and every publish path return blocking invalid_cascade (empty or non-list value) or cascade_* findings for an invalid list, a member that is not a destination of the same project or repeats the source, a participant that is not a warm, acceptance-enabled, non-PBX destination, an acceptance opening prompt containing a {placeholder}, transfer.wait_until_answered not true, a disabled or incomplete transfer.fallback_chain, hangup.enabled not true or a non-positive hangup.max_duration.timeout_seconds, or any hangup.max_duration.warning. A workspace draft save that removes or renames a sequence participant, including an imported customer deletion, returns 422 with cascade_destination_removed.
M-PS83: operator-configured transfer lines (Teams Auto Attendant or Call Queue) set transfer.destinations.<id>.dtmf (keys pressed after the line answers) and acceptance.answer_wait_seconds (silent wait for a person) only through the full draft (PUT /draft). The workspace snapshot never returns them, and workspace and customer saves keep both values unchanged, including when a workspace edit turns acceptance off or switches to cold transfer, so the conflict is reported instead of silently dropped. When either key is present, workspace save, validate and every publish path mirror the runtime gate with blocking findings: transfer_line_keys_invalid at .dtmf (not a string of 1-32 characters from 0-9, *, # and w, with at least one key); transfer_line_requires_warm_acceptance at .transfer_mode (not warm, or a pbx_extension) or .warm_transfer.acceptance_enabled (acceptance not enabled); transfer_line_keys_require_answer_wait at .acceptance.answer_wait_seconds (keys without an answer wait); answer_wait_seconds_invalid at .acceptance.answer_wait_seconds (not a number from 5 to 60); answer_wait_opening_prompt_placeholder at .warm_transfer.acceptance_prompt (any {placeholder}, including {caller_name} and {reason}); and answer_wait_budget_exceeded at both .ring_timeout_seconds and .warm_transfer.acceptance_timeout_seconds when the ring timeout (destination, else transfer.default_ring_timeout_seconds) + answer wait + max_attempts x timeout_seconds + 1 s per dtmf character exceeds 75 s. Destinations without either key are unaffected.
S-PS37: a destination ID the customer deleted stays hidden from the customer, so it must not be re-used. Workspace GET findings, validate and draft save report a blocking customer_deleted_destination_id finding at transfer.destinations.<id> ("This ID was removed by the customer. Use a new ID.") for any such ID in the draft or the submitted destinations, and the draft save then returns 422 and writes nothing. A draft save whose customer_destinations_version no longer matches returns the existing 409 conflict before any customer-input finding. Only these transfer-routing routes run this check; the full draft PUT /draft and publish paths do not.
S-PS38: operator-managed transfer groups (a destination with exactly members, selection and warmup_message) and availability checks (transfer.destinations.<person>.availability_check) are set only through the full draft (PUT /draft), which stores them unchanged and does not check them. A group is not a destination row: the workspace snapshot, dry-run, the dashboard and customer responses leave it out, and none of them return members, selection or availability_check. A workspace payload field with any of those names is unsupported_field. Workspace, customer and publish saves keep both unchanged, including a group the submitted destinations omit, and a submitted row using a group's key is refused by group_keys_invalid.
Workspace GET findings, validate and every publish path mirror the runtime's group rules with blocking findings. At the group: group_keys_invalid (not exactly those three keys), group_selection_invalid (selection not round_robin, sequence or random), group_warmup_required, group_members_invalid (not a non-empty list), group_member_missing, group_member_is_group and group_member_repeated. At each member: group_requires_warm_acceptance, group_opening_prompt_placeholder, group_member_requires_fallback_on_decline (acceptance.on_decline not fallback) and group_member_requires_notification (no email or SMS failed-transfer notification enabled); its number stays under the existing row rules. Since M-PS84, duplicate_phone_number (at transfer.destinations.<key>.phone_number) is reported only when a number repeats between participants of one transfer sequence or one group, disabled participants included, with numbers compared after trimming and empty numbers ignored; destinations outside the sequence or group may share a number. group_selection_without_members reports selection on a person. Project prerequisites use group_requires_wait_until_answered, group_requires_fallback_chain, group_requires_call_duration_limit and group_duration_warning_not_allowed, or the matching cascade_* finding when the project also has a sequence; group_requires_hold_music (transfer.briefing.hold_music.enabled not true) applies to any group. In a customer dashboard publish, group and availability-check findings collapse into one generic entry.
A workspace draft save, including an imported customer edit, returns 422 with operator_managed_destination_changed at transfer.destinations.<key> and writes nothing when it removes or renames a group, a group member or a destination with a check, changes a group's members or selection, changes a check, or changes the number of a destination with a check. display_name stays editable, and so does the number of a member without a check. Removing a sequence participant keeps cascade_destination_removed. A customer delete of a group member or a destination with a check returns the existing transfer_destination_delete_blocked. The check's own settings, plugin and country policy are checked by the deploy gate, not here. A key that exists only in the project file never reaches the API draft, so a workspace save or publish neither sees nor removes it.
S-PS38 n8n plugin settings: webhook_base_url is required, and availability_check_path and the secrets api_key and availability_check_secret are optional. workflow_id is no longer in the catalog: a request carrying it returns unsupported_plugin_config_field, and a value already stored is kept but not shown. A blank optional text field means unset. Path and secret must be set together (availability_check_settings_incomplete). The secret must reference one flat field under this client (availability_check_secret_invalid), so its project secret uses target field <instance_id>_availability_check_secret, not a plugins/... path.
For LLM policy saves, missing an existing draft token returns 428; using * for an existing draft returns 412; stale concrete tokens return 409. Save and validate seed only an absent/null/empty draft LLM slice from the published policy, preserving all unrelated draft settings. Malformed nonempty drafts remain authoritative and cannot be masked by published state.
Native timeouts are finite positive numbers; the legacy flat editor's 1–60 second bound does not apply to native routes. A supported provider can be introduced only when it already exists on a valid stored native route or is enabled in the project catalog. New Azure/LangGraph setup and primary realtime transitions require the full project workflow. Existing model edits retain their current behavior; this limited editor does not establish a global model entitlement policy.
#POST /v1/projects/{project_id}/test-call/request
Records an ops-mediated request for a guided test call on a project. This endpoint only records a request to operations: it does not place a call and returns no transcripts, recordings, audio, caller data, or provider credentials.
Authentication: partner_admin scope required (partner_user and any other scope receive 404 Not Found)
#Parameters
| Name | In | Type | Required | Default | Description |
|---|---|---|---|---|---|
project_id | path | string | Yes | - | Unique project identifier |
#Request Body
The request body carries only a free-text note. The project, client, and requester are resolved server-side from the authenticated, assignment-gated scope; no other field is read or echoed.
| Field | Type | Required | Description |
|---|---|---|---|
note | string | Yes | Description of what is needed for the guided test call, 1 to 2000 characters |
#Response
| Field | Type | Description |
|---|---|---|
status | string | requested |
project_id | string | Project identifier |
request_id | string | Generated request identifier |
#POST /v1/projects/{project_id}/dashboard-metadata/publish
Publishes API-managed dashboard analytics metadata for a scoped client project without exposing the generic partner runtime draft/publish surface.
Authentication: bearer client_admin scope required
#Headers
| Header | Required | Description |
|---|---|---|
Idempotency-Key | Yes | Prevents duplicate dashboard metadata publishes for the same client actor and payload |
#Request Body
Only the closed analytics subset below is accepted. Unsupported fields are rejected.
| Field | Type | Required | Description |
|---|---|---|---|
analytics.outcome_enum[] | array | No | Safe dashboard outcome keys and display labels |
analytics.caller_mix_supported | boolean | No | Whether caller-mix metrics may be shown when privacy thresholds are satisfied |
analytics.caller_mix.stable_caller_key_available | boolean | No | Whether a stable caller identity key exists for privacy-safe caller-mix aggregation |
At least one supported analytics field is required. The API returns 409 Conflict when the latest published runtime version is not already deployed, preserves the latest deployed runtime document, forces display_name and timezone from the project row, validates the merged config, inserts a normal published configuration-revision row, and records it as deployed because this metadata is API-managed and requires no runtime rollout.
#Response
Returns client_id, project_id, revision_id, version_number, deploy_status, source_revision, and a safe dashboard metadata snapshot derived from the committed config. The saved current snapshot refresh is attempted after commit as a best-effort side effect; refresh failure is logged and does not change the response.
#GET /v1/projects/{project_id}/draft
Returns the current draft configuration for a project.
Authentication: partner_admin, aidial_admin, or aidial_operator scope required
#Parameters
| Name | In | Type | Required | Default | Description |
|---|---|---|---|---|---|
project_id | path | string | Yes | - | Unique project identifier |
#Response Headers
| Header | Description |
|---|---|
ETag | Current draft ETag, quoted |
#Response
| Field | Type | Description |
|---|---|---|
id | integer | Draft revision identifier |
project_id | string | Project identifier |
revision_type | string | Always draft |
config_data | object | Draft runtime configuration document |
etag | string | Current draft ETag |
author_user_id | string | Actor identifier for the draft author |
author_role | string | Actor role recorded with the draft |
created_at | string (ISO 8601) | Draft timestamp |
If the project exists but has no draft, the route returns 404 Not Found with a contextual partner error message.
#PUT /v1/projects/{project_id}/draft
Saves or updates the draft configuration for a project.
Authentication: partner_admin scope required
#Parameters
| Name | In | Type | Required | Default | Description |
|---|---|---|---|---|---|
project_id | path | string | Yes | - | Unique project identifier |
#Headers
| Name | Required | Description |
|---|---|---|
If-Match | Conditional | Required when updating an existing draft. Use the current ETag returned by GET /v1/projects/{project_id}/draft |
#Request Body
The request body must be a JSON object and must not exceed 1 MB.
| Field | Type | Required | Description |
|---|---|---|---|
config_data | object | Yes | Draft runtime configuration document |
Draft saves validate mirrored project metadata fields when present. config_data.display_name, if present, must be a non-empty string no longer than 255 characters. config_data.timezone, if present, must be a valid IANA timezone.
#ETag Behaviour
| State | Behaviour |
|---|---|
Draft exists and If-Match is omitted | 428 Precondition Required |
Draft exists and If-Match: * is sent | 412 Precondition Failed |
| Draft exists and ETag does not match | 409 Conflict |
No draft exists and If-Match is omitted or * | Creates a new draft |
| No draft exists and a concrete stale ETag is sent | 409 Conflict |
#Response
Returns the saved draft response and a new ETag response header.
#POST /v1/projects/{project_id}/publish
Publishes the current draft through the controlled release flow. The route requires a current successful validation report, creates a published revision with a deploy_pending publish outcome, deletes the draft, and updates the project's latest published version number.
Authentication: partner_admin scope required
#Parameters
| Name | In | Type | Required | Default | Description |
|---|---|---|---|---|---|
project_id | path | string | Yes | - | Unique project identifier |
#Headers
| Name | Required | Description |
|---|---|---|
Idempotency-Key | Yes | Required idempotency key, 1 to 128 characters |
If-Match | Yes | Current draft ETag |
#Request Body
| Field | Type | Required | Description |
|---|---|---|---|
validation_report_id | string | Yes | Current successful validation report for the draft and runtime target |
target_runtime | string | No | Runtime target; current code supports default |
#Publish Validation
The draft config_data must be a non-empty JSON object with:
| Field | Type | Required | Description |
|---|---|---|---|
display_name | string | Yes | Non-empty display name |
timezone | string | Yes | Valid IANA timezone |
If config_data.agents is present, publish also validates the agent contract before inserting the published revision. Canonical runtime shape is an object keyed by runtime agent name, including agents.main. Portal editor list shape is accepted only when it compiles to the approved runtime contract. During compilation, an explicit portal agent_id: main is treated as the runtime entry agent even when it is not the first list item; otherwise the first portal agent becomes runtime main. mixin_keys are folded into runtime tools, and router route_targets become runtime routes with { to, when }. Portal voice_override_key values must resolve to a full runtime tts object through voice_overrides, voices, or an agent-level tts object. Route and handoff targets are resolved against the original portal agent IDs before this main renaming step. critical and high agent findings block publish and leave the draft intact. medium and low findings do not block publish by themselves and are omitted from the publish error response.
Agent publish validation covers:
| Code | Blocking | Description |
|---|---|---|
missing_agent_id | Yes | Agent row does not include a stable agent ID |
duplicate_agent_id | Yes | Agent ID is repeated in the draft |
missing_label | Yes | Agent label is empty |
unsupported_role | Yes | Agent role is not single_agent, generic_router, or generic_task |
missing_handoff_target | Yes | Handoff target does not exist in the same draft |
missing_route_target | Yes | Route target does not exist in the same draft |
self_route | Yes | Agent routes or hands off to itself |
unresolved_tool | Yes | Referenced tool key is not available in the project catalog |
unresolved_mixin | Yes | Referenced mixin key is not available in the project catalog |
invalid_voice_override | Yes | Voice override key is not available in the project catalog |
route_cycle_without_task | Yes | Route graph cycles without a terminal generic_task agent |
missing_runtime_agents | Yes | Runtime agent configuration is missing or empty |
missing_runtime_entry_agent | Yes | Runtime agent configuration does not include an entry agent |
missing_runtime_agent_type | Yes | Runtime agent does not declare agent_type |
missing_runtime_routes | Yes | Runtime router agent does not compile to any route |
missing_runtime_handoff_target | Yes | Runtime handoff target does not target an existing agent |
missing_runtime_route_target | Yes | Runtime route does not target an existing agent |
missing_runtime_route_when | Yes | Runtime route is missing the required when condition |
unresolved_runtime_tts | Yes | Portal voice override does not resolve to a full runtime tts object |
invalid_runtime_handoff_targets | Yes | Runtime handoff targets are not a list |
runtime_task_has_routes | Yes | Runtime task agent defines routes instead of being terminal |
runtime_route_cycle_without_task | Yes | Runtime route graph cycles without a terminal task agent |
empty_welcome_greeting | No | Welcome greeting is empty |
Publish also runs blocking checks for raw secret references, transfer routing, provider configuration, LLM policy, plugin configuration, email delivery, SMS policy, identity verification, and outbound compliance where those sections are present in the draft. Identity verification validation expects runtime keys such as caller_lookup.plugin and sms_code.input_modes; editor aliases such as plugin_instance_id and channels are blocking findings. Blocking validation errors are returned in the standard partner validation envelope under error.details.field_errors[]. Findings are partner-safe and must not include raw prompt instructions, bearer tokens, raw secret values, ciphertext, or other secret material.
The publish background task no longer marks a version as runtime deployed by itself. A published revision remains pending until the controlled deployment path records deployment evidence or marks the deployment failed. The controlled project deployment installs this exact revision and records deploy_completed together with its runtime observations. A newer publish is refused with release_in_progress until then.
#Response
| Field | Type | Description |
|---|---|---|
publish_outcome_id | string | Controlled release outcome identifier |
parent_publish_outcome_id | string or null | Parent outcome for deploy completion evidence |
status | string | deploy_pending on successful publish before deployment evidence is recorded |
operation | string | publish |
project_id | string | Project identifier |
target_revision_id | integer | Published revision identifier |
target_version_number | integer or null | Published version number |
restored_from_revision_id | integer or null | Source revision identifier when the outcome was created by rollback, otherwise null |
restored_from_version_number | integer or null | Source published version number when the outcome was created by rollback, otherwise null |
latest_completed_published_version_number | integer or null | Latest completed runtime version |
runtime_target | string | Runtime target, currently default |
validation_report_id | string or null | Validation report used for release |
runtime_artifact_ref | string or null | Safe runtime artifact reference |
failure_code | string or null | Failure code when applicable |
failure_message | string or null | Failure message when applicable |
summary | string | Human-readable release outcome summary |
created_at_utc | string (ISO 8601) | Outcome creation timestamp |
testing_override_summary | object or null | Testing override production summary when present |
#POST /v1/projects/{project_id}/validate
Validates a draft or published version against active release-governance rules.
Authentication: partner_admin scope required
#Request Body
| Field | Type | Required | Description |
|---|---|---|---|
target.type | string | Yes | draft or published_version |
target.revision_token | string | Conditional | Required when target.type=draft |
target.version_number | integer | Conditional | Required when target.type=published_version |
target_runtime | string | No | Runtime target; current code supports default |
#Response
Returns a validation report with validation_report_id, status (passed, warning, or failed), is_publish_current, fingerprint, findings, and checked_at_utc.
#POST /v1/projects/{project_id}/rollback
Creates a controlled rollback release by validating and copying a completed published version into a new pending published revision.
Authentication: partner_admin scope required
#Headers
| Name | Required | Description |
|---|---|---|
Idempotency-Key | Yes | Required idempotency key, 1 to 128 characters |
#Request Body
| Field | Type | Required | Description |
|---|---|---|---|
target_version_number | integer | Yes | Completed published version to roll back to |
validation_report_id | string | Yes | Current validation report for the rollback target |
target_runtime | string | No | Runtime target; current code supports default |
#Response
Returns the same controlled release outcome shape used by POST /v1/projects/{project_id}/publish, with operation set to rollback.
#POST /v1/projects/{project_id}/runtime-provenance
Records internal deploy completion or failure evidence for a controlled release outcome.
Authentication: Bearer aidial_admin only
This is an internal deploy-evidence route, not a partner integration endpoint. It requires an Idempotency-Key header and a RuntimeProvenanceRequest body. deploy_completed evidence must include supported observed revision/version, schema version, contract version, and artifact hash fields; deploy_failed evidence must include failure_code and failure_message.
Releases are normally completed by the controlled project deploy, which shares this route's transaction logic. Do not hand-write deploy_completed evidence for a revision that the deploy has not installed; recording deploy_failed for a release that cannot be deployed is the supported way to release the project's publish slot.
#GET /v1/projects/{project_id}/versions
Returns paginated published version history for a project, sorted by version number descending.
Authentication: partner_admin, partner_user, aidial_admin, or aidial_operator scope required
#Parameters
| Name | In | Type | Required | Default | Description |
|---|---|---|---|---|---|
project_id | path | string | Yes | - | Unique project identifier |
page | query | integer | No | 1 | Page number, minimum 1 |
page_size | query | integer | No | 20 | Page size, 1 to 100 |
#Response
| Field | Type | Description |
|---|---|---|
items | array | Version list items |
items[].version_number | integer | Published version number |
items[].published_at | string (ISO 8601) | Publish timestamp |
items[].published_by | string | Display-safe actor label |
items[].validation_status | string | Validation status |
items[].deploy_status | string | Deployment status |
items[].latest_publish_outcome_status | string or null | Latest controlled release outcome status |
items[].rollback_eligible | boolean | Whether rollback is currently allowed |
items[].rollback_block_reason | string or null | Why rollback is blocked |
items[].testing_override_summary | object or null | Testing override production summary when present |
pagination | object | Pagination metadata |
#GET /v1/projects/{project_id}/versions/compare
Returns a safe redacted diff between a draft and/or published project revision.
Authentication: partner_admin, partner_user, aidial_admin, or aidial_operator scope required
#Query Parameters
| Name | Type | Required | Description |
|---|---|---|---|
from_type | string | Yes | draft or published_version |
from_version | integer | Conditional | Required when from_type=published_version |
to_type | string | Yes | draft or published_version |
to_version | integer | Conditional | Required when to_type=published_version |
The diff redacts sensitive values before returning before and after fields.
#GET /v1/projects/{project_id}/runtime-drift
Classifies runtime drift for the active project runtime target.
Authentication: partner_admin, partner_user, aidial_admin, or aidial_operator scope required
| Name | In | Type | Required | Default | Description |
|---|---|---|---|---|---|
fresh | query | boolean | No | false | Request a fresh drift check; only partner_admin persists fresh checks |
#Response
Returns status, expected/observed/pending version numbers where known, summary, recommended_action, checked_at_utc, and optional testing_override_summary.
#GET /v1/projects/{project_id}/versions/{version_number}
Returns full detail for a published project configuration version.
Authentication: partner_admin, partner_user, aidial_admin, or aidial_operator scope required
#Parameters
| Name | In | Type | Required | Default | Description |
|---|---|---|---|---|---|
project_id | path | string | Yes | - | Unique project identifier |
version_number | path | integer | Yes | - | Published version number |
#Response
Returns the published version response shape with id, project_id, version_number, config_data, validation_status, published_at, display-safe published_by, deploy_status, optional deploy error/timestamp, release outcome status, rollback eligibility, release summary, and optional testing override summary.
#Project Secrets
Project secret routes manage write-only integration secrets for a project. Plaintext values are written to the server-side secret store only and are never persisted alongside project metadata or returned by any response. Secret responses expose safe metadata and reference previews only. All secret routes use the same project scoping and anti-enumeration behaviour as the core project routes.
Supported secret categories are provider, plugin, webhook_signing, email_service, sms_service, and identity_verification.
#Secret Metadata
| Field | Type | Description |
|---|---|---|
secret_id | string | Secret identifier |
category | string | Secret category |
label | string | Human-readable secret label |
reference | string | Safe reference used by runtime config to resolve the secret |
reference_type | string | Reference form the stored secret uses, as supported by its category |
target_field | string | Runtime configuration field the secret maps to |
status | string | active, rotation_required, unavailable, or disabled |
created_at | string (ISO 8601) | Creation timestamp |
created_by | string | Display-safe actor label |
last_rotated_at | string (ISO 8601) or null | Last rotation timestamp |
last_rotated_by | string or null | Display-safe rotation actor label |
rotation_required | boolean | Whether rotation is required |
dependent_config_keys | array | Safe config keys that depend on this secret |
revision_token | string | Secret concurrency token |
#GET /v1/projects/{project_id}/secrets
Returns browser-safe project secret metadata for an assigned project.
Authentication: partner_admin or partner_user scope required
#Response
| Field | Type | Description |
|---|---|---|
project_id | string | Project identifier |
can_edit | boolean | true only for partner_admin |
secrets | array | Secret metadata objects |
supported_categories | array | Supported category descriptors |
reference_contracts | array | Per-category reference contracts; each entry includes category, reference_type, reference_format, and rotation_stability |
updated_at | string (ISO 8601) | Snapshot timestamp |
Responses never include plaintext secret values. Out-of-scope projects return 404 Not Found.
#POST /v1/projects/{project_id}/secrets
Stores a plaintext secret in the server-side secret store and returns safe metadata only.
Authentication: partner_admin scope required
#Headers
| Name | Required | Description |
|---|---|---|
Idempotency-Key | Yes | Prevents duplicate secret creation |
#Request Body
| Field | Type | Required | Description |
|---|---|---|---|
category | string | Yes | Secret category |
label | string | Yes | Secret label, 3 to 80 characters |
target_field | string | Yes | Runtime configuration field, 1 to 120 characters |
value | string | Yes | Plaintext secret value, 8 to 8192 characters; written to the secret store only |
#Response
Returns 201 Created with the safe secret metadata shape. The plaintext value is never echoed.
#POST /v1/projects/{project_id}/secrets/{secret_id}/rotate
Rotates a write-only project secret and returns safe metadata only.
Authentication: partner_admin scope required
#Headers
| Name | Required | Description |
|---|---|---|
Idempotency-Key | Yes | Prevents duplicate rotation |
#Request Body
| Field | Type | Required | Description |
|---|---|---|---|
value | string | Yes | New plaintext secret value, 8 to 8192 characters |
revision_token | string | Yes | Current secret revision token |
#Response
Returns the updated safe secret metadata. A stale revision_token returns 409 Conflict. The plaintext value is never echoed.
#POST /v1/projects/{project_id}/secrets/validate
Validates a category and target-field mapping without accepting a plaintext value.
Authentication: partner_admin scope required
#Request Body
| Field | Type | Required | Description |
|---|---|---|---|
category | string | Yes | Secret category |
target_field | string | Yes | Runtime configuration field, 1 to 120 characters |
reference_type | string | Yes | Must equal the reference_type published for the supplied category in reference_contracts from GET /v1/projects/{project_id}/secrets; any other value is rejected with 422 |
#Response
| Field | Type | Description |
|---|---|---|
findings | array | Safe validation findings with severity, code, field, message, and blocking |
reference_preview | string or null | Safe reference preview when the mapping is valid |
Findings are partner-safe and never include plaintext secret values.
#Legacy Billing Endpoints
These legacy API-key project routes expose minimal billing configuration and billing summaries for cost calculation. They use the legacy project and billing response models and the general HTTPException error shape. Responses are scoped to the authenticated client_id; out-of-scope or inactive projects return 404 Not Found.
#GET /v1/projects/{project_id}/billing-config
Returns the minimal billing configuration for a project, suitable for caching in cost calculations.
Authentication: Required (X-API-Key or Authorization: Bearer <jwt>)
#Response
| Field | Type | Description |
|---|---|---|
project_id | string | Project identifier |
rate_per_minute | number | Call rate per minute |
currency | string | Billing currency (ISO 4217) |
billing_model | string | Billing model |
minimum_billable_seconds | integer | Minimum billable duration per call |
Returns 404 Not Found when the project is inactive or outside the client scope.
#GET /v1/projects/{project_id}/billing-summary
Returns aggregated billing totals for a project over a date range.
Authentication: Required (X-API-Key or Authorization: Bearer <jwt>)
#Parameters
| Name | In | Type | Required | Default | Description |
|---|---|---|---|---|---|
project_id | path | string | Yes | - | Unique project identifier |
start_date | query | string (date) | Yes | - | Start date (inclusive, YYYY-MM-DD) |
end_date | query | string (date) | Yes | - | End date (exclusive, YYYY-MM-DD) |
#Response
| Field | Type | Description |
|---|---|---|
project_id | string | Project identifier |
display_name | string or null | Project display name |
rate_per_minute | number | Call rate per minute |
currency | string | Billing currency (ISO 4217) |
total_calls | integer | Total completed calls |
total_duration_seconds | integer | Total call duration in seconds |
total_cost | number | Total cost in project currency |
start_date | string (date) | Summary start date |
end_date | string (date) | Summary end date |
#PUT /v1/projects/{project_id}
Updates legacy project billing configuration. Only provided fields are updated; omitted fields remain unchanged.
Authentication: Required (X-API-Key or Authorization: Bearer <jwt>)
#Request Body
| Field | Type | Required | Description |
|---|---|---|---|
display_name | string | No | New display name |
rate_per_minute | number | No | New billing rate per minute |
currency | string | No | New currency code (ISO 4217) |
billing_model | string | No | New billing model |
minimum_billable_seconds | integer | No | New minimum billable duration |
is_active | boolean | No | New active status |
#Response
Returns the updated legacy project shape. Changing the currency or re-activating a project whose currency does not match the client's active metered billing currency returns 409 Conflict to prevent silent mis-billing.
#Warm transfer privacy mode
Transfer-routing workspace reads and draft saves accept warm_transfer.warm_transfer_mode values leave, stay, stay_silent, and stay_silent_no_transcript. An omitted existing runtime mode still means leave. There is no stay_listen mode.
stay_silent_no_transcript completes the normal screening, briefing and handoff announcements, then keeps AiDial silent without listening to or transcribing the human conversation. Transcript and summary coverage ends at handoff; call duration and recording can continue under the project's recording policy. A retained recording can contain sensitive information even when that information is absent from the transcript.
The mode is preserved in draft and published deployable configuration at transfer.destinations.<destination_id>.warm_transfer_mode. Saving still requires the customer-destination revision token and existing draft ETag; validation/publication retains its current immutable revision and deployment gates. Customer-safe destination snapshots keep their existing field allowlist.
#Error Semantics
| Condition | Status |
|---|---|
| Missing or invalid authentication | 401 Unauthorized |
| Valid authentication but project is outside the resolved client scope | 404 Not Found |
| Valid authentication but role is not allowed for partner lifecycle endpoints | 404 Not Found |
| Invalid partner lifecycle request body or query field | 422 Unprocessable Entity |
Missing Idempotency-Key where required | 400 Bad Request |
Duplicate lifecycle Idempotency-Key for the same operation and payload | Replays the stored response when available; otherwise 409 Conflict while the original request is still processing |
Duplicate controlled-release Idempotency-Key for the same operation and payload | 409 Conflict |
Reused Idempotency-Key with a different payload | 409 Conflict or 422 Unprocessable Entity, depending on the endpoint family |
| Stale draft ETag | 409 Conflict |
Missing required If-Match for existing draft update or publish | 428 Precondition Required |
If-Match: * used against an existing draft | 412 Precondition Failed |
| Missing current validation report before publish or rollback | 422 Unprocessable Entity |
| Controlled release already pending for the project | 409 Conflict |
| Stale lifecycle or secret revision token | 409 Conflict |
| Archived project draft save or publish | 422 Unprocessable Entity |
| Request body larger than 1 MB on routes that read a raw JSON body | 413 Payload Too Large |
| Project currency conflicts with the client's active metered billing currency on legacy update | 409 Conflict |
| Secret write failed during project update | 503 Service Unavailable |
#Data Scoping
All project metadata, lifecycle, draft, version, and legacy billing configuration responses are scoped to the authenticated client identity. You only see projects belonging to your organisation or assigned tenant scope.
Project paths use the API host's local API metadata store. Calls analytics endpoints use strict (client_id, data_env) routing through the tenant routing map; use the Calls Analytics page for routed call data. A missing tenant route is server misconfiguration and returns 500 Internal Server Error; a routed host, tunnel, or database outage returns 503 Service Unavailable.
Project responses do not expose raw encrypted fields, ciphertext, wrapped keys, plaintext secret values, or *_encrypted database fields. Version comparison responses redact secret-like fields before returning diffs.
#Related Pages
- Authentication - API key and bearer authentication
- Calls Analytics - routed call records and analytics
- Error Responses - error format details