#Projects

32 min read

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:

  1. X-API-Key
  2. Authorization: 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 HTTPException error shape.

#Project Detail

Used by partner/admin project lifecycle endpoints.

FieldTypeDescription
project_idstringUnique project identifier
client_idstringClient identifier
display_namestringProject display name
timezonestringProject timezone (IANA format)
lifecycle_statestringactive when is_active=true, otherwise archived
has_draftbooleanWhether a current draft exists
latest_published_version_numberinteger or nullLatest published runtime version number
created_atstring (ISO 8601)Creation timestamp
updated_atstring (ISO 8601)Last update timestamp
created_bystringActor identifier recorded at project creation
initial_draft_etagstring or nullNullable model field; current lifecycle create responses use the lifecycle snapshot shape instead
testing_override_summaryobject or nullTesting override production summary when the router attaches it

#Lifecycle Snapshot

Used by POST /v1/projects, lifecycle/profile routes, archive/restore, and project assignment routes.

FieldTypeDescription
projectobjectLifecycle project metadata
project.project_idstringUnique project identifier
project.client_idstringClient identifier
project.display_namestringProject display name
project.timezonestringProject timezone (IANA format)
project.lifecycle_statestringsetup, active, or archived
project.revision_tokenstringLifecycle concurrency token
project.latest_published_versioninteger or nullLatest published runtime version
project.draft_statusstringdraft when a draft exists, otherwise none
project.publish_statusstringpublished or not_published
project.created_atstring (ISO 8601)Creation timestamp
project.created_bystringActor identifier recorded at project creation
project.updated_atstring (ISO 8601)Last lifecycle update timestamp
project.lifecycle_notesstringLatest lifecycle note, present when one has been recorded
project.updated_by, project.archived_at, project.archived_by, project.restored_at, project.restored_bystring (ISO 8601) or stringTransition audit fields, each present only once the corresponding action has occurred
profileobjectSafe project profile fields
permissionsobjectCurrent caller permissions for lifecycle operations
assignmentsarrayProject visibility assignments
available_membersarray or nullEligible assignment targets when included for partner admins
updated_atstring (ISO 8601)Snapshot update timestamp

#Legacy Project

Used by the legacy project list/detail/update response path.

FieldTypeDescription
project_idstringUnique project identifier
client_idstringClient identifier
display_namestring or nullProject display name
rate_per_minutenumberCall rate per minute
currencystringBilling currency (ISO 4217)
billing_modelstringBilling model, such as per_minute, per_call, or tiered
minimum_billable_secondsintegerMinimum billable duration per call
is_activebooleanWhether the project is active
timezonestringProject timezone (IANA format)
created_at_utcstring (ISO 8601)Creation timestamp
updated_at_utcstring (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

NameInTypeRequiredDefaultDescription
include_inactivequerybooleanNofalseInclude inactive projects. For partner/admin list responses this also includes archived projects
include_archivedquerybooleanNofalseInclude archived projects in partner/admin list responses
data_envquerystringNoAuth context data environmentPartner assignment data environment. Supported values: prod, staging, dev
sort_byquerystringNocreated_at_utcPartner/admin sort field: display_name, created_at_utc, updated_at_utc, or project_id
sort_orderquerystringNodescPartner/admin sort direction: asc or desc
pagequeryintegerNo1Partner/admin page number, minimum 1
page_sizequeryintegerNo20Partner/admin page size, 1 to 100

#Partner/Admin Response

FieldTypeDescription
itemsarrayProject detail objects
paginationobjectPagination metadata
pagination.pageintegerCurrent page
pagination.page_sizeintegerResults per page
pagination.total_recordsintegerTotal matching records
pagination.total_pagesintegerTotal pages

#Example

Request:

Bash
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):

JSON
{
  "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

NameRequiredDescription
Idempotency-KeyConditionalRequired unless idempotency_key is supplied in the request body; 1 to 128 characters

#Request Body

FieldTypeRequiredDescription
display_namestringYesProject display name, 3 to 120 characters
timezonestringYesIANA timezone identifier
managed_client_idstringYesMust match the authenticated client_id
profileobjectNoOptional lifecycle profile fields
idempotency_keystringConditionalAlternative 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:

Bash
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):

JSON
{
  "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

NameInTypeRequiredDefaultDescription
project_idpathstringYes-Unique project identifier

#Response

Returns a project detail object for partner/admin read scopes. Out-of-scope projects return 404 Not Found.

#Example

Request:

Bash
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.

FieldTypeRequiredDescription
display_namestringNoProject display name, 3 to 120 characters
timezonestringNoIANA timezone identifier
lifecycle_notesstringNoLifecycle note, up to 500 characters
profileobjectNoLifecycle profile fields
revision_tokenstringYesCurrent 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

NameInTypeRequiredDefaultDescription
project_idpathstringYes-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

NameInTypeRequiredDefaultDescription
project_idpathstringYes-Unique project identifier

#Headers

NameRequiredDescription
Idempotency-KeyConditionalRequired unless idempotency_key is supplied in the request body

#Request Body

FieldTypeRequiredDescription
revision_tokenstringYesCurrent lifecycle revision token
lifecycle_notestringNoOptional transition note
idempotency_keystringConditionalAlternative 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

NameInTypeRequiredDefaultDescription
project_idpathstringYes-Unique project identifier

#Headers

NameRequiredDescription
Idempotency-KeyConditionalRequired unless idempotency_key is supplied in the request body

#Request Body

FieldTypeRequiredDescription
revision_tokenstringYesCurrent lifecycle revision token
lifecycle_notestringNoOptional transition note
idempotency_keystringConditionalAlternative 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

NameRequiredDescription
Idempotency-KeyConditionalRequired unless idempotency_key is supplied in the request body

#Request Body

FieldTypeRequiredDescription
member_idstringYesEligible partner member identifier
role_visibilitystringYesread or manage
assignment_notestringNoOptional assignment note
revision_tokenstringYesCurrent project lifecycle revision token
idempotency_keystringConditionalAlternative 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

NameRequiredDescription
Idempotency-KeyConditionalRequired unless idempotency_key is supplied in the request body

#Request Body

FieldTypeRequiredDescription
assignment_revision_tokenstringYesCurrent assignment revision token
idempotency_keystringConditionalAlternative 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.

RouteAuthenticationPurpose
GET /dashboard-metadata?refresh=falseClient, partner, or internal read scopesReturn a safe dashboard metadata snapshot
POST /dashboard-metadata/publishBearer client_adminPublish safe API-managed dashboard analytics metadata; requires Idempotency-Key
GET /governanceBearer partner_admin or partner_userReturn governance snapshot
PUT /governance/draftBearer partner_adminSave governance draft; requires Idempotency-Key
POST /governance/validateBearer partner_adminValidate governance draft; requires Idempotency-Key
GET /testing-overridesBearer partner_admin or partner_userReturn testing override contract snapshot
PATCH /testing-overridesBearer partner_adminSave or disable testing overrides; Idempotency-Key must be at least 8 characters
GET /transfer-routingPartner/internal read scopesReturn safe transfer-routing snapshot; partner draft views include saved customer destinations and customer_destinations_version
PUT /transfer-routing/draftpartner_adminSave configured transfer-routing subset; customer_destinations_version required, plus exact If-Match for an existing draft
POST /transfer-routing/validatepartner_adminValidate current transfer-routing config; request body must be empty or {}
POST /transfer-routing/testpartner_adminDry-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-configurationpartner_admin or partner_userReturn provider configuration snapshot
PUT /provider-configurationpartner_adminSave provider configuration draft subset with optional If-Match; runtime-native blocks are read-only here
POST /provider-configuration/validatepartner_admin or partner_userValidate provider configuration; only partner_admin may supply an override body
GET /llm-policypartner_admin or partner_userReturn LLM policy snapshot
PUT /llm-policypartner_adminSave 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/validatepartner_adminValidate LLM policy
GET /plugin-configurationpartner_admin or partner_userReturn plugin configuration snapshot
PUT /plugin-configurationpartner_adminSave plugin configuration draft subset with If-Match or body revision_token when a draft exists
POST /plugin-configuration/validatepartner_adminValidate plugin configuration with the same revision guard
GET /identity-verificationpartner_admin or partner_userReturn identity verification snapshot
PUT /identity-verificationpartner_adminSave runtime-shaped identity verification draft subset with optional If-Match
POST /identity-verification/validatepartner_adminValidate runtime-shaped identity verification
GET /bookingpartner_admin or partner_userReturn booking posture and readiness snapshot
PUT /bookingpartner_adminSave booking draft subset with optional If-Match
POST /booking/validatepartner_adminValidate booking posture and readiness
GET /email-deliverypartner_admin or partner_userReturn email delivery snapshot
PUT /email-deliverypartner_adminSave email delivery draft subset with optional If-Match
POST /email-delivery/validatepartner_adminValidate email delivery
GET /email-sms-policypartner_admin or partner_userReturn combined email & SMS policy snapshot; draft state and edit controls are included only for partner_admin
PUT /email-sms-policypartner_adminSave email & SMS policy draft subset with optional If-Match
POST /email-sms-policy/validatepartner_adminValidate email & SMS policy
GET /outbound-compliancePartner/internal read scopesReturn outbound compliance snapshot
PUT /outbound-complianceBearer partner_admin, aidial_admin, or aidial_operatorSave outbound compliance draft subset; If-Match required
POST /outbound-compliance/validate-recipientPartner/internal read scopesPreview recipient-local calling-window decisions
GET /sms-policypartner_admin or partner_userReturn SMS policy snapshot
PUT /sms-policypartner_adminSave SMS policy draft subset; If-Match must match body revision_token
POST /sms-policy/validatepartner_adminValidate 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

NameInTypeRequiredDefaultDescription
project_idpathstringYes-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.

FieldTypeRequiredDescription
notestringYesDescription of what is needed for the guided test call, 1 to 2000 characters

#Response

FieldTypeDescription
statusstringrequested
project_idstringProject identifier
request_idstringGenerated 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

HeaderRequiredDescription
Idempotency-KeyYesPrevents 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.

FieldTypeRequiredDescription
analytics.outcome_enum[]arrayNoSafe dashboard outcome keys and display labels
analytics.caller_mix_supportedbooleanNoWhether caller-mix metrics may be shown when privacy thresholds are satisfied
analytics.caller_mix.stable_caller_key_availablebooleanNoWhether 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

NameInTypeRequiredDefaultDescription
project_idpathstringYes-Unique project identifier

#Response Headers

HeaderDescription
ETagCurrent draft ETag, quoted

#Response

FieldTypeDescription
idintegerDraft revision identifier
project_idstringProject identifier
revision_typestringAlways draft
config_dataobjectDraft runtime configuration document
etagstringCurrent draft ETag
author_user_idstringActor identifier for the draft author
author_rolestringActor role recorded with the draft
created_atstring (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

NameInTypeRequiredDefaultDescription
project_idpathstringYes-Unique project identifier

#Headers

NameRequiredDescription
If-MatchConditionalRequired 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.

FieldTypeRequiredDescription
config_dataobjectYesDraft 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

StateBehaviour
Draft exists and If-Match is omitted428 Precondition Required
Draft exists and If-Match: * is sent412 Precondition Failed
Draft exists and ETag does not match409 Conflict
No draft exists and If-Match is omitted or *Creates a new draft
No draft exists and a concrete stale ETag is sent409 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

NameInTypeRequiredDefaultDescription
project_idpathstringYes-Unique project identifier

#Headers

NameRequiredDescription
Idempotency-KeyYesRequired idempotency key, 1 to 128 characters
If-MatchYesCurrent draft ETag

#Request Body

FieldTypeRequiredDescription
validation_report_idstringYesCurrent successful validation report for the draft and runtime target
target_runtimestringNoRuntime target; current code supports default

#Publish Validation

The draft config_data must be a non-empty JSON object with:

FieldTypeRequiredDescription
display_namestringYesNon-empty display name
timezonestringYesValid 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:

CodeBlockingDescription
missing_agent_idYesAgent row does not include a stable agent ID
duplicate_agent_idYesAgent ID is repeated in the draft
missing_labelYesAgent label is empty
unsupported_roleYesAgent role is not single_agent, generic_router, or generic_task
missing_handoff_targetYesHandoff target does not exist in the same draft
missing_route_targetYesRoute target does not exist in the same draft
self_routeYesAgent routes or hands off to itself
unresolved_toolYesReferenced tool key is not available in the project catalog
unresolved_mixinYesReferenced mixin key is not available in the project catalog
invalid_voice_overrideYesVoice override key is not available in the project catalog
route_cycle_without_taskYesRoute graph cycles without a terminal generic_task agent
missing_runtime_agentsYesRuntime agent configuration is missing or empty
missing_runtime_entry_agentYesRuntime agent configuration does not include an entry agent
missing_runtime_agent_typeYesRuntime agent does not declare agent_type
missing_runtime_routesYesRuntime router agent does not compile to any route
missing_runtime_handoff_targetYesRuntime handoff target does not target an existing agent
missing_runtime_route_targetYesRuntime route does not target an existing agent
missing_runtime_route_whenYesRuntime route is missing the required when condition
unresolved_runtime_ttsYesPortal voice override does not resolve to a full runtime tts object
invalid_runtime_handoff_targetsYesRuntime handoff targets are not a list
runtime_task_has_routesYesRuntime task agent defines routes instead of being terminal
runtime_route_cycle_without_taskYesRuntime route graph cycles without a terminal task agent
empty_welcome_greetingNoWelcome 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

FieldTypeDescription
publish_outcome_idstringControlled release outcome identifier
parent_publish_outcome_idstring or nullParent outcome for deploy completion evidence
statusstringdeploy_pending on successful publish before deployment evidence is recorded
operationstringpublish
project_idstringProject identifier
target_revision_idintegerPublished revision identifier
target_version_numberinteger or nullPublished version number
restored_from_revision_idinteger or nullSource revision identifier when the outcome was created by rollback, otherwise null
restored_from_version_numberinteger or nullSource published version number when the outcome was created by rollback, otherwise null
latest_completed_published_version_numberinteger or nullLatest completed runtime version
runtime_targetstringRuntime target, currently default
validation_report_idstring or nullValidation report used for release
runtime_artifact_refstring or nullSafe runtime artifact reference
failure_codestring or nullFailure code when applicable
failure_messagestring or nullFailure message when applicable
summarystringHuman-readable release outcome summary
created_at_utcstring (ISO 8601)Outcome creation timestamp
testing_override_summaryobject or nullTesting 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

FieldTypeRequiredDescription
target.typestringYesdraft or published_version
target.revision_tokenstringConditionalRequired when target.type=draft
target.version_numberintegerConditionalRequired when target.type=published_version
target_runtimestringNoRuntime 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

NameRequiredDescription
Idempotency-KeyYesRequired idempotency key, 1 to 128 characters

#Request Body

FieldTypeRequiredDescription
target_version_numberintegerYesCompleted published version to roll back to
validation_report_idstringYesCurrent validation report for the rollback target
target_runtimestringNoRuntime 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

NameInTypeRequiredDefaultDescription
project_idpathstringYes-Unique project identifier
pagequeryintegerNo1Page number, minimum 1
page_sizequeryintegerNo20Page size, 1 to 100

#Response

FieldTypeDescription
itemsarrayVersion list items
items[].version_numberintegerPublished version number
items[].published_atstring (ISO 8601)Publish timestamp
items[].published_bystringDisplay-safe actor label
items[].validation_statusstringValidation status
items[].deploy_statusstringDeployment status
items[].latest_publish_outcome_statusstring or nullLatest controlled release outcome status
items[].rollback_eligiblebooleanWhether rollback is currently allowed
items[].rollback_block_reasonstring or nullWhy rollback is blocked
items[].testing_override_summaryobject or nullTesting override production summary when present
paginationobjectPagination 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

NameTypeRequiredDescription
from_typestringYesdraft or published_version
from_versionintegerConditionalRequired when from_type=published_version
to_typestringYesdraft or published_version
to_versionintegerConditionalRequired 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

NameInTypeRequiredDefaultDescription
freshquerybooleanNofalseRequest 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

NameInTypeRequiredDefaultDescription
project_idpathstringYes-Unique project identifier
version_numberpathintegerYes-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

FieldTypeDescription
secret_idstringSecret identifier
categorystringSecret category
labelstringHuman-readable secret label
referencestringSafe reference used by runtime config to resolve the secret
reference_typestringReference form the stored secret uses, as supported by its category
target_fieldstringRuntime configuration field the secret maps to
statusstringactive, rotation_required, unavailable, or disabled
created_atstring (ISO 8601)Creation timestamp
created_bystringDisplay-safe actor label
last_rotated_atstring (ISO 8601) or nullLast rotation timestamp
last_rotated_bystring or nullDisplay-safe rotation actor label
rotation_requiredbooleanWhether rotation is required
dependent_config_keysarraySafe config keys that depend on this secret
revision_tokenstringSecret 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

FieldTypeDescription
project_idstringProject identifier
can_editbooleantrue only for partner_admin
secretsarraySecret metadata objects
supported_categoriesarraySupported category descriptors
reference_contractsarrayPer-category reference contracts; each entry includes category, reference_type, reference_format, and rotation_stability
updated_atstring (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

NameRequiredDescription
Idempotency-KeyYesPrevents duplicate secret creation

#Request Body

FieldTypeRequiredDescription
categorystringYesSecret category
labelstringYesSecret label, 3 to 80 characters
target_fieldstringYesRuntime configuration field, 1 to 120 characters
valuestringYesPlaintext 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

NameRequiredDescription
Idempotency-KeyYesPrevents duplicate rotation

#Request Body

FieldTypeRequiredDescription
valuestringYesNew plaintext secret value, 8 to 8192 characters
revision_tokenstringYesCurrent 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

FieldTypeRequiredDescription
categorystringYesSecret category
target_fieldstringYesRuntime configuration field, 1 to 120 characters
reference_typestringYesMust 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

FieldTypeDescription
findingsarraySafe validation findings with severity, code, field, message, and blocking
reference_previewstring or nullSafe 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

FieldTypeDescription
project_idstringProject identifier
rate_per_minutenumberCall rate per minute
currencystringBilling currency (ISO 4217)
billing_modelstringBilling model
minimum_billable_secondsintegerMinimum 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

NameInTypeRequiredDefaultDescription
project_idpathstringYes-Unique project identifier
start_datequerystring (date)Yes-Start date (inclusive, YYYY-MM-DD)
end_datequerystring (date)Yes-End date (exclusive, YYYY-MM-DD)

#Response

FieldTypeDescription
project_idstringProject identifier
display_namestring or nullProject display name
rate_per_minutenumberCall rate per minute
currencystringBilling currency (ISO 4217)
total_callsintegerTotal completed calls
total_duration_secondsintegerTotal call duration in seconds
total_costnumberTotal cost in project currency
start_datestring (date)Summary start date
end_datestring (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

FieldTypeRequiredDescription
display_namestringNoNew display name
rate_per_minutenumberNoNew billing rate per minute
currencystringNoNew currency code (ISO 4217)
billing_modelstringNoNew billing model
minimum_billable_secondsintegerNoNew minimum billable duration
is_activebooleanNoNew 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

ConditionStatus
Missing or invalid authentication401 Unauthorized
Valid authentication but project is outside the resolved client scope404 Not Found
Valid authentication but role is not allowed for partner lifecycle endpoints404 Not Found
Invalid partner lifecycle request body or query field422 Unprocessable Entity
Missing Idempotency-Key where required400 Bad Request
Duplicate lifecycle Idempotency-Key for the same operation and payloadReplays 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 payload409 Conflict
Reused Idempotency-Key with a different payload409 Conflict or 422 Unprocessable Entity, depending on the endpoint family
Stale draft ETag409 Conflict
Missing required If-Match for existing draft update or publish428 Precondition Required
If-Match: * used against an existing draft412 Precondition Failed
Missing current validation report before publish or rollback422 Unprocessable Entity
Controlled release already pending for the project409 Conflict
Stale lifecycle or secret revision token409 Conflict
Archived project draft save or publish422 Unprocessable Entity
Request body larger than 1 MB on routes that read a raw JSON body413 Payload Too Large
Project currency conflicts with the client's active metered billing currency on legacy update409 Conflict
Secret write failed during project update503 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.