{"openapi":"3.1.0","info":{"title":"UiriX Public API","version":"1.0.0-draft","summary":"Conversation data, agent control, outbound calls and webhooks for UiriX accounts.","description":"**Contract v1.0.** Every operation in this document is implemented and live.\n\nServer-to-server API. Every request carries `Authorization: Bearer <key>`; every response carries `X-Request-Id`.\nAll timestamps are ISO-8601 UTC (`Z`). All ids are opaque prefixed strings. Every documented key is always present\n(`null` when unknown). One error envelope for every non-2xx response. The scope each operation requires is listed under `x-uirix-scope`.\n\n# Start here — how an agent works, and the order things happen in\n\nAn **agent** is a phone / chat / voice-chat assistant that belongs to one **account**. It carries a **knowledge summary**\n(the only knowledge it reads at runtime, built from the documents and pages you give it), three **instruction prompts**\n(inbound phone, chat, voice chat), **greetings**, a **voice**, a set of **capabilities** (the tools it may call) and the\nowner's **refinement rules** (\"Improve agent\"). Knowledge and instructions are **built**: you change inputs, then rebuild;\nthe built outputs are what the agent runs. Conversations, calls, webhooks and metrics sit on top of that.\n\n| Step | What happens | Endpoints |\n|---|---|---|\n| 1. Account | A tenant with a balance and quotas; keys are issued to it. | `GET /accounts`, `GET /accounts/{acc}/limits`, `POST /users/{usr}/credentials` (master key) |\n| 2. Create the agent | The only creation path is the V4 build: give a website URL and a voice; the platform crawls, summarises, writes the greeting and the three prompts. Poll the steps. | `POST /accounts/{acc}/agents/build`, `GET /agents/{agt}/build`, `POST /agents/{agt}/build` |\n| 3. Knowledge base | Upload files, post text, crawl a page or a site. Documents go `processing` → `indexed`. | `POST /agents/{agt}/kb/documents`, `GET /agents/{agt}/kb/documents` |\n| 4. \"Keep as is\" documents | A guide whose exact wording matters (buttons, steps, prices, FAQ answers) skips the summariser; its full text follows the summary in the prompt on every surface. Flag on create, with PATCH, or by opening the text with `IMPORTANT INSTRUCTION FOR AI ASSISTANT`. Chat carries up to 1,000,000 characters of such documents (2,000,000 on the large tier), calls 270,000 — `verbatim_in_calls` goes first. | `PATCH /agents/{agt}/kb/documents/{doc}`, sizes in `GET /agents/{agt}/kb/status` → `verbatim` |\n| 5. Rebuild the summary | New or changed documents are pending until the summary is rebuilt. Watch `summary.status` / `summary.last_job`. | `POST /agents/{agt}/kb/reindex`, `GET /agents/{agt}/kb/status` |\n| 6. Read or rewrite the summary | Text you can read and, if you must, replace (the next reindex overwrites it). | `GET/PATCH /agents/{agt}/kb/summary` |\n| 7. Instructions | Each surface runs its own prompt. A knowledge change needs a full rebuild; a rules / role / greeting change needs an instructions rebuild. Every build makes a prompt version you can audit, deploy, roll back. | `GET/PATCH /agents/{agt}`, `GET /agents/{agt}/prompts`, `POST /agents/{agt}/prompts`, `POST /agents/{agt}/prompts/{pv}/restore` |\n| 8. Improve the agent | Plain-language rules, normalised and conflict-checked, injected into every prompt. Keep them few. | `PATCH /agents/{agt}` → refinement rules |\n| 9. Capabilities | The tools the agent may call and the values each needs (transfer directory, website URL, where leads / tickets / orders are e-mailed). | `GET /capabilities`, `PATCH /agents/{agt}` |\n| 10. Voice and greetings | Voice per provider, speaking rate, greeting per surface and language. | `GET /voices`, `GET/PATCH /agents/{agt}/voice`, `PATCH /agents/{agt}` |\n| 11. Train / test | Talk to the agent over HTTPS; tools fire for real. | `POST /conversations`, `POST /conversations/{conv}/messages`, `POST /conversations/{conv}/end` |\n| 12. Run | Widget, inbound number, outbound campaigns with profiles. | `GET/PATCH /agents/{agt}/widget`, `/profiles`, `/calls` |\n| 13. Watch | Conversations as structured turns + summary + identity; webhooks; metrics. | `GET /conversations`, `POST /webhooks/endpoints`, `GET /metrics/…` |\n\n**What the agent knows at runtime:** the knowledge summary plus the \"keep as is\" documents — nothing else, on every surface;\nthere is no retrieval over raw documents during a conversation. Calls have less room than chat (the realtime models share\na 128k-token window with the audio), so documents beyond the calls cap are summarised instead (`kb/status.verbatim.calls.dropped`).\n\n**What needs a rebuild:** documents / pages / crawl → knowledge rebuild, then an instructions rebuild; rules / role / greeting /\ncapabilities → instructions rebuild; \"keep as is\" flags, voice, speaking rate, widget settings, notification e-mails → nothing.\n\n**Download this documentation:** [zip of every page + openapi.yaml](./docs/download) · [all pages in one markdown file](./docs/all.md) ·\n[page index](./docs/pages) · [Start here as markdown](./docs/pages/START_HERE.md) · [openapi.yaml](./openapi.yaml). No credential needed.\n\nHuman documentation in the repository: `DOCS/api/README.md` (start with `DOCS/api/START_HERE.md`).\n","contact":{"name":"UiriX API support","url":"https://dashboard.uirix.com"},"license":{"name":"Proprietary","url":"https://uirix.com"}},"servers":[{"url":"https://api.uirix.com/v1","description":"Production"},{"url":"https://dashboard.uirix.com/api/ext/v1","description":"Production (fallback path)"},{"url":"https://dev-dashboard.uirix.com/api/ext/v1","description":"Development"}],"security":[{"bearerAuth":[]}],"tags":[{"name":"Platform","description":"Health, OpenAPI and docs. No credential required."},{"name":"Users","description":"Tenant provisioning (master key)."},{"name":"Credentials","description":"API key issuance, rotation and revocation."},{"name":"Audit","description":"Audit log export (master key)."},{"name":"Accounts","description":"Account profile, limits, usage and balance."},{"name":"Agents","description":"Agent CRUD, prompt versions and deploy markers."},{"name":"Knowledge Base","description":"KB documents, indexing status and reindex."},{"name":"Widget","description":"Widget settings and public token."},{"name":"Voice","description":"Voice configuration."},{"name":"Conversations","description":"Unified conversation read model, summaries, recordings, deletion."},{"name":"Chat","description":"Chat via API — create, message, identify, end."},{"name":"Metrics","description":"Aggregate metrics."},{"name":"Calls","description":"Outbound calls."},{"name":"DNC","description":"Do-not-call list."},{"name":"Campaigns","description":"Batch outbound — contact lists and campaigns (DOCS/api/campaigns.md)."},{"name":"Webhooks","description":"Webhook endpoints, deliveries and replay."},{"name":"Admin","description":"Master-key management surface — platform KPIs, customer dossiers, system status and journaled operator writes (DOCS/api/admin.md)."},{"name":"Media","description":"Signed recording URLs."}],"paths":{"/health":{"get":{"tags":["Platform"],"operationId":"getHealth","summary":"Service health","description":"Unauthenticated, unmetered, no side effects. 200 for `ok`/`degraded`, 503 for `down`.","security":[],"responses":{"200":{"description":"Healthy or degraded","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Health"},"example":{"status":"ok","version":"1.0.0","time":"2026-09-03T14:32:07Z","components":{"api":"ok","db":"ok","transcripts":"ok","summaries":"ok","webhooks":"ok","media":"ok"}}}}},"503":{"description":"Database unreachable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Health"}}}}}}},"/openapi.json":{"get":{"tags":["Platform"],"operationId":"getOpenApi","summary":"This document as JSON","security":[],"responses":{"200":{"description":"OpenAPI 3.1 document","content":{"application/json":{"schema":{"type":"object"}}}}}}},"/docs":{"get":{"tags":["Platform"],"operationId":"getDocs","summary":"Interactive reference (HTML)","security":[],"responses":{"200":{"description":"HTML page","content":{"text/html":{"schema":{"type":"string"}}}}}}},"/users":{"post":{"tags":["Users"],"operationId":"createUser","summary":"Provision a tenant (master key)","description":"Creates the `users` row, the matching `subscriptions` row and the\n`ext_account_settings` row in one transaction. `plan: trial` uses the\nconfigured trial plan and sets `plan_expires_at` 30 days out;\n`plan: starter` uses the paid entry plan.\n\nA password is optional: when it is omitted a random 32-byte secret is\nhashed with bcrypt(10) and **never returned** — the tenant reaches the\naccount through the password-reset flow.\n","x-uirix-scope":"users:manage","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserCreate"},"example":{"email":"ops@example-shop.de","name":"Example Shop","plan":"starter","company_name":"Example Shop GmbH","language":"de","timezone":"Europe/Berlin","currency_code":"USD","external_ids":{"hubspot_company_id":"5256925284"}}}}},"responses":{"201":{"description":"Created","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/User"}}}},"400":{"description":"`invalid_email` (field `email`) · `validation_error` (unknown plan, bad\n`currency_code`) · `unsupported_field` (unknown body key)\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"description":"`email_exists` — field `email`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"$ref":"#/components/responses/TooManyRequests"}}},"get":{"tags":["Users"],"operationId":"listUsers","summary":"List tenants (master key)","description":"Keyset cursor over `(created_at, id)`, newest first.","x-uirix-scope":"users:manage","parameters":[{"name":"status","in":"query","schema":{"$ref":"#/components/schemas/AccountStatus"}},{"name":"updated_since","in":"query","schema":{"type":"string","format":"date-time"}},{"$ref":"#/components/parameters/limit"},{"$ref":"#/components/parameters/cursor"}],"responses":{"200":{"description":"List of users","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ListEnvelope"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/User"}}}}]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}}}},"/users/{usr}":{"parameters":[{"$ref":"#/components/parameters/usr"}],"get":{"tags":["Users"],"operationId":"getUser","summary":"Get a tenant (master key)","x-uirix-scope":"users:manage","responses":{"200":{"description":"User","content":{"application/json":{"schema":{"$ref":"#/components/schemas/User"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}},"patch":{"tags":["Users"],"operationId":"patchUser","summary":"Update status, external ids, limits, retention (master key)","description":"`external_ids` is merged key-by-key; a `null` value clears that key.\n\nIMPLEMENTATION: `status` accepts `active` and `suspended`. `trial` is\nderived from the account's plan and is rejected with\n`400 validation_error` (field `status`) rather than silently ignored.\n","x-uirix-scope":"users:manage","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserPatch"},"example":{"external_ids":{"hubspot_company_id":"5256925284","stale_id":null},"limits":{"max_calls_per_day":50,"max_agent_creates_per_day":5,"max_builds_per_day":20,"max_concurrent_builds":1}}}}},"responses":{"200":{"description":"Updated user","content":{"application/json":{"schema":{"$ref":"#/components/schemas/User"}}}},"400":{"description":"`unsupported_field` · `validation_error` · `empty_update`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/users/{usr}/credentials":{"parameters":[{"$ref":"#/components/parameters/usr"}],"post":{"tags":["Credentials"],"operationId":"createCredential","summary":"Issue an API key (secret shown once)","description":"Master key for any user; a personal key holding `credentials:manage` for\nits OWN user only — anything else is `403 forbidden_account`.\n\nIMPLEMENTATION: `account_ids` beyond the user's own account require the\nmaster key (`403 forbidden_account`).\n","x-uirix-scope":"users:manage | credentials:manage","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiKeyCreate"},"example":{"name":"warehouse-sync","scopes":["accounts:read","agents:read","conversations:read","summaries:read"],"account_ids":["acc_17"],"environment":"live","expires_in_days":365,"ip_allowlist":["203.0.113.9","203.0.113.0/28"]}}}},"responses":{"201":{"description":"Credential with secret","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiKeyWithSecret"}}}},"400":{"description":"`invalid_scope` (field `scopes`) · `validation_error` (`expires_in_days` > 365, bad `ip_allowlist`)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}},"get":{"tags":["Credentials"],"operationId":"listUserCredentials","summary":"List a user's API keys (no secrets)","x-uirix-scope":"users:manage | credentials:manage","parameters":[{"$ref":"#/components/parameters/limit"},{"$ref":"#/components/parameters/cursor"}],"responses":{"200":{"description":"Keys","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ListEnvelope"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/ApiKey"}}}}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/credentials":{"get":{"tags":["Credentials"],"operationId":"listCredentials","summary":"List the caller's API keys (every key with the master key)","x-uirix-scope":"credentials:manage","parameters":[{"name":"user_id","in":"query","description":"`usr_17` / `acc_17`. Master key: filters the platform-wide listing.\nPersonal key: only its own user is accepted, anything else is\n`403 forbidden_account`.\n","schema":{"type":"string"}},{"$ref":"#/components/parameters/limit"},{"$ref":"#/components/parameters/cursor"}],"responses":{"200":{"description":"Keys","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ListEnvelope"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/ApiKey"}}}}]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}}}},"/credentials/{key}/rotate":{"parameters":[{"$ref":"#/components/parameters/key"}],"post":{"tags":["Credentials"],"operationId":"rotateCredential","summary":"Mint a successor key (the predecessor lives 7 more days)","description":"The successor inherits scopes, `account_ids` and `ip_allowlist`. The\npredecessor's `expires_at` is pulled forward to `grace_expires_at`\n(`now + 7d`, or its own earlier expiry) and a sweep revokes it when the\ngrace period ends. Both keys authenticate until then.\n","x-uirix-scope":"credentials:manage","responses":{"201":{"description":"New credential with secret","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ApiKeyWithSecret"},{"type":"object","required":["grace_expires_at"],"properties":{"grace_expires_at":{"type":"string","format":"date-time","description":"When the predecessor stops working."}}}]}}}},"400":{"description":"`validation_error` — the credential is already revoked","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/credentials/{key}":{"parameters":[{"$ref":"#/components/parameters/key"}],"delete":{"tags":["Credentials"],"operationId":"revokeCredential","summary":"Revoke a key immediately","description":"The next request with that key is `401 invalid_credentials`.","x-uirix-scope":"credentials:manage","responses":{"204":{"description":"Revoked"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/audit":{"get":{"tags":["Audit"],"operationId":"exportAudit","summary":"Export the audit log (master key)","description":"One row per metered request — 2xx and 4xx/5xx alike — retained 12 months.\n`/health`, `/docs` and `/openapi.json` are not audited, and the stored\n`path` never contains the query string. Master-key rows always carry\n`cross_account: true`.\n\n`format=csv` streams a file (up to 50 000 rows, no cursor — narrow the\nwindow instead); `format=json` returns the list envelope with a cursor\nover `(created_at, id)`.\n","x-uirix-scope":"audit:read","parameters":[{"name":"from","in":"query","description":"Default: 24 h before `to`.","schema":{"type":"string","format":"date-time"}},{"name":"to","in":"query","description":"Default: now.","schema":{"type":"string","format":"date-time"}},{"name":"credential_id","in":"query","description":"`key_42`, or the literal `master`.","schema":{"type":"string"}},{"name":"account_id","in":"query","description":"Rows that touched this account.","schema":{"type":"string"}},{"name":"status_code","in":"query","schema":{"type":"integer","minimum":100,"maximum":599}},{"name":"format","in":"query","schema":{"type":"string","enum":["json","csv"],"default":"json"}},{"$ref":"#/components/parameters/limit"},{"$ref":"#/components/parameters/cursor"}],"responses":{"200":{"description":"Audit rows","headers":{"Content-Disposition":{"description":"Present for `format=csv`: `attachment; filename=\"uirix-audit-YYYY-MM-DD.csv\"`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ListEnvelope"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AuditRow"}},"meta":{"allOf":[{"$ref":"#/components/schemas/Meta"},{"type":"object","properties":{"from":{"type":"string","format":"date-time"},"to":{"type":"string","format":"date-time"}}}]}}}]},"example":{"data":[{"request_id":"req_01J8Z9K3AA","credential_type":"key","credential_id":"key_42","account_ids":["acc_17"],"cross_account":false,"method":"GET","path":"/api/ext/v1/conversations","status_code":200,"error_code":null,"ip":"203.0.113.9","user_agent":"equalweb-sync/1.0","duration_ms":184,"created_at":"2026-09-02T09:14:02.000Z"}],"pagination":{"limit":50,"has_more":false,"next_cursor":null},"meta":{"request_id":"req_01J8ZB6K7L","environment":"production","from":"2026-09-02T00:00:00.000Z","to":"2026-09-03T00:00:00.000Z"}}},"text/csv":{"schema":{"type":"string"},"example":"created_at,request_id,credential_type,credential_id,account_ids,cross_account,method,path,status_code,error_code,ip,user_agent,duration_ms\n2026-09-02T09:14:02.000Z,req_01J8Z9K3AA,key,key_42,acc_17,false,GET,/api/ext/v1/conversations,200,,203.0.113.9,equalweb-sync/1.0,184\n"}}},"400":{"description":"`invalid_date` · `invalid_date_range` · `invalid_id` (`credential_id`) · `validation_error` (`status_code`)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}}}},"/accounts":{"get":{"tags":["Accounts"],"operationId":"listAccounts","summary":"List accounts on the credential","description":"A personal key sees the accounts on the key. The master key lists **every**\naccount (`admin:all_accounts`) — this is the one account route that does\nnot require `X-Uirix-Account`; the audit row is still flagged\n`cross_account: true`.\n","x-uirix-scope":"accounts:read","parameters":[{"name":"status","in":"query","schema":{"$ref":"#/components/schemas/AccountStatus"}},{"name":"updated_since","in":"query","schema":{"type":"string","format":"date-time"}},{"name":"account_id","in":"query","description":"Repeatable. Any account not on the credential is `403 forbidden_account`.","schema":{"oneOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}]}},{"$ref":"#/components/parameters/limit"},{"$ref":"#/components/parameters/cursor"}],"responses":{"200":{"description":"Accounts","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ListEnvelope"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Account"}}}}]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}}}},"/accounts/{acc}":{"parameters":[{"$ref":"#/components/parameters/acc"}],"get":{"tags":["Accounts"],"operationId":"getAccount","summary":"Get an account","x-uirix-scope":"accounts:read","responses":{"200":{"description":"Account","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Account"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}},"patch":{"tags":["Accounts"],"operationId":"patchAccount","summary":"Update external ids, redaction default, retention","description":"`external_ids` is merged key-by-key; a `null` value clears that key.","x-uirix-scope":"accounts:write","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccountPatch"},"example":{"external_ids":{"equalweb_customer_id":"55231"},"default_redaction":false,"transcript_retention_days":365}}}},"responses":{"200":{"description":"Updated account","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Account"}}}},"400":{"description":"`unsupported_field` · `validation_error` · `empty_update`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/accounts/{acc}/limits":{"parameters":[{"$ref":"#/components/parameters/acc"}],"get":{"tags":["Accounts"],"operationId":"getAccountLimits","summary":"Call quotas and rate limits","description":"`calls_today` counts calls created since 00:00 UTC and\n`calls_in_progress` those still running.\n\nIMPLEMENTATION: until per-call accounting exists, both\ncounters are derived from the outbound `usage_logs` session types.\n","x-uirix-scope":"accounts:read","responses":{"200":{"description":"Limits","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccountLimits"},"example":{"account_id":"acc_17","max_concurrent_calls":2,"max_calls_per_day":200,"calls_today":14,"calls_in_progress":1,"quotas":{"day":"2026-09-06","resets_at":"2026-09-07T00:00:00.000Z","agent_creates":{"limit":10,"used":1},"builds":{"limit":30,"used":3},"kb_documents":{"limit":100,"used":0},"chat_messages":{"limit":2000,"used":412},"webhook_tests":{"limit":100,"used":2},"max_concurrent_builds":2,"builds_in_progress":1},"rate_limits":{"read":60,"metrics":20,"audio":30,"backfill":10,"write":60,"calls":20,"build":5,"chat":30},"meta":{"request_id":"req_01J8ZB3E4F","environment":"production"}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/accounts/{acc}/usage":{"parameters":[{"$ref":"#/components/parameters/acc"}],"get":{"tags":["Accounts"],"operationId":"getAccountUsage","summary":"Usage and cost per month, day, agent, agent type or channel","description":"Aggregates the billing stores (`usage_logs` + `public_sessions`) over\n`[from, to)`. `cost_usd` is the billed amount after the account's session\nmultipliers and `tokens` are billed tokens.\n\n`conversations` here counts **billed sessions** and can exceed the\n`GET /conversations` count for the same window, which de-duplicates\ndashboard chats that are billed through a mirror row.\n\n`voice_seconds` uses the Twilio call duration when present; a duration\nderived from two timestamps is ignored above 4 hours (a stale\n`updated_at` on an abandoned widget session is not voice time).\n","x-uirix-scope":"usage:read","parameters":[{"name":"from","in":"query","required":true,"schema":{"type":"string","format":"date-time"}},{"name":"to","in":"query","required":true,"schema":{"type":"string","format":"date-time"}},{"name":"group_by","in":"query","description":"One to three of `agent`, `agent_type`, `channel`, `date` (UTC day), `month` (UTC `YYYY-MM`), comma-separated, each once — e.g. `month,agent,channel`. Each row carries one key per dimension.","schema":{"type":"string","default":"agent","example":"month,agent,channel"}}],"responses":{"200":{"description":"Usage report","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UsageReport"},"example":{"data":[{"agent_id":"agt_245","conversations":312,"voice_seconds":18420,"tokens":1842000,"cost_usd":41.27},{"agent_id":"agt_246","conversations":40,"voice_seconds":0,"tokens":160200,"cost_usd":2.11}],"totals":{"conversations":352,"voice_seconds":18420,"tokens":2002200,"cost_usd":43.38},"meta":{"request_id":"req_01J8ZB4G5H","environment":"production","from":"2026-08-27T00:00:00.000Z","to":"2026-09-03T00:00:00.000Z","group_by":"agent","currency_code":"USD"}}}}},"400":{"description":"`missing_param` (`from`/`to`) · `invalid_date` · `invalid_date_range`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/accounts/{acc}/balance":{"parameters":[{"$ref":"#/components/parameters/acc"}],"get":{"tags":["Accounts"],"operationId":"getAccountBalance","summary":"Wallet balance and subscription status","description":"`plan_limit` is the plan price — spending above it is charged to the\nwallet. `status` is the subscription status that gates billable actions;\nwhen it is not `active`, `POST /conversations`, `POST …/messages` and\n`POST /calls` answer `402 payment_required`.\n","x-uirix-scope":"usage:read","responses":{"200":{"description":"Balance","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Balance"},"example":{"account_id":"acc_17","wallet_balance":182.4,"currency_code":"USD","monthly_spending":43.38,"plan_limit":49,"status":"active","meta":{"request_id":"req_01J8ZB5H6J","environment":"production"}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/accounts/{acc}/agents":{"parameters":[{"$ref":"#/components/parameters/acc"}],"get":{"tags":["Agents"],"operationId":"listAgents","summary":"List the account's agents","description":"Summary objects (§3.6) enriched with `agent_version`, `prompt_version`,\nthe `model` actually used for chat (resolved from `use_large_model`\nthrough `aiConfigService`, the same call the chat engine makes) and\n`knowledge_bases[]` with file/page counts and summary freshness.\n\nIMPLEMENTATION: not a `ListEnvelope` — the published contract answers\n`{ account_id, agents[], meta }`, and an account holds tens of agents,\nso there is no cursor.\n","x-uirix-scope":"agents:read","responses":{"200":{"description":"Agents","content":{"application/json":{"schema":{"type":"object","required":["account_id","agents","meta"],"properties":{"account_id":{"type":"string","examples":["acc_17"]},"agents":{"type":"array","items":{"$ref":"#/components/schemas/AgentSummary"}},"meta":{"$ref":"#/components/schemas/Meta"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}}}},"/accounts/{acc}/agents/{agt}":{"parameters":[{"$ref":"#/components/parameters/acc"},{"$ref":"#/components/parameters/agt"}],"get":{"tags":["Agents"],"operationId":"getAgentNested","summary":"Alias of GET /agents/{agt}","x-uirix-scope":"agents:read","responses":{"200":{"description":"Agent","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Agent"}}}},"404":{"$ref":"#/components/responses/NotFound"}}},"patch":{"tags":["Agents"],"operationId":"patchAgentNested","summary":"Alias of PATCH /agents/{agt}","x-uirix-scope":"agents:write","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentPatch"}}}},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentWriteResult"}}}},"404":{"$ref":"#/components/responses/NotFound"}}}},"/accounts/{acc}/agents/{agt}/prompt":{"parameters":[{"$ref":"#/components/parameters/acc"},{"$ref":"#/components/parameters/agt"}],"post":{"tags":["Prompts"],"operationId":"deployPromptVersionNested","summary":"Alias of POST /agents/{agt}/prompts (§11.2, singular)","x-uirix-scope":"agents:write","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PromptDeploy"}}}},"responses":{"201":{"description":"Deployed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PromptDeployResult"}}}},"409":{"description":"`version_label_exists`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/accounts/{acc}/agents/{agt}/kb/documents":{"parameters":[{"$ref":"#/components/parameters/acc"},{"$ref":"#/components/parameters/agt"}],"get":{"tags":["KnowledgeBase"],"operationId":"listKbDocumentsNested","summary":"Alias of GET /agents/{agt}/kb/documents","x-uirix-scope":"kb:read","responses":{"200":{"description":"Documents","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListEnvelope"}}}}}},"post":{"tags":["KnowledgeBase"],"operationId":"createKbDocumentNested","summary":"Alias of POST /agents/{agt}/kb/documents (§11.2)","x-uirix-scope":"kb:write","responses":{"202":{"description":"Accepted for processing"}}}},"/agents/{agt}":{"parameters":[{"$ref":"#/components/parameters/agt"}],"get":{"tags":["Agents"],"operationId":"getAgent","summary":"Full agent configuration","description":"Every configurable field plus nested `voice` and `widget` blocks. The\nwidget block reports `has_public_token` but NEVER the token itself —\nuse `GET /agents/{agt}/widget`.\n","x-uirix-scope":"agents:read","responses":{"200":{"description":"Agent","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Agent"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"`agent_not_found` — also for an agent in another account","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}},"patch":{"tags":["Agents"],"operationId":"patchAgent","summary":"Partial update (43-field whitelist)","description":"Exactly the 43 fields in `AgentPatch` are accepted; any other key is\n`400 unsupported_field` naming it, and `{}` is `400 empty_update`.\n\n19 of them are behavioural and mint a new `agent_version` — prompts (3),\nbuild outputs and refinement rules (6), model (4), capabilities (2) and\nvoice (4). One request bumps ONCE, however many of them it changes.\nA prompt field also deploys a prompt version.\n\nThe UiriX dashboard writes agents through the same service, so an edit\nmade in the UI is versioned identically (`created_by: dashboard:usr_17`).\n","x-uirix-scope":"agents:write","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentPatch"},"example":{"instructions":"You are the EqualWeb assistant. Hand off to a human when asked.","temperature":0.3}}}},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentWriteResult"}}}},"400":{"description":"`unsupported_field` · `validation_error` · `empty_update`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/agents/{agt}/prompts":{"parameters":[{"$ref":"#/components/parameters/agt"}],"get":{"tags":["Prompts"],"operationId":"listPromptVersions","summary":"Prompt version history","description":"Newest first. `body` is omitted here — read one version to get it.\n\nIMPLEMENTATION: no cursor. A `ListEnvelope` is still returned (with\n`has_more: false`, `next_cursor: null`) because an agent has at most a\nfew dozen versions per slot.\n","x-uirix-scope":"agents:read","parameters":[{"name":"type","in":"query","schema":{"$ref":"#/components/schemas/PromptType"}}],"responses":{"200":{"description":"Prompt versions","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ListEnvelope"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/PromptVersion"}}}}]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}},"post":{"tags":["Prompts"],"operationId":"deployPromptVersion","summary":"Deploy a prompt version","description":"One transaction: writes the agent column, retires the active version,\ninserts the new immutable row, sets `current_prompt_version` and bumps\n`agent_version`.\n","x-uirix-scope":"agents:write","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PromptDeploy"},"example":{"type":"chat","body":"You are the EqualWeb website assistant…","label":"chat-v4","notes":"tighter EAA answer"}}}},"responses":{"201":{"description":"Deployed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PromptDeployResult"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"`version_label_exists` — field `label`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/agents/{agt}/prompts/{pv}":{"parameters":[{"$ref":"#/components/parameters/agt"},{"name":"pv","in":"path","required":true,"schema":{"type":"string","examples":["pv_12"]}}],"get":{"tags":["Prompts"],"operationId":"getPromptVersion","summary":"One prompt version, including its body","x-uirix-scope":"agents:read","responses":{"200":{"description":"Prompt version","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/PromptVersion"},{"type":"object","properties":{"body":{"type":["string","null"]}}}]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/agents/{agt}/prompts/{pv}/restore":{"parameters":[{"$ref":"#/components/parameters/agt"},{"name":"pv","in":"path","required":true,"schema":{"type":"string","examples":["pv_9"]}}],"post":{"tags":["Prompts"],"operationId":"restorePromptVersion","summary":"Redeploy an old body as a NEW version","description":"Versions are immutable: this creates a new row carrying\n`restored_from: pv_9`, labelled `{old_label}-restored` unless `label`\nis given, and bumps `agent_version`.\n","x-uirix-scope":"agents:write","requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"label":{"type":"string","minLength":1,"maxLength":80}}}}}},"responses":{"201":{"description":"Restored","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/PromptDeployResult"},{"type":"object","properties":{"restored_from":{"type":"string","examples":["pv_9"]}}}]}}}},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"`version_label_exists`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/agent-versions":{"get":{"tags":["Agents"],"operationId":"listAgentVersions","summary":"Deploy markers for charts","description":"Every prompt deploy with the agent version it carried, newest first,\nkeyset-paged over `(deployed_at, id)`. Overlay these on a\n`group_by=agent_version` metrics series to see when each version went\nlive.\n\nIMPLEMENTATION — migration 004 added `agent_prompt_versions.agent_version`\nand backfilled the rows in force, so a retired prompt version now\nreports the agent version it belonged to instead of `null` (this closes\ndeviation #1 of DOCS/api/status/phase1-agents.md). Rows written before\nthe column existed still fall back to the agent's current version while\nthey are active.\n","x-uirix-scope":"agents:read","parameters":[{"name":"account_id","in":"query","schema":{"oneOf":[{"type":"string","pattern":"^(acc|usr)_[0-9]+$"},{"type":"array","items":{"type":"string"}}]},"description":"Required unless the credential covers exactly one account."},{"name":"agent_id","in":"query","schema":{"type":"string","pattern":"^agt_[0-9]+$"}},{"name":"from","in":"query","schema":{"type":"string","format":"date-time"},"description":"On `deployed_at`."},{"name":"to","in":"query","schema":{"type":"string","format":"date-time"}},{"$ref":"#/components/parameters/limit"},{"$ref":"#/components/parameters/cursor"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","required":["data","pagination","meta"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AgentVersionMarker"}},"pagination":{"$ref":"#/components/schemas/Pagination"},"meta":{"$ref":"#/components/schemas/Meta"}}}}}},"400":{"description":"`invalid_id` · `invalid_date` · `invalid_limit` · `invalid_cursor` · `unsupported_filter`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}}}},"/agents/{agt}/kb/documents":{"parameters":[{"$ref":"#/components/parameters/agt"}],"get":{"tags":["KnowledgeBase"],"operationId":"listKbDocuments","summary":"Files, text documents and crawled pages","description":"`doc_f{n}` are uploaded files and inline text; `doc_c{n}` are crawled\npages. Keyset-paged on `(created_at, kind, id)`, newest first.\n","x-uirix-scope":"kb:read","parameters":[{"name":"kind","in":"query","schema":{"type":"string","enum":["file","text","page"]}},{"name":"status","in":"query","schema":{"$ref":"#/components/schemas/DocumentStatus"}},{"$ref":"#/components/parameters/limit"},{"$ref":"#/components/parameters/cursor"}],"responses":{"200":{"description":"Documents","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ListEnvelope"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/KbDocument"}}}}]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"404":{"$ref":"#/components/responses/NotFound"}}},"post":{"tags":["KnowledgeBase"],"operationId":"createKbDocument","summary":"Add a file, an inline text document or a URL","description":"Three request shapes, one answer. Ingestion runs asynchronously through\nthe same pipeline the dashboard uses (10 MB multer limit, per-plan file\nand crawl-page caps, parse → index, summary staleness).\n\nA site crawl answers `{ \"doc_id\": null, \"crawl_id\": \"…\" }` and surfaces\nits pages as `doc_c…` documents; poll `GET /kb/status`.\n\nURLs are SSRF-checked before anything is queued: private, loopback,\nlink-local and UiriX hosts and non-http(s) schemes are `400 invalid_url`.\n","x-uirix-scope":"kb:write","requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"file":{"type":"string","format":"binary"}},"required":["file"]}},"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/KbTextDocument"},{"$ref":"#/components/schemas/KbUrlDocument"}]},"examples":{"text":{"value":{"title":"Opening hours","text":"We are open Sun–Thu 09:00–18:00 Israel time.","language":"en"}},"url":{"value":{"url":"https://www.equalweb.com/pricing/","mode":"page"}}}}}},"responses":{"202":{"description":"Accepted for processing","content":{"application/json":{"schema":{"type":"object","required":["doc_id","status","meta"],"properties":{"doc_id":{"type":["string","null"],"examples":["doc_f101"]},"crawl_id":{"type":["string","null"],"description":"Site and page crawls only."},"status":{"type":"string","enum":["processing"]},"meta":{"$ref":"#/components/schemas/Meta"}}}}}},"400":{"description":"`invalid_url` · `validation_error` (missing `title`/`text`, or no recognisable shape)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"`insufficient_scope` · `plan_limit_reached` (per-plan file or crawl-page cap)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"$ref":"#/components/responses/NotFound"},"413":{"description":"`payload_too_large` — upload above 10 MB","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/agents/{agt}/kb/status":{"parameters":[{"$ref":"#/components/parameters/agt"}],"get":{"tags":["KnowledgeBase"],"operationId":"getKbStatus","summary":"Crawl, summary and vector-sync status","description":"The state of the one summary the agent carries into conversations.\nUploaded documents and crawled pages are inputs to it and are never read\nduring a conversation, so `summary` — not the document list — is what\nsays whether the agent knows something.\n\n`summary.stale` does NOT clear on its own: nothing rebuilds a summary\nwithout being asked, so polling for it to become `false` after an upload\nwaits forever. Request a rebuild (`POST /agents/{agt}/kb/reindex`), then\npoll until `pending_sources` is 0 and `version` has moved.\n\nA stale summary is still served to every conversation, with no staleness\ncheck anywhere at runtime — the agent answers from old knowledge rather\nthan from none.\n\nIMPLEMENTATION: `crawl` is process-local and reads `idle` after a\nrestart even if a crawl ran. `vector_sync` is always `null` — the OpenAI\nvector-store sync was retired in September 2026 and retrieval now runs\noff the KB summary. The field is kept so clients written against the\ncontract do not break when a replacement lands.\n","x-uirix-scope":"kb:read","responses":{"200":{"description":"Status","content":{"application/json":{"schema":{"$ref":"#/components/schemas/KbStatus"}}}},"404":{"$ref":"#/components/responses/NotFound"}}}},"/agents/{agt}/kb/reindex":{"parameters":[{"$ref":"#/components/parameters/agt"}],"post":{"tags":["KnowledgeBase"],"operationId":"reindexKb","summary":"Ask for the KB summary to be rebuilt","description":"Merges every source that is not yet in the summary into it. The `202`\nmeans the request was accepted, NOT that the summary was rebuilt: the\nbuild runs after the response and a failure reaches the server log, not\nthe caller. Confirm with `GET /agents/{agt}/kb/status` — `pending_sources`\n0 and `version` moved.\n","x-uirix-scope":"kb:write","responses":{"202":{"description":"Accepted","content":{"application/json":{"schema":{"type":"object","required":["agent_id","status","meta"],"properties":{"agent_id":{"type":"string","examples":["agt_245"]},"status":{"type":"string","enum":["rebuild_requested"],"description":"The request was accepted. It is not a claim that the summary was rebuilt."},"meta":{"$ref":"#/components/schemas/Meta"}}}}}},"404":{"$ref":"#/components/responses/NotFound"}}}},"/agents/{agt}/widget":{"parameters":[{"$ref":"#/components/parameters/agt"}],"get":{"tags":["Widget"],"operationId":"getWidget","summary":"Widget settings, public token and embed snippet","description":"The only endpoint that exposes `public_token`. `public_token` and\n`embed_snippet` are `null` until a token is minted — either by setting\n`allowed_domains` or through `/widget/regenerate-token`.\n","x-uirix-scope":"agents:read","responses":{"200":{"description":"Widget","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Widget"}}}},"404":{"$ref":"#/components/responses/NotFound"}}},"patch":{"tags":["Widget"],"operationId":"patchWidget","summary":"Update widget appearance and domains","description":"Appearance is presentation, not behaviour: this does NOT bump\n`agent_version`. Setting a non-empty `allowed_domains` on a widget that\nhas no token yet mints one, matching the dashboard.\n","x-uirix-scope":"agents:write","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WidgetPatch"}}}},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Widget"}}}},"400":{"description":"`unsupported_field` · `validation_error` · `empty_update`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"$ref":"#/components/responses/NotFound"}}}},"/agents/{agt}/widget/regenerate-token":{"parameters":[{"$ref":"#/components/parameters/agt"}],"post":{"tags":["Widget"],"operationId":"regenerateWidgetToken","summary":"Mint a new public token","description":"The old token stops working immediately — existing embeds break until updated.","x-uirix-scope":"agents:write","responses":{"200":{"description":"Widget with the new token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Widget"}}}},"404":{"$ref":"#/components/responses/NotFound"}}}},"/agents/{agt}/voice":{"parameters":[{"$ref":"#/components/parameters/agt"}],"get":{"tags":["Voice"],"operationId":"getVoiceConfig","summary":"Realtime voice configuration","description":"`provider` is the agent's `ai_provider`; `model` is the realtime model\nthat provider actually uses (`gpt-realtime-2.1` for OpenAI,\n`gemini-live-2.5-flash-native-audio` for Gemini), read from the same\nconfig files the voice services read.\n","x-uirix-scope":"agents:read","responses":{"200":{"description":"Voice configuration","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VoiceConfig"}}}},"404":{"$ref":"#/components/responses/NotFound"}}},"patch":{"tags":["Voice"],"operationId":"patchVoiceConfig","summary":"Update the voice configuration","description":"Bumps `agent_version` — this is what the realtime services read at call\nsetup. `voice_id`, `voice_locale`, `speaking_rate`, `barge_in` and\n`style` are mirrored onto the `agents` row through the shared update\nservice so the two never drift.\n","x-uirix-scope":"agents:write","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VoiceConfigPatch"},"example":{"speaking_rate":1.1,"barge_in":true}}}},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"type":"object","required":["voice","agent_version","meta"],"properties":{"voice":{"$ref":"#/components/schemas/VoiceConfig"},"agent_version":{"type":"string","examples":["agt245-v4"]},"meta":{"$ref":"#/components/schemas/Meta"}}}}}},"400":{"description":"`unsupported_field` · `validation_error` · `empty_update`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"$ref":"#/components/responses/NotFound"}}}},"/conversations":{"post":{"tags":["Conversations"],"operationId":"createConversation","summary":"Start a conversation with an agent","description":"Creates an ordinary `conv_p…` conversation carrying\n`channel_detail.channel = \"api\"`: it appears in `GET /conversations`, in\nthe dashboard's Chat tab, gets a summary and is billed like widget chat.\n\n`session_id` is minted server-side as `api_` + a ULID — the request never\nchooses it. `agent_version`, `prompt_version` and `model` are stamped at\ncreation and never move, even if the agent is redeployed mid-conversation.\n\n`send_greeting: true` writes the agent's greeting as turn 0\n(`turn_type: \"greeting\"`, `latency_ms: null`), resolved exactly as the\nwidget resolves it: `public_chat_build_output.responseTemplates.greeting_initial`,\nthen `public_chat_greeting`, then the agent's `greeting`, then a generic\nwelcome.\n\n`identity`, `custom` and `external_session_id` are applied **before** the\ngreeting, so the first turn can already use a name supplied here.\n\n**Balance.** `BalanceLimitService` runs on create and on every turn; an\ninactive subscription or an exhausted balance is `402 payment_required`\n(the check is cached for up to 10 minutes after a top-up).\n","x-uirix-scope":"conversations:write","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateConversationRequest"},"examples":{"full":{"value":{"agent_id":"agt_245","send_greeting":true,"external_session_id":"crm-session-42","identity":{"equalweb_agent_id":"1619","email":"procurement@example-shop.de","email_source":"crm_prefill"},"custom":{"plan":"free","site_id":"55231"}}}}}}},"responses":{"201":{"description":"Created.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreatedConversation"}}}},"400":{"description":"`validation_error` (missing `agent_id`) · `invalid_id` · `unsupported_field`\n· `invalid_type` / `invalid_email` / `invalid_phone` (identity).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PaymentRequired"},"403":{"description":"`insufficient_scope` · `forbidden_account`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`agent_not_found` — unknown, or an agent of another account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}},"get":{"tags":["Conversations"],"operationId":"listConversations","summary":"List conversations across every channel","description":"One normalised object per conversation, whatever the channel — widget and\nAPI chat (`conv_p…`), phone calls and dashboard realtime voice (`conv_u…`)\nand dashboard chat (`conv_d…`).\n\n**Default window.** With no `from`, `to` or `updated_since` the last 30\ndays are returned and `meta.applied_default_range` is `true`; the window\nthat was applied is always echoed in `meta.from` / `meta.to`.\n\n**Backfill.** A `from` older than 30 days, or a `limit` above 200, needs\nthe `conversations:backfill` scope and is metered in the backfill rate\nclass (10/min). With that scope `limit` may go up to 500.\n\n**Paging.** Keyset over `(sort column, store, ref_id)` with\n`TOP(limit + 1)` — no `COUNT`. `pagination.next_cursor` is signed and\nbound to the exact filter set AND the calling credential; reuse under any\nother filter, sort, limit or key is `400 invalid_cursor`. Terminal page:\n`has_more: false` **and** `next_cursor: null`.\n\n**Scope.** `conversations:read`. A key holding only `summaries:read` may\ncall this with `include=summary` and nothing else — asking for `identity`\nor `metrics` is `403 insufficient_scope`.\n\n**IMPLEMENTATION.** Internal AI operations that live in `usage_logs`\n(`config`, `knowledge`, `build`, `summary`, `assistant`) are never\nreturned — they are platform work, not customer conversations, and their\ntranscripts are internal prompt traffic. There is no override.\nSoft-deleted conversations are excluded from the list; reading one by id\nis `410 conversation_deleted`. `visitor_ip` is never exposed.\n","x-uirix-scope":"conversations:read","parameters":[{"name":"account_id","in":"query","description":"Repeatable. Defaults to the credential's home account. An account outside the credential is `403 forbidden_account`.","schema":{"type":"array","items":{"type":"string","pattern":"^(acc|usr)_\\d+$"}},"style":"form","explode":true},{"name":"agent_id","in":"query","description":"Repeatable — `agt_245`.","schema":{"type":"array","items":{"type":"string","pattern":"^agt_\\d+$"}},"style":"form","explode":true},{"name":"agent_type","in":"query","description":"Repeatable.","schema":{"type":"array","items":{"$ref":"#/components/schemas/AgentType"}},"style":"form","explode":true},{"name":"channel","in":"query","description":"Repeatable.","schema":{"type":"array","items":{"$ref":"#/components/schemas/Channel"}},"style":"form","explode":true},{"name":"from","in":"query","description":"Inclusive lower bound on `started_at` (strict ISO-8601 with a time part and a zone).","schema":{"type":"string","format":"date-time"}},{"name":"to","in":"query","description":"**Exclusive** upper bound on `started_at`.","schema":{"type":"string","format":"date-time"}},{"name":"updated_since","in":"query","description":"Conversations whose status, transcript, summary or identity changed\nafter this time. Preferred for incremental sync. Given alone it opens\nthe `started_at` window fully instead of applying the 30-day default.\n","schema":{"type":"string","format":"date-time"}},{"name":"status","in":"query","description":"Repeatable.","schema":{"type":"array","items":{"$ref":"#/components/schemas/ConversationStatus"}},"style":"form","explode":true},{"name":"language","in":"query","description":"Repeatable BCP-47; matches prefix-wise (`en` matches `en-US`).","schema":{"type":"array","items":{"type":"string"}},"style":"form","explode":true},{"name":"outcome","in":"query","description":"Repeatable summary outcome.","schema":{"type":"array","items":{"$ref":"#/components/schemas/Outcome"}},"style":"form","explode":true},{"name":"escalated","in":"query","description":"`summary.escalated_to_human.value`.","schema":{"type":"boolean"}},{"name":"has_identity","in":"query","description":"The \"lead\" filter — at least one of `identity.email`,\n`identity.phone_e164`, `identity.equalweb_agent_id` is non-null.\nPhone conversations count because their numbers are derived from the\ncall, so the filter and the payload always agree.\n","schema":{"type":"boolean"}},{"name":"has_recording","in":"query","schema":{"type":"boolean"}},{"name":"external_session_id","in":"query","description":"IMPLEMENTATION — matched against the conversation's stored session id.","schema":{"type":"string"}},{"name":"include","in":"query","description":"CSV of `summary`, `identity`, `metrics`. Default `summary,identity`. `transcript` is detail-only → `400 unsupported_filter`.","schema":{"type":"string","examples":["summary,identity,metrics"]}},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":500,"default":50}},{"name":"cursor","in":"query","schema":{"type":"string"}},{"name":"sort","in":"query","schema":{"type":"string","enum":["-started_at","started_at","-updated_at","updated_at"],"default":"-started_at"}}],"responses":{"200":{"description":"OK","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConversationList"}}}},"400":{"description":"`invalid_date` · `invalid_date_range` · `invalid_cursor` · `invalid_limit`\n· `unsupported_filter` (unknown parameter, enum value, include or sort)\n· `invalid_id` (malformed `agent_id` / `account_id`) · `validation_error`\n(non-boolean `escalated` / `has_identity` / `has_recording`)\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"`forbidden_account` (account not on the credential; also the master key\nwithout `X-Uirix-Account`) · `insufficient_scope` (no\n`conversations:read`/`summaries:read`; `include` beyond `summary` on a\nsummaries-only key; deep history or `limit > 200` without\n`conversations:backfill`)\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/conversations/deleted":{"get":{"tags":["Conversations"],"operationId":"listDeletedConversations","summary":"Tombstones for consumers that missed the webhook","description":"Keyset page over `(deleted_at DESC, id DESC)`. Requires\n`conversations:read`.\n\nThis path is matched BEFORE `/conversations/{conv}`; `deleted` is never\nparsed as a conversation id.\n","security":[{"bearerAuth":[]}],"parameters":[{"name":"account_id","in":"query","schema":{"type":"string"}},{"name":"since","in":"query","schema":{"type":"string","format":"date-time"}},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"cursor","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"A page of tombstones.","content":{"application/json":{"schema":{"type":"object","required":["data","pagination","meta"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Tombstone"}},"pagination":{"$ref":"#/components/schemas/Pagination"},"meta":{"$ref":"#/components/schemas/Meta"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}}}},"/conversations/{conv}":{"get":{"tags":["Conversations"],"operationId":"getConversation","summary":"One conversation with its transcript","description":"The list object plus `transcript`, `external_session_id`, `redacted` and\n`redaction_rules_applied`. Turns are parsed from the JSON transcript for\n`conv_p…` / `conv_u…` and from the `messages` table for `conv_d…`.\n\nA conversation belonging to another account is `404` — existence is never\nleaked. A soft-deleted one is `410 conversation_deleted`.\n\nA key holding only `summaries:read` gets `403 insufficient_scope` here and\nmust use `/conversations/{conv}/summary`.\n","x-uirix-scope":"conversations:read","parameters":[{"name":"conv","in":"path","required":true,"schema":{"type":"string","pattern":"^conv_[pud]\\d+$"}},{"name":"include","in":"query","description":"CSV of `transcript`, `summary`, `identity`, `metrics`. Default: all four.","schema":{"type":"string"}},{"name":"transcript_format","in":"query","description":"`text` adds `transcript_text`, a derived rendering; `transcript` (turns) stays the source of truth.","schema":{"type":"string","enum":["turns","text"],"default":"turns"}},{"name":"redact","in":"query","description":"Mask PII in the transcript and summary. Defaults to the account's `default_redaction`.","schema":{"type":"boolean"}},{"name":"account_id","in":"query","schema":{"type":"array","items":{"type":"string"}},"style":"form","explode":true}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConversationDetail"}}}},"400":{"description":"`invalid_id` · `unsupported_filter` (unknown `include` or `transcript_format`)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"`forbidden_account` · `insufficient_scope` (`summaries:read` alone)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"`conversation_not_found` — unknown id, or a conversation of another account","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"410":{"description":"`conversation_deleted` — the envelope carries `deleted_at` and `reason`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}},"parameters":[{"name":"conv","in":"path","required":true,"schema":{"type":"string","pattern":"^conv_[pud]\\d+$"}}]},"/conversations/{conv}/summary":{"get":{"tags":["Conversations"],"operationId":"getConversationSummary","summary":"The summary object on its own","description":"Never `404` for \"not summarised yet\" — that is `summary_status: \"pending\"`\nwith null members. Available to a key holding only `summaries:read`, which\ngets no transcript and no identity.\n","x-uirix-scope":"summaries:read","parameters":[{"name":"conv","in":"path","required":true,"schema":{"type":"string","pattern":"^conv_[pud]\\d+$"}},{"name":"account_id","in":"query","schema":{"type":"array","items":{"type":"string"}},"style":"form","explode":true}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConversationSummaryResponse"}}}},"400":{"description":"`invalid_id`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"`conversation_not_found`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"410":{"description":"`conversation_deleted`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/conversations/{conv}/recording":{"parameters":[{"name":"conv","in":"path","required":true,"schema":{"type":"string","pattern":"^conv_[pud]\\d+$"}}],"get":{"tags":["Conversations"],"operationId":"getConversationRecording","summary":"Signed, expiring URL for a call recording","description":"Phone conversations only. Returns a signed URL, never bytes and never a\npermanent link. The signature is\n`HMAC-SHA256(PUBLIC_API_RECORDING_SECRET, \"sid.acc.exp\")`, so a url\nminted for one account can never be replayed against another.\n\n`ttl_seconds` is CLAMPED to 900-3600 (values outside the range are\nadjusted, not rejected). Requires `audio:read`; rate class `audio`\n(30/min).\n\n`channels` is `dual` with a `channel_map` for calls placed after the\ndual-channel recording change, and `mono` with\n`channel_map: null` for older recordings — the value is read from the\nrecording itself. `size_bytes` and `duration_ms` may be `null` when the\nstorage backend does not answer in time; the url is still valid.\n","security":[{"bearerAuth":[]}],"parameters":[{"name":"account_id","in":"query","schema":{"type":"string"}},{"name":"ttl_seconds","in":"query","schema":{"type":"integer","minimum":900,"maximum":3600,"default":900},"description":"Clamped into the range."}],"responses":{"200":{"description":"The recording descriptor.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Recording"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"`insufficient_scope` — `audio:read` is required.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`conversation_not_found` · `recording_not_found` (a chat conversation, or a call that was not recorded).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"410":{"description":"`recording_expired` — past `recording_retention_days`; the error\nobject carries `retention_expires_at`. `conversation_deleted` when\nthe conversation was erased.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` (audio class).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/media/recordings/{sid}.mp3":{"parameters":[{"name":"sid","in":"path","required":true,"schema":{"type":"string","pattern":"^RE[0-9a-fA-F]{32}$"}}],"get":{"tags":["Media"],"operationId":"streamRecording","summary":"Stream a recording (authorised by its signature)","description":"Takes NO bearer credential — an `<audio>` element cannot send one. The\nquery signature is the credential, and the route is limited per IP at\n30/min. The sid is additionally checked against the account named in the\nsignature, so a valid signature can never be pointed at another\ntenant's audio.\n","security":[],"parameters":[{"name":"acc","in":"query","required":true,"schema":{"type":"string","pattern":"^acc_\\d+$"}},{"name":"exp","in":"query","required":true,"schema":{"type":"integer"},"description":"Unix seconds."},{"name":"sig","in":"query","required":true,"schema":{"type":"string","pattern":"^[0-9a-f]{64}$"}}],"responses":{"200":{"description":"The audio.","content":{"audio/mpeg":{"schema":{"type":"string","format":"binary"}}}},"403":{"description":"`signature_invalid` (mismatch or malformed id) · `url_expired` (past `exp`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`recording_not_found` — the sid does not belong to the signed account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — 30 requests per minute per IP.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/conversations/{conv}/messages":{"post":{"tags":["Conversations"],"operationId":"sendConversationMessage","summary":"Send a user message and receive the agent's turn","description":"**Non-streaming**: the response returns when the full reply is ready\n(typically 2–8 s; up to 15 s when the agent runs tools). Tools configured\non the agent — lead capture, e-mail, orders, connector capabilities — run\ninside this call, and a captured e-mail or phone lands in the\nconversation's identity block.\n\nThe user's message is appended as turn `turn_index − 1`; read the whole\ntranscript with `GET /conversations/{conv}`.\n\n**Concurrency.** One turn at a time per conversation: a second message\nwhile one is running is `409 turn_in_progress`. The transcript is a single\nJSON column updated read-modify-write, so concurrent turns would lose\nmessages.\n\n**Idempotency.** `idempotency_key` is honoured for 24 h per conversation:\nthe original response body is returned byte-for-byte and the turn is\nneither re-run nor re-billed.\n\n**Billing.** Each turn is billed as a widget chat turn\n(`session_type = \"chat\"`, the `publicWidget` multiplier), distinguished\nonly by `channel = \"api\"`. `4xx`, `409` and `429` are never billed.\n\n**Engine.** Always OpenAI, whatever the agent's `ai_provider`.\n","x-uirix-scope":"conversations:write","parameters":[{"name":"conv","in":"path","required":true,"schema":{"type":"string","pattern":"^conv_p\\d+$"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SendMessageRequest"}}}},"responses":{"200":{"description":"The agent's turn.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TurnResponse"}}}},"400":{"description":"`invalid_text` (empty or whitespace) · `text_too_long` (> 10 000 chars)\n· `unsupported_field` · `invalid_id` · `not_api_conversation`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PaymentRequired"},"403":{"description":"`insufficient_scope` · `forbidden_account`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`conversation_not_found`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`turn_in_progress` · `conversation_already_ended`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"410":{"description":"`conversation_deleted`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/conversations/{conv}/identify":{"post":{"tags":["Conversations"],"operationId":"identifyConversation","summary":"Attach CRM identity to a conversation","description":"Idempotent **merge** into the identity block, written through the same\nservice the widget's `window.uirix.identify()` uses, so the two surfaces\ncan never disagree: keys you send overwrite, keys you omit are untouched,\n`utm` and `custom` merge key by key.\n\n`equalweb_agent_id` must be a JSON **string** and round-trips verbatim\n(`\"1619\"`, `\"-1\"`, `\"007\"`); a number is `400 invalid_type` because it\nwould destroy leading zeros. `phone_e164` is validated with\nlibphonenumber and stored in E.164 only.\n\nReturns the full identity block — all 23 keys.\n\n**IMPLEMENTATION.** An explicit `null` does **not** clear a stored value;\nit is treated as \"not supplied\" (see the deviations note).\n","x-uirix-scope":"conversations:write","parameters":[{"name":"conv","in":"path","required":true,"schema":{"type":"string","pattern":"^conv_p\\d+$"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/IdentifyRequest"}}}},"responses":{"200":{"description":"The merged identity block.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"type":"object","required":["conversation_id","identity","meta"],"properties":{"conversation_id":{"type":"string","pattern":"^conv_p\\d+$"},"identity":{"$ref":"#/components/schemas/Identity"},"meta":{"$ref":"#/components/schemas/Meta"}}}}}},"400":{"description":"`invalid_type` (numeric `equalweb_agent_id`) · `invalid_email` ·\n`invalid_phone` · `unsupported_field` · `not_api_conversation` · `invalid_id`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"`insufficient_scope` · `forbidden_account`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`conversation_not_found`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"410":{"description":"`conversation_deleted`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/conversations/{conv}/end":{"post":{"tags":["Conversations"],"operationId":"endConversation","summary":"Close a conversation","description":"Marks the conversation `completed`, sets `ended_at` and queues the\nsummary. The summariser runs in the background so the call returns\nimmediately.\n\nConversations that are never closed become `abandoned` after 30 minutes\nwithout a message, so `/end` is a courtesy that gets a `completed` status\nand the summary sooner.\n\nThe body must be empty.\n","x-uirix-scope":"conversations:write","parameters":[{"name":"conv","in":"path","required":true,"schema":{"type":"string","pattern":"^conv_p\\d+$"}}],"responses":{"200":{"description":"Closed.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EndedConversation"}}}},"400":{"description":"`not_api_conversation` · `invalid_id` · `unsupported_field`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"`insufficient_scope` · `forbidden_account`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`conversation_not_found`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`conversation_already_ended`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"410":{"description":"`conversation_deleted`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/metrics/conversations":{"get":{"tags":["Metrics"],"operationId":"getConversationMetrics","summary":"Conversation aggregates","description":"Daily counts equal what pagination over `GET /conversations` returns for\nthe same window (±0): both sides share one definition of what counts as\na conversation — the same status expression, the same turn count and the\nsame internal-operations allow-list.\n\nRate class `metrics` (20 requests/minute), separate from the `read`\nclass.\n\n`from` and `to` are required: an unbounded aggregate over an account's\nwhole history is a table scan someone would eventually schedule every\nminute.\n","x-uirix-scope":"metrics:read","x-uirix-rate-class":"metrics","parameters":[{"name":"from","in":"query","required":true,"schema":{"type":"string","format":"date-time"},"description":"Inclusive, on `started_at`. Missing → `400 missing_param`."},{"name":"to","in":"query","required":true,"schema":{"type":"string","format":"date-time"},"description":"Exclusive."},{"name":"granularity","in":"query","schema":{"type":"string","enum":["day","week","month"],"default":"day"},"description":"Only meaningful when `date` is in `group_by`. `hour` →\n`400 unsupported_filter`. A week bucket is the Monday of that week\n(ISO), a month bucket is the first of the month.\n"},{"name":"group_by","in":"query","schema":{"type":"string"},"description":"Comma-separated, at most 3 of `date`, `account_id`, `agent_id`,\n`agent_type`, `intent`, `outcome`, `language`, `agent_version`.\nMore than 3 or an unknown dimension → `400 unsupported_filter`.\nWith no `group_by` the result is a single row for the whole range.\n","example":"date,agent_type"},{"name":"metrics","in":"query","schema":{"type":"string"},"description":"Comma-separated subset of the 15 metric names; default is all of them."},{"name":"timezone","in":"query","schema":{"type":"string","default":"UTC"},"description":"IANA name. Only bucket labels are localised; every timestamp in the\nresponse stays UTC. An unrecognised zone falls back to UTC and\n`meta.timezone_applied` differs from `meta.timezone`.\n","example":"Asia/Jerusalem"},{"name":"account_id","in":"query","schema":{"oneOf":[{"type":"string","pattern":"^(acc|usr)_[0-9]+$"},{"type":"array","items":{"type":"string"}}]},"description":"Repeatable. Defaults to the credential's accounts."},{"name":"agent_id","in":"query","schema":{"oneOf":[{"type":"string","pattern":"^agt_[0-9]+$"},{"type":"array","items":{"type":"string"}}]}},{"name":"agent_type","in":"query","schema":{"oneOf":[{"type":"string","enum":["chat","phone","phone_outbound","dashboard"]},{"type":"array","items":{"type":"string"}}]}},{"name":"language","in":"query","schema":{"oneOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}]}}],"responses":{"200":{"description":"One row per non-empty group. Groups with zero conversations are not\nemitted; a metric that cannot be computed is `null`, never omitted.\n","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"}},"content":{"application/json":{"schema":{"type":"object","required":["data","meta"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/ConversationMetricsRow"}},"meta":{"$ref":"#/components/schemas/MetricsMeta"}}},"example":{"data":[{"date":"2026-08-20","agent_type":"chat","conversations":214,"unique_visitors":198,"messages":1842,"escalations":19,"escalation_rate":0.0888,"containment_rate":0.9112,"resolved_rate":0.74,"leads_captured":22,"leads_created":24,"avg_handling_time_seconds":412,"median_handling_time_seconds":305,"avg_agent_latency_ms":null,"abandonment_rate":0.21,"unanswered_rate":null,"avg_csat":null}],"meta":{"request_id":"req_01J8ZG1A2B","environment":"production","from":"2026-08-01T00:00:00.000Z","to":"2026-08-21T00:00:00.000Z","granularity":"day","timezone":"UTC","timezone_applied":"UTC","group_by":["date","agent_type"]}}}}},"400":{"description":"`missing_param` (field `from` / `to`) · `invalid_date` ·\n`invalid_date_range` · `unsupported_filter` (bad `group_by`,\n`granularity`, `metrics`, `agent_type`) · `invalid_id` (field `agent_id`)\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"`insufficient_scope` (needs `metrics:read`) · `forbidden_account`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/calls":{"post":{"tags":["Calls"],"operationId":"createCall","summary":"Originate one outbound call","description":"Places ONE call from a UiriX voice agent to a number. The call is\nasynchronous: you get a `call_id` immediately and follow it with\n`GET /calls/{call}` or the `call.*` webhooks.\n\n**A one-off call is dialled when you ask for it.** A calling window is a\nCAMPAIGN concept — a campaign works down a list of people who never asked\nto be rung — and `POST /calls` does not inherit one. With nothing said\nabout time the call is `queued` immediately, whatever the local hour at\nthe other end.\n\nA call is `scheduled` only when this request asked for it, and the\nresponse says which part of the request did, in `scheduled_reason`:\n`policy.scheduled_at` waits for exactly that instant\n(`requested_time`); an explicit `earliest_local_time` /\n`latest_local_time` window is enforced in the RECIPIENT's own zone and a\ncall arriving outside `[earliest, latest)` waits for the next window\nstart (`outside_requested_window`). The zone comes from\n`context.timezone` when given, otherwise from the NANP area code,\notherwise from the calling country of `to_e164`, otherwise UTC.\n\n**Nothing defers a call silently.** If a call cannot go now and the\nrequest never asked it to wait, the answer is `409\ncall_deferral_refused` — never a `202` carrying a time the caller did\nnot choose.\n\n**`policy.voicemail: \"leave_message\"` is `400 unsupported_policy` in\nv1** (decision 17): detection works (`answered_by: \"voicemail\"`) and the\ncall is hung up, but no scripted message is left. `policy.voicemail_script`\nis rejected the same way.\n\nRate class `calls` (20/min). Requires `calls:write`.\n","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateCallRequest"}}}},"responses":{"202":{"description":"Accepted — the call is queued or scheduled.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateCallResponse"}}}},"400":{"description":"`invalid_phone` (`to_e164` / `from_e164`) · `unsupported_policy`\n(`policy.voicemail`, `policy.voicemail_script`) · `validation_error`\n(`earliest_local_time`, `max_attempts`, `retry_after_minutes`,\n`context.briefing` over 8,000 chars, `context.language` not one of the\n17 agent languages, `context.matter_key` over 128) ·\n`invalid_date` (`policy.scheduled_at`) · `unsupported_field`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"description":"`payment_required` — the account balance or subscription does not allow placing calls.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`dnc_blocked` — the number is on the account do-not-call list · `insufficient_scope` · `forbidden_account`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`agent_not_found`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`agent_has_no_phone_number` · `from_number_not_owned` ·\n`call_deferral_refused` — the call could not be placed now and the\nrequest did not ask for it to wait; the error carries\n`would_have_been_scheduled_at` and `recipient_timezone`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`call_quota_exceeded` (daily `max_calls_per_day`, UTC day; the error\nobject carries `limit`, `used`, `retry_after` and `resets_at`) or\n`rate_limited` (calls class).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"get":{"tags":["Calls"],"operationId":"listCalls","summary":"List calls","description":"Keyset page over `(created_at DESC, id DESC)`. Default window: the last\n30 days on `created_at`. Requires `calls:read`.\n","security":[{"bearerAuth":[]}],"parameters":[{"name":"account_id","in":"query","schema":{"type":"string"},"description":"Account the credential covers."},{"name":"matter_key","in":"query","schema":{"type":"string"},"description":"Exact match — one case is one queryable thread."},{"name":"status","in":"query","description":"Repeatable / comma separated.","schema":{"type":"array","items":{"$ref":"#/components/schemas/CallStatus"}}},{"name":"agent_id","in":"query","schema":{"type":"string"}},{"name":"to_e164","in":"query","schema":{"type":"string"}},{"name":"from","in":"query","schema":{"type":"string","format":"date-time"}},{"name":"to","in":"query","schema":{"type":"string","format":"date-time"}},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"cursor","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"A page of calls.","content":{"application/json":{"schema":{"type":"object","required":["data","pagination","meta"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Call"}},"pagination":{"$ref":"#/components/schemas/Pagination"},"meta":{"$ref":"#/components/schemas/Meta"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}}}},"/calls/{call}":{"parameters":[{"name":"call","in":"path","required":true,"schema":{"type":"string","pattern":"^call_\\d+$"}}],"get":{"tags":["Calls"],"operationId":"getCall","summary":"Read one call","description":"Requires `calls:read`. A call of another account is `404 not_found` — existence is never leaked.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"The call.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Call"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"`not_found`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["Calls"],"operationId":"cancelCall","summary":"Cancel a call that has not been answered","description":"Cancels `queued`, `scheduled` or `ringing`; anything else is\n`409 call_not_cancellable` (the error object carries the current\n`status`). A ringing call is also cancelled at the carrier. Requires\n`calls:write`; rate class `calls`.\n","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Cancelled.","content":{"application/json":{"schema":{"type":"object","required":["call_id","status","meta"],"properties":{"call_id":{"type":"string","examples":["call_88"]},"status":{"type":"string","const":"cancelled"},"meta":{"$ref":"#/components/schemas/Meta"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"`not_found`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`call_not_cancellable`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/dnc":{"post":{"tags":["Do-not-call"],"operationId":"createDncEntry","summary":"Add a number to the do-not-call list","description":"Idempotent: re-adding an existing number returns the existing entry with\n`200` instead of `201`. Every entry is mirrored onto\n`outbound_list_items.do_not_call`, so campaigns honour it too. Requires\n`dnc:write`.\n\nIMPLEMENTATION: `source` accepts `api` (default) and `import` only —\n`agent_request` is written by the platform when a call's analysis\ndetects the request, and naming it here is `400 unsupported_filter`.\n","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["phone_e164"],"additionalProperties":false,"properties":{"account_id":{"type":"string"},"phone_e164":{"type":"string","examples":["+12125550147"]},"reason":{"type":["string","null"],"maxLength":500},"source":{"type":"string","enum":["api","import"],"default":"api"}}}}}},"responses":{"200":{"description":"The number was already on the list.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DncEntry"}}}},"201":{"description":"Created.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DncEntry"}}}},"400":{"description":"`invalid_phone` · `unsupported_filter` (`source`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}}},"get":{"tags":["Do-not-call"],"operationId":"listDncEntries","summary":"List do-not-call entries","description":"Keyset page over `(created_at DESC, id DESC)`. Requires `dnc:read`.","security":[{"bearerAuth":[]}],"parameters":[{"name":"account_id","in":"query","schema":{"type":"string"}},{"name":"phone_e164","in":"query","schema":{"type":"string"}},{"name":"source","in":"query","schema":{"type":"string","enum":["api","agent_request","import"]}},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"cursor","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"A page of entries.","content":{"application/json":{"schema":{"type":"object","required":["data","pagination","meta"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/DncEntry"}},"pagination":{"$ref":"#/components/schemas/Pagination"},"meta":{"$ref":"#/components/schemas/Meta"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}}}},"/webhooks/endpoints":{"post":{"tags":["Webhooks"],"operationId":"createWebhookEndpoint","summary":"Register a subscriber URL","description":"The `secret` is returned **once**, here, and stored encrypted; there is\nno retrieval endpoint. Use `POST /webhooks/endpoints/{whk}/rotate-secret`\nif it is lost.\n\nURL rules: `https://` only, a public host only (validated with the same\nSSRF guard the crawler uses, and re-checked at send time through a\nguarded DNS lookup), never a UiriX host. Outside production a loopback\nor private target is accepted so an integrator can point a webhook at\ntheir own machine; `EXT_WEBHOOK_ALLOW_PRIVATE=false` turns that off and\nproduction never allows it.\n","x-uirix-scope":"webhooks:manage","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEndpointCreate"},"example":{"url":"https://hooks.example.com/uirix","events":["conversation.started","conversation.ended","conversation.escalated","summary.ready"],"description":"warehouse + HubSpot sync"}}}},"responses":{"201":{"description":"Created — the only response that carries `secret`.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEndpointWithSecret"}}}},"400":{"description":"`invalid_url` (field `url` — not https, private host, or a UiriX host) ·\n`unsupported_event` (field `events`) · `validation_error` ·\n`unsupported_field`\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}}},"get":{"tags":["Webhooks"],"operationId":"listWebhookEndpoints","summary":"List the account's endpoints","description":"IMPLEMENTATION — not cursor-paged. An account has a handful of\nendpoints, so the whole set is returned inside the standard list\nenvelope with `has_more: false` and `next_cursor: null`; adding paging\nlater is not a breaking change.\n","x-uirix-scope":"webhooks:manage","parameters":[{"$ref":"#/components/parameters/accountIdQuery"}],"responses":{"200":{"description":"OK — never contains `secret`.","content":{"application/json":{"schema":{"type":"object","required":["data","pagination","meta"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/WebhookEndpoint"}},"pagination":{"$ref":"#/components/schemas/Pagination"},"meta":{"$ref":"#/components/schemas/Meta"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}}}},"/webhooks/endpoints/{whk}":{"parameters":[{"name":"whk","in":"path","required":true,"schema":{"type":"string","pattern":"^whk_[0-9]+$"},"example":"whk_7"}],"get":{"tags":["Webhooks"],"operationId":"getWebhookEndpoint","summary":"Read one endpoint","x-uirix-scope":"webhooks:manage","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEndpoint"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}},"patch":{"tags":["Webhooks"],"operationId":"patchWebhookEndpoint","summary":"Change url, events, description or enabled","description":"`enabled: true` on an auto-disabled endpoint also resets\n`consecutive_failures` and clears `disabled_reason` — otherwise the next\ndead delivery would switch it off again immediately.\n","x-uirix-scope":"webhooks:manage","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEndpointPatch"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEndpoint"}}}},"400":{"description":"`invalid_url` · `unsupported_event` · `unsupported_field` · `empty_update`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/webhooks/endpoints/{whk}/rotate-secret":{"parameters":[{"name":"whk","in":"path","required":true,"schema":{"type":"string","pattern":"^whk_[0-9]+$"}}],"post":{"tags":["Webhooks"],"operationId":"rotateWebhookSecret","summary":"Mint a new signing secret","description":"The current secret becomes `previous` for 7 days. During the overlap\nevery delivery carries both `X-Uirix-Signature` (new) and\n`X-Uirix-Signature-Previous` (old), so a receiver can be redeployed at\nany point in the window without dropping an event.\n","x-uirix-scope":"webhooks:manage","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","required":["endpoint_id","secret","previous_secret_expires_at","meta"],"properties":{"endpoint_id":{"type":"string","example":"whk_7"},"secret":{"type":"string","pattern":"^whsec_[A-Za-z0-9_-]{43}$"},"previous_secret_expires_at":{"type":"string","format":"date-time"},"meta":{"$ref":"#/components/schemas/Meta"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/webhooks/endpoints/{whk}/test":{"parameters":[{"name":"whk","in":"path","required":true,"schema":{"type":"string","pattern":"^whk_[0-9]+$"}}],"post":{"tags":["Webhooks"],"operationId":"testWebhookEndpoint","summary":"Send a synchronous ping","description":"Signs and POSTs a `ping` event and waits for the answer (10 s timeout,\nno retries).\n\nIMPLEMENTATION — the ping is written to the delivery log like any other\nevent, already terminal (`delivered` or `failed`), so it is visible at\n`GET /webhooks/deliveries?event=ping`.\n","x-uirix-scope":"webhooks:manage","responses":{"200":{"description":"The attempt's result — `delivered:false` is a normal answer, not an error.","content":{"application/json":{"schema":{"type":"object","required":["delivered","response_code","duration_ms","delivery_id","error","meta"],"properties":{"delivered":{"type":"boolean"},"response_code":{"type":["integer","null"]},"duration_ms":{"type":"integer"},"delivery_id":{"type":"string","pattern":"^dlv_[0-9A-HJKMNP-TV-Z]{26}$"},"error":{"type":["string","null"],"description":"`timeout` · `connection_refused` · `dns_error` · `redirect` · `blocked_host` · `http_<code>`"},"meta":{"$ref":"#/components/schemas/Meta"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/webhooks/deliveries":{"get":{"tags":["Webhooks"],"operationId":"listWebhookDeliveries","summary":"The delivery log","description":"Keyset-paged over `(created_at, id)`, newest first. Default window is the last 7 days.","x-uirix-scope":"webhooks:manage","parameters":[{"name":"status","in":"query","schema":{"type":"string","enum":["pending","delivered","failed","replaying"]}},{"name":"event","in":"query","schema":{"type":"string"},"description":"One of the subscribable events, or `ping`."},{"name":"endpoint_id","in":"query","schema":{"type":"string","pattern":"^whk_[0-9]+$"}},{"name":"conversation_id","in":"query","schema":{"type":"string","pattern":"^conv_[pud][0-9]+$"}},{"name":"from","in":"query","schema":{"type":"string","format":"date-time"}},{"name":"to","in":"query","schema":{"type":"string","format":"date-time"}},{"$ref":"#/components/parameters/limit"},{"$ref":"#/components/parameters/cursor"},{"$ref":"#/components/parameters/accountIdQuery"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","required":["data","pagination","meta"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/WebhookDelivery"}},"pagination":{"$ref":"#/components/schemas/Pagination"},"meta":{"$ref":"#/components/schemas/Meta"}}}}}},"400":{"description":"`invalid_date` · `invalid_date_range` · `invalid_limit` · `invalid_cursor` · `unsupported_filter` · `unsupported_event`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}}}},"/webhooks/deliveries/{dlv}/replay":{"parameters":[{"name":"dlv","in":"path","required":true,"schema":{"type":"string","pattern":"^dlv_[0-9A-HJKMNP-TV-Z]{26}$"}}],"post":{"tags":["Webhooks"],"operationId":"replayWebhookDelivery","summary":"Re-queue a delivery","description":"Same `delivery_id`, same frozen bytes; only `X-Uirix-Timestamp` and the\nsignature are new. The attempt counter restarts so the full retry ladder\nis available again. Works for `failed` and `delivered` deliveries.\n","x-uirix-scope":"webhooks:manage","responses":{"202":{"description":"Queued","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookDelivery"}}}},"400":{"description":"`invalid_id` · `validation_error` — the delivery is already queued\n(`pending` / `replaying`).\n\nIMPLEMENTATION — webhooks.md prints `409 validation_error`; the error\ncatalogue pins `validation_error` to 400 and `ApiError` derives the\nstatus from the catalogue, so the stable half of the contract (the\n`code`) is honoured and the status follows the catalogue. Same\nresolution as the account and credential timestamps.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/voices":{"get":{"summary":"List the voices available for a provider","description":"The set of voice ids an agent may be given. The catalogues for `openai` and\n`gemini` do not overlap (`gpt_live` shares ten voices with `openai` and adds twelve\nof its own, offered only where the platform has GPT-Live switched on), so changing an agent's `ai_provider` invalidates its\ncurrent voice. Read this before writing `voice` on `PATCH /agents/{agt}`, and\nsend `ai_provider` and `voice` in the same request when switching provider:\nnothing validates the pairing server-side today.\n\nReference data, identical for every account, so it takes no account parameter.\n","operationId":"listVoices","tags":["Agents"],"x-uirix-scope":"agents:read","parameters":[{"name":"provider","in":"query","required":false,"description":"Omit to receive both catalogues.","schema":{"type":"string","enum":["openai","gemini","gpt_live"]}},{"name":"include_inactive","in":"query","required":false,"description":"Retired voices are hidden by default. Ask for them only to explain a voice an\nexisting agent already has, never to offer one as a new choice.\n","schema":{"type":"string","enum":["true","false"]}}],"responses":{"200":{"description":"The voice catalogue.","content":{"application/json":{"schema":{"type":"object","required":["data","meta"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Voice"}},"meta":{"allOf":[{"$ref":"#/components/schemas/Meta"},{"type":"object","properties":{"providers":{"type":"array","items":{"type":"string","enum":["openai","gemini","gpt_live"]}},"include_inactive":{"type":"boolean"},"counts":{"type":"object","additionalProperties":{"type":"integer"}}}}]}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}}}},"/agents/{agt}/profiles":{"parameters":[{"$ref":"#/components/parameters/agt"}],"get":{"tags":["Profiles"],"operationId":"listOutboundProfiles","summary":"List an agent's outbound call profiles","description":"Newest first, keyset paged. Every row is the full profile object,\nincluding the whole `system_instruction_template` — there is no summary\nform, because the template is normally what the caller came for.\n\nAn agent the credential does not own answers `404 agent_not_found`, not\nan empty list: a silent empty page reads as \"this agent has no profiles\".\n","x-uirix-scope":"agents:read","parameters":[{"name":"q","in":"query","required":false,"description":"Case-insensitive substring match on `label`.","schema":{"type":"string","minLength":1,"maxLength":255}},{"name":"category","in":"query","required":false,"description":"Exact match. Free text — see the `category` field.","schema":{"type":"string","minLength":1,"maxLength":50}},{"name":"is_active","in":"query","required":false,"schema":{"type":"string","enum":["true","false"]}},{"$ref":"#/components/parameters/limit"},{"$ref":"#/components/parameters/cursor"}],"responses":{"200":{"description":"Profiles for the agent.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ListEnvelope"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/OutboundProfile"}}}}]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"`agent_not_found` — unknown agent, or one belonging to another account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}},"post":{"tags":["Profiles"],"operationId":"createOutboundProfile","summary":"Create an outbound call profile","description":"Creates the profile against the agent in the path, and generates\n`profile_code` server-side.\n\n`profile_code` is NOT accepted in the body (`400 unsupported_field`):\ncodes are not unique in the database, so two accounts naming a profile\n`pci_euro` would collide in every lookup that goes by code. The epoch\nsuffix is what keeps them apart.\n\nAny unknown key is `400 unsupported_field` naming the key. A rejected\ncreate writes nothing.\n","x-uirix-scope":"agents:write","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OutboundProfileCreate"},"example":{"label":"PCI euro","category":"collections","system_instruction_template":"# Identity\nYou are Dana from EqualWeb billing…","greeting_message":"Hello, this is Dana from EqualWeb. Am I speaking with [Customer Name]?","allowed_capabilities":["send_payment_link","create_payment_promise","end_call"],"completions":{"payment_link_base_url":"https://example.com/pay"}}}}},"responses":{"201":{"description":"Created, with the generated `profile_code`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OutboundProfileResponse"}}}},"400":{"description":"`unsupported_field` (unknown body key, including `profile_code`) · `validation_error` · `invalid_id`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"`agent_not_found`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/profiles/{prf}":{"parameters":[{"$ref":"#/components/parameters/prf"}],"get":{"tags":["Profiles"],"operationId":"getOutboundProfile","summary":"Get one outbound call profile","description":"The full object. `system_instruction_template` and\n`allowed_capabilities` are returned complete — never truncated or\nsummarised, because they are what a transcript is compared against.\n\nA profile belonging to another account is `404`, never `403`: a `prf_`\nid must not be a way to discover what exists elsewhere.\n","x-uirix-scope":"agents:read","responses":{"200":{"description":"The profile.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OutboundProfileResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"`profile_not_found`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}},"patch":{"tags":["Profiles"],"operationId":"updateOutboundProfile","summary":"Update an outbound call profile","description":"Partial update — only the keys present are written; everything else,\nincluding the instruction template, is untouched. `null` clears a\nnullable field.\n\n`profile_id`, `account_id`, `agent_id`, `profile_code`, `created_at` and\n`updated_at` are not writable. A profile cannot be moved to another\nagent: it is written against one agent's persona and capability set.\n","x-uirix-scope":"agents:write","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OutboundProfilePatch"},"example":{"allowed_capabilities":["send_email","end_call","send_sms"]}}}},"responses":{"200":{"description":"The updated profile.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OutboundProfileResponse"}}}},"400":{"description":"`unsupported_field` · `validation_error` · `empty_update`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"`profile_not_found`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/profiles/{prf}/clone":{"parameters":[{"$ref":"#/components/parameters/prf"}],"post":{"tags":["Profiles"],"operationId":"cloneOutboundProfile","summary":"Copy a profile under a new label","description":"Copies every field onto a new profile on the SAME agent, with a fresh\n`profile_code`. The clone is never the category default, whatever the\noriginal was — promoting it is a separate, explicit `PATCH`, so cloning\ncannot silently redirect a live campaign.\n","x-uirix-scope":"agents:write","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["label"],"properties":{"label":{"type":"string","minLength":1,"maxLength":255},"account_id":{"type":"string","examples":["acc_1001"]}},"additionalProperties":false},"example":{"label":"Lead Qualify — Q4 script"}}}},"responses":{"201":{"description":"The new profile.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OutboundProfileResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"`profile_not_found`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/agents/{agt}/kb/summary":{"parameters":[{"$ref":"#/components/parameters/agt"}],"get":{"tags":["KnowledgeBase"],"operationId":"getKbSummary","summary":"The knowledge summary — the only knowledge the agent carries","description":"The exact text the model receives on every surface, whole. An agent with no\nsummary answers 200 with `text: null` — a real state (it answers from its\ninstructions alone), not an error.\n","x-uirix-scope":"kb:read","responses":{"200":{"description":"The summary.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/KbSummary"}}}},"404":{"$ref":"#/components/responses/NotFound"}}},"patch":{"tags":["KnowledgeBase"],"operationId":"updateKbSummary","summary":"Replace the knowledge summary","description":"Writes what the model will read on the next conversation: `version`\nincrements, `stale` is cleared. Creates the summary (201) when the agent has\nnone. A later rebuild (`POST /kb/reindex`, or the dashboard) overwrites it by\nmerging the pending sources in. Delivered verbatim — write facts, in the\nagent's language, without markup the model would read aloud.\n","x-uirix-scope":"kb:write","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["text"],"additionalProperties":false,"properties":{"account_id":{"type":"string"},"text":{"type":"string","minLength":1,"maxLength":1000000}}}}}},"responses":{"200":{"description":"Replaced.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/KbSummaryWriteResponse"}}}},"201":{"description":"Created — the agent had no summary before.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/KbSummaryWriteResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/capabilities":{"get":{"summary":"The agent capability catalogue","description":"Every id `PATCH /agents/{agt}` accepts in `capabilities`, with what it does.\n`knowledge` entries shape how the agent answers a subject (and what the\nknowledge-summary builder extracts); `action` entries give the model a tool.\nSome actions need a value in `agents.completions` under `required_field.id`\nbefore they do anything. Unknown ids are refused on write since 2026-09-05.\n\nReference data, identical for every account — no account parameter.\n","operationId":"listAgentCapabilities","tags":["Agents"],"x-uirix-scope":"agents:read","parameters":[{"name":"kind","in":"query","required":false,"schema":{"type":"string","enum":["knowledge","action"]}},{"name":"include_inactive","in":"query","required":false,"description":"Retired entries are hidden by default; ask for them only to explain an id an agent already holds.","schema":{"type":"string","enum":["true","false"]}}],"responses":{"200":{"description":"The catalogue.","content":{"application/json":{"schema":{"type":"object","required":["data","meta"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AgentCapability"}},"meta":{"allOf":[{"$ref":"#/components/schemas/Meta"},{"type":"object","properties":{"kinds":{"type":"array","items":{"type":"string"}},"include_inactive":{"type":"boolean"},"counts":{"type":"object","properties":{"knowledge":{"type":"integer"},"action":{"type":"integer"}}}}}]}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}}}},"/outbound-capabilities":{"get":{"summary":"The outbound capability catalogue, per profile type","description":"The tools an outbound call may use, grouped by the profile TYPE that unlocks\nthem, plus each type's focuses and starting roles. A profile may grant only\nthe capabilities of its type's group and the communication tools — the same\nrule the dashboard wizard applies, enforced on `POST /agents/{agt}/profiles`\nand `PATCH /profiles/{prf}`. The inline `profile` on `POST /calls` has no\ntype and may name any active id.\n\n`appointments` exists but is inactive (hidden in the product) and is omitted\nunless `include_inactive=true`.\n","operationId":"listOutboundCapabilities","tags":["Profiles"],"x-uirix-scope":"agents:read","parameters":[{"name":"category","in":"query","required":false,"schema":{"type":"string","enum":["collections","sales","events","appointments"]}},{"name":"include_inactive","in":"query","required":false,"schema":{"type":"string","enum":["true","false"]}}],"responses":{"200":{"description":"One entry per type.","content":{"application/json":{"schema":{"type":"object","required":["data","meta"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/OutboundCategory"}},"meta":{"allOf":[{"$ref":"#/components/schemas/Meta"},{"type":"object","properties":{"categories":{"type":"array","items":{"type":"string"}},"category_groups":{"type":"object","description":"type → the tool groups it unlocks","additionalProperties":{"type":"array","items":{"type":"string"}}},"include_inactive":{"type":"boolean"}}}]}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}}}},"/profiles/{prf}/build":{"parameters":[{"$ref":"#/components/parameters/prf"}],"post":{"summary":"Rebuild the template and greeting from the card's inputs (\"Rebuild with AI\")","description":"Runs the dashboard's builder: the type, label, description, focuses,\ncapabilities, completions, tone, behavior, refinement rules and (optionally)\nthe agent's knowledge base become a new `system_instruction_template` and\n`greeting_message`, in the agent's language. Two model calls, billed to the\naccount as `build` usage.\n\n`202` — the build runs AFTER the response, for 30–90 s (minutes with retries):\nthe public hosts sit behind a 100-second proxy limit. Poll\n`GET /profiles/{prf}/build` until `done` or `error`, then read the profile;\n`updated_at` moved and the template changed. The profile row is the record —\nthe build state is process-local and forgets after an hour.\n\nOverwrites the two outputs, including hand-written text. The inputs are untouched.\n","operationId":"buildOutboundProfile","tags":["Profiles"],"x-uirix-scope":"agents:write","requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"account_id":{"type":"string"},"use_knowledge_base":{"type":"boolean","default":true,"description":"Fold the agent's knowledge summary into the script."}}}}}},"responses":{"202":{"description":"Build started.","content":{"application/json":{"schema":{"type":"object","required":["profile_id","status","build","meta"],"properties":{"profile_id":{"type":"string","examples":["prf_42"]},"status":{"type":"string","enum":["building"]},"build":{"$ref":"#/components/schemas/ProfileBuildState"},"meta":{"$ref":"#/components/schemas/Meta"}}}}}},"400":{"description":"`validation_error` — `category` is not a type, or `allowed_capabilities` is empty (the message lists the type's tools).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"`profile_build_in_progress` — a build of this profile is still running (carries `started_at`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}},"get":{"summary":"The state of the last build","operationId":"getOutboundProfileBuild","tags":["Profiles"],"x-uirix-scope":"agents:read","responses":{"200":{"description":"The state, or `none`.","content":{"application/json":{"schema":{"type":"object","required":["profile_id","status","build","updated_at","meta"],"properties":{"profile_id":{"type":"string"},"status":{"type":"string","enum":["building","done","error","none"]},"build":{"oneOf":[{"$ref":"#/components/schemas/ProfileBuildState"},{"type":"null"}]},"updated_at":{"type":["string","null"],"format":"date-time","description":"The profile's own `updated_at` — the durable confirmation that a build landed."},"meta":{"$ref":"#/components/schemas/Meta"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/accounts/{acc}/agents/build":{"parameters":[{"$ref":"#/components/parameters/acc"}],"post":{"summary":"Create an agent from a website (the dashboard wizard, server-side)","description":"The V4 agent-creation wizard as one call: send a website URL, a language and a\nvoice (gender, optionally a name). The agent is created AT ONCE with the wizard's\ndefaults — provider **gemini** unless `provider` says otherwise, main role\n`secretary`, large model, the ready-made capability set (`end_call`,\n`create_lead`, `send_email`, `send_website_link` — no `send_sms` for API-built\nagents — plus the selected `addons`, and the `products_catalog` focus; the\nContact-me action's e-mail is the account's), the voice's marketing name in the agent's language as its\nname — and the remaining steps run in the background: crawl the site, build the\nknowledge summary, generate the greeting and the agent's short description, build the phone / widget-chat /\nwidget-voice prompts. `202` with `agent_id`; poll `GET /agents/{agt}/build`.\nThe chain takes 4–9 minutes. A failed greeting is a warning; any other failure\nstops the chain and leaves the agent in `draft` (re-run with\n`POST /agents/{agt}/build`; an agent cannot be deleted through the API).\n","operationId":"buildAgentFromWebsite","tags":["Agents"],"x-uirix-scope":"agents:admin","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentBuildRequest"}}}},"responses":{"202":{"description":"Agent created; the build runs after the response.","content":{"application/json":{"schema":{"type":"object","required":["agent_id","status","build","agent","meta"],"properties":{"agent_id":{"type":"string","examples":["agt_412"]},"status":{"type":"string","enum":["building","done","error"]},"build":{"$ref":"#/components/schemas/AgentBuildState"},"agent":{"$ref":"#/components/schemas/Agent"},"meta":{"$ref":"#/components/schemas/Meta"}}}}}},"400":{"description":"`validation_error` with `field` (`website_url`, `language`, `voice.gender`, `voice.name`, `provider`, `main_role`, `addons`, `max_pages`; the message lists what is accepted) · `unsupported_field`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"`insufficient_scope` (a key without `agents:admin`) · `plan_limit_reached` · `forbidden_account`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/agents/{agt}/build":{"parameters":[{"$ref":"#/components/parameters/agt"}],"get":{"summary":"The state of the agent's build, step by step","description":"Poll until `status` is `done` or `error`. `build.steps` has one entry per\nstep (`create`, `crawl`, `knowledge`, `greeting`, `instructions`,\n`ready`) with its status, timing, what it produced (`detail`) or why it failed\n(`error`). The state is process-local and kept for an hour after the end: after\nthat, or after a restart, `status` is `none` and `build` is `null`, while\n`agent` (read from the agent row) still says where it got to.\n","operationId":"getAgentBuild","tags":["Agents"],"x-uirix-scope":"agents:read","responses":{"200":{"description":"The state, or `none`.","content":{"application/json":{"schema":{"type":"object","required":["agent_id","status","build","agent","meta"],"properties":{"agent_id":{"type":"string"},"status":{"type":"string","enum":["building","done","error","none"]},"build":{"oneOf":[{"$ref":"#/components/schemas/AgentBuildState"},{"type":"null"}]},"agent":{"$ref":"#/components/schemas/AgentBuildSnapshot"},"meta":{"$ref":"#/components/schemas/Meta"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}},"post":{"summary":"Re-run the build from a step","description":"Runs the background steps again from `from_step` on (default `crawl`),\nusing the website the agent was created with unless `website_url` is given.\nEarlier steps are marked `skipped`. For an agent whose build failed, whose\nknowledge changed (`from_step: knowledge`), or whose site changed.\n","operationId":"rebuildAgent","tags":["Agents"],"x-uirix-scope":"agents:write","requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"from_step":{"type":"string","enum":["crawl","knowledge","greeting","instructions"],"default":"crawl"},"website_url":{"type":"string","description":"Only with `from_step` `crawl`; default the stored URL."},"max_pages":{"type":"integer","minimum":1,"maximum":200,"default":15}}}}}},"responses":{"202":{"description":"Re-run started.","content":{"application/json":{"schema":{"type":"object","required":["agent_id","status","build","meta"],"properties":{"agent_id":{"type":"string"},"status":{"type":"string","enum":["building"]},"build":{"$ref":"#/components/schemas/AgentBuildState"},"meta":{"$ref":"#/components/schemas/Meta"}}}}}},"400":{"description":"`validation_error` (`from_step`, `website_url`, `max_pages`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"`agent_build_in_progress` — a build of this agent is still running (carries `started_at`, `step`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/accounts/{acc}/agents/{agt}/build":{"parameters":[{"$ref":"#/components/parameters/acc"},{"$ref":"#/components/parameters/agt"}],"get":{"summary":"§11.2 alias of GET /agents/{agt}/build","operationId":"getAgentBuildNested","tags":["Agents"],"x-uirix-scope":"agents:read","responses":{"200":{"description":"Same as GET /agents/{agt}/build."}}},"post":{"summary":"§11.2 alias of POST /agents/{agt}/build","operationId":"rebuildAgentNested","tags":["Agents"],"x-uirix-scope":"agents:write","responses":{"202":{"description":"Same as POST /agents/{agt}/build."}}}},"/partners/members":{"post":{"tags":["Partners"],"operationId":"createPartnerMember","summary":"Create a member — user on pay-as-you-go with a wallet credit, agent built from the website, one-time login link (master key)","description":"The partner's sign-up screen in one call (Nir, 2026-09-06 — \"EqualWeb Member\"):\ncreates the user on the pay-as-you-go plan with the partner wallet credit (no card;\nno phone number can be bought until a card is saved), creates the agent and starts\nthe V4 build in the background, and issues a single-use dashboard login link that\nlands on the agent's widget \"site code\" page. Poll `GET /partners/members/{usr}` for\nbuild progress; every read issues a fresh link. Rate class `build`.\n","x-uirix-scope":"users:manage (master key only)","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerMemberCreate"},"example":{"company_name":"Customer Ltd","email":"owner@customer.com","password":"S3cure-Passw0rd!","website_url":"https://www.customer.com","language":"he","voice":{"gender":"female"},"external_ids":{"equalweb_customer_id":"55231"}}}}},"responses":{"202":{"description":"Created; the build is running","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerMember"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"description":"`email_exists`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/partners/members/{usr}":{"parameters":[{"$ref":"#/components/parameters/usr"}],"get":{"tags":["Partners"],"operationId":"getPartnerMember","summary":"Member state — build progress, wallet, and a fresh login link (master key)","x-uirix-scope":"users:manage (master key only)","responses":{"200":{"description":"Member","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerMember"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/users/{usr}/login-links":{"parameters":[{"$ref":"#/components/parameters/usr"}],"post":{"tags":["Users"],"operationId":"createLoginLink","summary":"One-time dashboard login link for the user (master key)","description":"Single use, expires in minutes (`PUBLIC_API_LOGIN_LINK_TTL_MINUTES`, default 10).\n`agent_id` lands on that agent's widget code page; `next` any in-app path; absolute\nURLs are ignored. Exchanged by the dashboard for a 15-minute session; the phone\nverification step is skipped for these links.\n","x-uirix-scope":"users:manage (master key only)","requestBody":{"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"agent_id":{"type":"string","pattern":"^agt_\\d+$"},"next":{"type":"string","maxLength":500,"description":"In-app path, e.g. /dashboard"}}}}}},"responses":{"201":{"description":"Link","content":{"application/json":{"schema":{"type":"object","required":["user_id","login_url","expires_at","lands_on"],"properties":{"user_id":{"type":"string"},"login_url":{"type":"string","format":"uri"},"expires_at":{"type":"string","format":"date-time"},"lands_on":{"type":"string"},"meta":{"$ref":"#/components/schemas/Meta"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/partners/members/{usr}/convert":{"parameters":[{"$ref":"#/components/parameters/usr"}],"post":{"tags":["Partners"],"operationId":"convertPartnerMember","summary":"Make an account that already exists a partner member (master key)","description":"Moves the subscription to pay-as-you-go with `payment_gateway` and `next_charge_date`\ncleared (the monthly card job never charges it) and `plan_expires_at` 2036, stamps the\npartner marker on `external_ids`, credits the wallet when asked, and provisions the\nwidget of the account's agent from `website_url`. Answers the member object, login link\nincluded. Converting twice does not credit twice unless asked again.\n","x-uirix-scope":"users:manage (master key only)","requestBody":{"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"wallet_credit_usd":{"type":"number","minimum":0,"maximum":100000},"partner":{"type":"string","maxLength":100},"external_ids":{"type":"object","additionalProperties":{"type":"string","maxLength":400}},"website_url":{"type":"string","maxLength":2048,"description":"Provisions the widget from this site"},"agent_id":{"type":"string","pattern":"^agt_d+$"},"widget_enabled":{"type":"boolean","default":true}}}}}},"responses":{"200":{"description":"Member","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerMember"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/agents/{agt}/kb/documents/{doc}":{"patch":{"operationId":"patchKbDocument","summary":"Flag a file or text document \"keep as is\" (not summarised; full text in the prompt)","tags":["Knowledge"],"security":[{"bearerAuth":[]}],"parameters":[{"name":"agt","in":"path","required":true,"schema":{"type":"string"}},{"name":"doc","in":"path","required":true,"schema":{"type":"string","examples":["doc_f101"]}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/KbDocumentVerbatimPatch"}}}},"responses":{"200":{"description":"The document object","content":{"application/json":{"schema":{"$ref":"#/components/schemas/KbDocument"}}}},"400":{"description":"validation_error — a crawled page (doc_c…) cannot be kept as written"},"404":{"description":"document_not_found"}}}},"/conversations/import":{"post":{"tags":["Conversations"],"operationId":"importConversation","summary":"Import a finished external call into the call log","description":"Writes a phone call that happened **outside UiriX** — your own voice stack, a human\nagent — into the call log of one of your agents. Two effects: when that number calls\nthe agent, the call is part of the returning-call context the agent loads (the last\nthree calls with the number in 30 days, matched on `phone_e164` for the inbound agent,\nnewest first; an imported summary is carried whole, up to 600 characters); and\n`GET /conversations` lists it like any other call, with\n`channel_detail.origin = \"import\"`.\n\nNothing runs: no agent, no model, no summariser when you send a `summary`, no\n`conversation.started` / `conversation.ended` webhook, no billing and no usage\n(`GET /usage` does not count it). `agent_version`, `prompt_version` and `model` are\nnull — the agent never ran. The identity block carries what you send plus\n`custom.external_session_id`.\n\n**Idempotent** on `external_session_id` per account: a repeat answers\n`409 import_exists` with `error.conversation_id` naming the existing conversation —\nor, with `upsert: true`, replaces the record in place and answers `200`.\n\n**What the agent reads:** the first 600 characters of the summary (the outcome in\nfront) in both runtime blocks. A longer summary is stored and returned whole and the\nresponse carries `warnings: [{ code: \"summary_truncated_for_agent\", message }]`.\n","x-uirix-scope":"conversations:write","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConversationImportRequest"}}}},"responses":{"200":{"description":"upsert: true replaced an earlier import with the same external_session_id — the same object, same conversation_id.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConversationImportResponse"}}}},"201":{"description":"The imported conversation — the same object as GET /conversations/{conv} (transcript, summary, identity, metrics) plus warnings.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConversationImportResponse"}}}},"400":{"description":"validation_error (a required field, turns[i].at, neither summary nor turns), invalid_phone, invalid_date, invalid_date_range, unsupported_field.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"agent_not_found — no such agent on the credential's accounts.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"import_exists — this external_session_id was already imported for the account; error.conversation_id names it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"410":{"description":"conversation_deleted — the earlier import with this external_session_id was deleted; it cannot be replaced.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/voices/{voice_id}/sample":{"get":{"tags":["Voices"],"operationId":"getVoiceSample","summary":"Hear a voice — the CDN URL of its English MP3 sample (or the bytes)","description":"Answers the CDN URL of the voice's sample (the voice saying its marketing line in English,\n5–8 s). Rendered on the first request (a few seconds, one render at a time per voice) and\nserved from the CDN afterwards. `GET /voices` already returns the same URL as\n`sample_url`; use this route when that field is null or when you only have the id.\n`download=true` streams the MP3 itself (audio/mpeg). Browsers should play `sample_url`\nfrom the CDN directly — it needs no key.\n","x-uirix-scope":"agents:read","parameters":[{"name":"voice_id","in":"path","required":true,"schema":{"type":"string"},"example":"kore"},{"name":"provider","in":"query","required":false,"schema":{"type":"string","enum":["openai","gemini"]},"description":"Only needed if two catalogues ever share a voice id."},{"name":"download","in":"query","required":false,"schema":{"type":"string","enum":["true","false"]},"description":"true streams the MP3 bytes (audio/mpeg) instead of the JSON."}],"responses":{"200":{"description":"The sample's CDN URL — or, with download=true, the MP3 itself.","content":{"application/json":{"schema":{"type":"object","required":["voice_id","provider","name","language","sample_url","rendered_now","bytes"],"properties":{"voice_id":{"type":"string","example":"kore"},"provider":{"type":"string","enum":["openai","gemini"]},"name":{"type":"string","example":"Kore"},"language":{"type":"string","example":"en"},"sample_url":{"type":"string","format":"uri","example":"https://cdn.uirix.com/shared/samples/Kore.en.mp3?v=1"},"rendered_now":{"type":"boolean","description":"true when this request rendered the file."},"bytes":{"type":"integer"},"meta":{"$ref":"#/components/schemas/Meta"}}}},"audio/mpeg":{"schema":{"type":"string","format":"binary"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"not_found — no such voice (field voice_id), or the provider has no samples.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/agents/{agt}/demo":{"parameters":[{"$ref":"#/components/parameters/agt"}],"get":{"tags":["Agents"],"operationId":"getAgentDemo","summary":"The agent demo page — the customer's site with the widget open, no login","description":"A link a customer opens (no login) to see a screenshot of their own home page with the\nagent's real widget open on top — chat and voice working. Stable for the agent until the\nwidget token is regenerated. Provisions the widget (token + the site's domain) when the\nagent has none, and renders a missing screenshot (a few seconds) on the first call.\n","x-uirix-scope":"agents:read","responses":{"200":{"description":"The demo link and what it renders.","content":{"application/json":{"schema":{"type":"object","required":["agent_id","demo_url","ready","build_status","website_url","screenshot_url","widget"],"properties":{"agent_id":{"type":"string","example":"agt_3702"},"demo_url":{"type":"string","format":"uri"},"ready":{"type":"boolean","description":"true once the build finished."},"build_status":{"type":["string","null"]},"website_url":{"type":["string","null"],"format":"uri"},"screenshot_url":{"type":["string","null"],"format":"uri","description":"The backdrop; null when the site could not be captured."},"widget":{"type":"object","required":["public_token","enabled","allowed_domains"],"properties":{"public_token":{"type":"string"},"enabled":{"type":"boolean"},"allowed_domains":{"type":"array","items":{"type":"string"}}}},"meta":{"$ref":"#/components/schemas/Meta"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"agent_not_found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/admin/overview":{"get":{"tags":["Admin"],"operationId":"adminOverview","summary":"Platform KPIs in one call (master key)","description":"Customers, agents (by provider / language), conversations 24h / 7d / 30d by\nchannel, cost and billed 7d / 30d, revenue 30d, wallet total, the five accounts\nwith the highest bill, open items (outbox, failed deliveries, stale summaries)\nand a 2-second health probe of the four processes. Each aggregate is bounded by\na 10-second deadline; one that times out is `null` and named in `problems`.\n","x-uirix-scope":"master","responses":{"200":{"description":"The overview","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminOverview"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/TooManyRequests"}}}},"/admin/customers":{"get":{"tags":["Admin"],"operationId":"adminListCustomers","summary":"Search customers across the platform (master key)","description":"`q` is a case-insensitive *contains* over `users.email`, `name`, `company_name`\nand `external_ids.equalweb_customer_id` (`usr_17` / `acc_17` / `17` also match the\nid). Keyset cursor over `(created_at, id)`, newest first, bound to the filter set.\n","x-uirix-scope":"master","parameters":[{"name":"q","in":"query","schema":{"type":"string","maxLength":200}},{"name":"plan","in":"query","description":"Plan slug (`starter`, `payg`, `guest` …) or a numeric plans.id","schema":{"type":"string"}},{"name":"status","in":"query","schema":{"$ref":"#/components/schemas/AccountStatus"}},{"name":"partner","in":"query","description":"A partner name, or `any` / `none`","schema":{"type":"string"}},{"$ref":"#/components/parameters/limit"},{"$ref":"#/components/parameters/cursor"}],"responses":{"200":{"description":"Customers","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ListEnvelope"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminCustomerRow"}}}}]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}}}},"/admin/customers/{usr}/overview":{"parameters":[{"$ref":"#/components/parameters/usr"}],"get":{"tags":["Admin"],"operationId":"adminCustomerOverview","summary":"The customer dossier (master key)","x-uirix-scope":"master","responses":{"200":{"description":"The dossier","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminCustomerOverview"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/admin/customers/{usr}/wallet/history":{"parameters":[{"$ref":"#/components/parameters/usr"}],"get":{"tags":["Admin"],"operationId":"adminWalletHistory","summary":"The customer's wallet history, newest first (master key)","x-uirix-scope":"master","parameters":[{"$ref":"#/components/parameters/limit"},{"$ref":"#/components/parameters/cursor"}],"responses":{"200":{"description":"Wallet history","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ListEnvelope"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminWalletHistoryEntry"}}}}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/admin/customers/{usr}/wallet/credits":{"parameters":[{"$ref":"#/components/parameters/usr"}],"post":{"tags":["Admin"],"operationId":"adminCreditWallet","summary":"Credit the customer's wallet — idempotent, journaled (master key)","description":"One transaction: `users.wallet_balance += amount_usd`, a `wallet_history` row\n(`type: topup`, description `Admin credit - master: <reason>`) and the journal\nrow. `amount_usd` is 0.01 – 1,000 (`PUBLIC_API_ADMIN_MAX_CREDIT_USD` overrides\nthe cap); never negative. A replay of the same `idempotency_key` for the same\ncustomer answers the original result with `replayed: true` (200, not 201).\n","x-uirix-scope":"master","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminWalletCreditRequest"},"example":{"amount_usd":25,"reason":"Goodwill after the 29 Sept outage (ticket 4412)","idempotency_key":"ticket-4412-goodwill","confirm":true}}}},"responses":{"200":{"description":"Replayed — the same idempotency key was already applied; the original numbers, `replayed: true`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminWalletCreditResult"}}}},"201":{"description":"Credited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminWalletCreditResult"}}}},"400":{"description":"`confirmation_required` (field `confirm`) · `validation_error` (`amount_usd`, `reason`)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"`idempotency_key_reused` — the key was used for another action or customer","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/admin/customers/{usr}/plan":{"parameters":[{"$ref":"#/components/parameters/usr"}],"post":{"tags":["Admin"],"operationId":"adminChangePlan","summary":"Move the customer to another plan (master key)","description":"The partner convert flow for any plan: the newest `subscriptions` row gets\n`plan_id`, `status: active` and a reset date one month out; `users.plan_expires_at`\nbecomes 2036 for pay-as-you-go and one month out otherwise. Pay-as-you-go also\nclears the Cardcom fields (the only path that touches them). A paid plan that is\nneither pay-as-you-go nor a trial needs a `payment_gateway` on the subscription,\nelse `409 card_required`.\n","x-uirix-scope":"master","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminPlanChangeRequest"},"example":{"plan_id":11,"reason":"Customer asked for pay-as-you-go on 30 Sept","confirm":true}}}},"responses":{"200":{"description":"The updated user + `previous_plan_id`, `plan_id`, `subscription` (after-state), `action_id`","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/User"},{"type":"object","properties":{"previous_plan_id":{"type":["integer","null"]},"plan_id":{"type":"integer"},"subscription":{"type":"object","additionalProperties":true},"action_id":{"type":"string","example":"adm_17"}}}]}}}},"400":{"description":"`confirmation_required` · `validation_error` (plan not active)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"`not_found` (user) · `plan_not_found` (field `plan_id`)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"`card_required` — paid plan, no payment method on file (`plan_id`, `plan_price` in the error)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/admin/customers/{usr}/status":{"parameters":[{"$ref":"#/components/parameters/usr"}],"patch":{"tags":["Admin"],"operationId":"adminSetCustomerStatus","summary":"Suspend or reactivate a customer (master key)","description":"Writes `users.status` (`Active` / `Suspended`) and nothing else; `subscriptions.status` gates billing and is left alone. `trial` is derived from the plan and cannot be set.","x-uirix-scope":"master","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminStatusRequest"},"example":{"status":"suspended","reason":"Two failed charges, no answer to billing e-mails","confirm":true}}}},"responses":{"200":{"description":"The updated user + `previous_status`, `action_id`","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/User"},{"type":"object","properties":{"previous_status":{"$ref":"#/components/schemas/AccountStatus"},"action_id":{"type":"string"}}}]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/admin/customers/{usr}/login-links":{"parameters":[{"$ref":"#/components/parameters/usr"}],"post":{"tags":["Admin"],"operationId":"adminCreateLoginLink","summary":"A one-time dashboard login link for the customer, journaled (master key)","description":"Same mechanics as `POST /users/{usr}/login-links`. The journal stores where the link lands and when it expires — never the link, which carries a one-time token.","x-uirix-scope":"master","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminLoginLinkRequest"},"example":{"agent_id":"agt_245","reason":"Support session, ticket 4412","confirm":true}}}},"responses":{"201":{"description":"The link","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminLoginLink"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"`not_found` (user) · `agent_not_found` (the agent is not the customer's)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/admin/agents":{"get":{"tags":["Admin"],"operationId":"adminListAgents","summary":"Every agent across every account, with cost_30d (master key)","x-uirix-scope":"master","parameters":[{"name":"q","in":"query","description":"Contains over agent name / display name / owner e-mail / company; `agt_245` or `245` match the id","schema":{"type":"string","maxLength":200}},{"name":"provider","in":"query","schema":{"type":"string","enum":["openai","gemini","gpt_live"]}},{"name":"language","in":"query","description":"`agent_language` code","schema":{"type":"string"}},{"name":"tier","in":"query","schema":{"type":"string","enum":["standard","large"]}},{"name":"enabled","in":"query","description":"`true` = config_status complete","schema":{"type":"string","enum":["true","false"]}},{"name":"has_phone","in":"query","schema":{"type":"string","enum":["true","false"]}},{"name":"account_id","in":"query","schema":{"type":"string","example":"acc_17"}},{"$ref":"#/components/parameters/limit"},{"$ref":"#/components/parameters/cursor"}],"responses":{"200":{"description":"Agents, keyset over `(updated_at, id)` newest first","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ListEnvelope"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminAgentRow"}}}}]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}}}},"/admin/agents/{agt}/actions":{"parameters":[{"$ref":"#/components/parameters/agt"}],"post":{"tags":["Admin"],"operationId":"adminAgentAction","summary":"Operate on an agent of any account (master key)","description":"A thin wrapper over the services the dashboard and `PATCH /agents/{agt}` run\n(`updateAgentFields`, the widget row, the knowledge reindex), so validation lives\nthere. `set_provider` (openai / gemini / gpt_live), `set_tier` (standard / large),\n`set_language` (one of the 17 codes), `set_voice` (a voice id), `widget_enable`,\n`widget_disable`, `reindex_kb` (202; the customer's daily build quota is not consumed).\n","x-uirix-scope":"master","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminAgentActionRequest"},"example":{"action":"set_tier","value":"large","reason":"Hebrew answers too short on standard","confirm":true}}}},"responses":{"200":{"description":"Done — `before` / `after` snapshots of the agent","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminAgentActionResult"}}}},"202":{"description":"`reindex_kb` — the rebuild was started","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminAgentActionResult"}}}},"400":{"description":"`confirmation_required` · `validation_error` (`action`, `value`; a voice that does not exist for the provider)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/admin/credentials/{key}":{"parameters":[{"$ref":"#/components/parameters/key"}],"patch":{"tags":["Admin"],"operationId":"adminPatchCredential","summary":"Edit a personal key — name, scopes, expiry, IP allow-list (master key)","description":"The dashboard's key editor with `name` and `ip_allowlist` added. Master-only scopes are `400 invalid_scope`; a revoked key is `400 validation_error`. The secret is untouched and narrowing is immediate.","x-uirix-scope":"master","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminCredentialPatch"},"example":{"scopes":["conversations:read","summaries:read"],"expires_in_days":30,"reason":"Contractor off-boarding","confirm":true}}}},"responses":{"200":{"description":"The key (prefix and metadata, never the secret) + `before` + `action_id`","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ApiKey"},{"type":"object","properties":{"before":{"$ref":"#/components/schemas/ApiKey"},"action_id":{"type":"string"}}}]}}}},"400":{"description":"`confirmation_required` · `invalid_scope` · `validation_error` · `empty_update`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/admin/system/status":{"get":{"tags":["Admin"],"operationId":"adminSystemStatus","summary":"Services, workers, queues, config problems, db latency (master key)","x-uirix-scope":"master","responses":{"200":{"description":"The status","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminSystemStatus"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}}}},"/admin/system/errors":{"get":{"tags":["Admin"],"operationId":"adminSystemErrors","summary":"Error lines from the newest log file of one service, newest first (master key)","description":"Reads the newest log file of the service under the log root (an allow-list of four folders), scans its last 2 MB, keeps lines containing `error`, caps at `limit` lines / 256 KB, scrubs secrets. Read-only.","x-uirix-scope":"master","parameters":[{"name":"service","in":"query","required":true,"schema":{"type":"string","enum":["dashboard","admin","twilio","outbound"]}},{"name":"minutes","in":"query","description":"Window, default 60, max 10080","schema":{"type":"integer","minimum":1,"maximum":10080}},{"name":"limit","in":"query","description":"Default 200, max 500","schema":{"type":"integer","minimum":1,"maximum":500}}],"responses":{"200":{"description":"The tail","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminErrorTail"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}}}},"/admin/config":{"get":{"tags":["Admin"],"operationId":"adminConfig","summary":"Flags, models per section, pricing, allow-listed env, versions — never a secret (master key)","x-uirix-scope":"master","responses":{"200":{"description":"The snapshot","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminConfigSnapshot"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}}}},"/admin/config/ai-models":{"patch":{"tags":["Admin"],"operationId":"adminSetAiModel","summary":"Change the model of one section of ai-config.json / ai-config-large.json (master key)","description":"Backs the file up next to itself (`ai-config.json.bak-<stamp>`), rewrites it, reloads the dashboard backend's config in place, journals the change. The other processes read the file at start-up — `restart_required` names them. Feature flags are not writable here.","x-uirix-scope":"master","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminAiModelPatch"},"example":{"tier":"large","section":"summary","model":"gpt-5.6-terra","reason":"D-4 ruling","confirm":true}}}},"responses":{"200":{"description":"Changed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminAiModelResult"}}}},"400":{"description":"`confirmation_required` · `validation_error` (`section` unknown or without a model, `model` not priced, `reasoningEffort` on a section without it)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}}}},"/admin/config/reload":{"post":{"tags":["Admin"],"operationId":"adminReloadConfig","summary":"Drop the dashboard backend's config cache after a manual edit (master key)","x-uirix-scope":"master","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminConfirmRequest"},"example":{"reason":"Edited model-pricing.json by hand","confirm":true}}}},"responses":{"200":{"description":"Reloaded","content":{"application/json":{"schema":{"type":"object","properties":{"reloaded":{"type":"array","items":{"type":"string"},"example":["dashboard:3000"]},"restart_required":{"type":"array","items":{"type":"string"},"example":["twilio:3002","outbound:3003","admin:3001"]},"reloaded_at":{"type":"string","format":"date-time"},"action_id":{"type":"string"},"meta":{"$ref":"#/components/schemas/Meta"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}}}},"/admin/actions":{"get":{"tags":["Admin"],"operationId":"adminListActions","summary":"The journal of operator writes (master key)","x-uirix-scope":"master","parameters":[{"name":"from","in":"query","schema":{"type":"string","format":"date-time"}},{"name":"to","in":"query","schema":{"type":"string","format":"date-time"}},{"name":"action","in":"query","schema":{"type":"string"},"example":"wallet_credit"},{"name":"target_type","in":"query","schema":{"type":"string","enum":["user","agent","credential","config"]}},{"name":"target_id","in":"query","description":"`usr_17` / `acc_17` / `agt_245` / `key_42` (sets the type too) or a bare integer","schema":{"type":"string"}},{"$ref":"#/components/parameters/limit"},{"$ref":"#/components/parameters/cursor"}],"responses":{"200":{"description":"Journal rows, newest first","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ListEnvelope"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AdminAction"}}}}]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}}}},"/admin/migrations":{"get":{"tags":["Admin"],"operationId":"adminMigrations","summary":"Both migration ledgers vs. the files on disk (master key)","x-uirix-scope":"master","responses":{"200":{"description":"The report","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminMigrations"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}}}},"/agents/{agt}/connectors":{"parameters":[{"$ref":"#/components/parameters/agt"}],"get":{"tags":["Agents"],"operationId":"listAgentConnectors","summary":"The connectors an agent has, and whether they are connected","description":"Google Calendar, Microsoft 365 Calendar, HubSpot and custom HTTP-API connectors of\nthe agent. Read-only: connecting is a dashboard action (agent → Connectors).\n\nUse it before granting a capability whose `required_connector` names a type —\n`calendars_connected` is `true` when at least one `google-calendar` or\n`microsoft-365-calendar` connector is `active`.\n\nNEVER returned: access or refresh tokens, API keys, OAuth state, base URLs,\ndiscovered endpoints, or the raw configuration. For the two calendar types only the\nselected calendar's `id` and `name` are read out of it, and a value that looks like an\ne-mail address is left out.\n\nAn agent of another account is `404 agent_not_found`, never `403`.\n","x-uirix-scope":"agents:read","responses":{"200":{"description":"The agent's connectors (an agent with none answers an empty `data`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentConnectorList"},"example":{"data":[{"connector_id":"con_41","type":"google-calendar","name":"Google Calendar","category":"calendar","status":"active","connected":true,"calendar":{"id":"primary","name":"Sales calendar"},"connected_at":"2026-09-12T08:41:07.000Z","last_sync":null}],"calendars_connected":true,"meta":{"request_id":"req_01J8ZB3E4F","environment":"production","agent_id":"agt_245"}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"`agent_not_found` — unknown agent, or one belonging to another account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/agents/{agt}/lists":{"parameters":[{"$ref":"#/components/parameters/agt"}],"post":{"tags":["Campaigns"],"operationId":"createContactList","summary":"Create an empty contact list","description":"A list belongs to one agent. Its name is unique per agent (case-insensitive,\n`409 list_name_exists`). Fill it with `POST /lists/{lst}/import`. There are no\nupdate or delete routes.\n\nRequires `calls:write`. A sandbox key is refused (`sandbox_unsupported`).\n","x-uirix-scope":"calls:write","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactListCreate"}}}},"responses":{"201":{"description":"The empty list.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactList"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"`agent_not_found` — unknown agent, or one belonging to another account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"`list_name_exists`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}},"get":{"tags":["Campaigns"],"operationId":"listContactLists","summary":"List an agent's contact lists","description":"Newest first, keyset paged. Requires `calls:read`.","x-uirix-scope":"calls:read","parameters":[{"$ref":"#/components/parameters/limit"},{"$ref":"#/components/parameters/cursor"}],"responses":{"200":{"description":"A page of lists.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ListEnvelope"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/ContactList"}}}}]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"`agent_not_found`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/lists/{lst}":{"parameters":[{"$ref":"#/components/parameters/lst"}],"get":{"tags":["Campaigns"],"operationId":"getContactList","summary":"Read one contact list","description":"The list with its size and how its contacts are spread over time zones\n(`timezones`; a contact without a stored zone is counted under `unknown`).\nRequires `calls:read`.\n","x-uirix-scope":"calls:read","responses":{"200":{"description":"The list.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactList"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"`list_not_found` — unknown list, or one belonging to another account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/lists/{lst}/import":{"parameters":[{"$ref":"#/components/parameters/lst"}],"post":{"tags":["Campaigns"],"operationId":"importContacts","summary":"Append contacts to a list","description":"Appends up to **1,000 rows** per request; a list holds at most **10,000** contacts\n(`409 list_limit_exceeded`, nothing imported). Each row has a required `phone`, the\noptional `name`, `first_name`, `last_name`, `external_reference`, `timezone` (IANA), and\nany other key, which is kept as `extra` and given to the agent as a fact about the contact.\n\nA phone number is normalised to E.164. Without a `+` prefix it is read as a national\nnumber of `default_country`, or, failing that, as an international number that lost its `+`.\nNumbers already in the list or repeated in the request are `duplicates`. Numbers on the\naccount's do-not-call list are skipped (`dnc_skipped`). A row that cannot be used is listed in\n`invalid` with its 1-based row number and the reason; it never aborts the batch.\n\nEvery contact gets a time zone: the row's `timezone`, else the one the number implies (NANP\narea code, then country, daylight saving time included), else the `default_country` zone, else\nthe account zone. Contacts that fell to the last two are counted under `unknown`.\n\nRequires `calls:write`. A sandbox key is refused.\n","x-uirix-scope":"calls:write","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactImportRequest"},"example":{"default_country":"IL","rows":[{"phone":"052-1234567","name":"Dana Levi","external_reference":"crm-1041","city":"Haifa"},{"phone":"+12125550147","first_name":"John","last_name":"Smith","timezone":"America/New_York"}]}}}},"responses":{"200":{"description":"The verdict per row, in totals.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactImportResult"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"`list_not_found`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"`list_limit_exceeded` — the list would pass 10,000 contacts; nothing was imported.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/lists/{lst}/items":{"parameters":[{"$ref":"#/components/parameters/lst"}],"get":{"tags":["Campaigns"],"operationId":"listContactItems","summary":"List the contacts of a list","description":"Oldest first (import order), keyset paged. Requires `calls:read`.","x-uirix-scope":"calls:read","parameters":[{"$ref":"#/components/parameters/limit"},{"$ref":"#/components/parameters/cursor"}],"responses":{"200":{"description":"A page of contacts.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ListEnvelope"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/ContactListItem"}}}}]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"`list_not_found`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/campaigns":{"post":{"tags":["Campaigns"],"operationId":"createCampaign","summary":"Create a campaign (a draft — dials nothing)","description":"Snapshots the list into the dialer's queue — one row per contact, do-not-call numbers left out —\nand stores the schedule. The campaign is a `draft` until `POST /campaigns/{cmp}/start`.\nContacts added to the list afterwards join a campaign that is running, paused or completed,\nbut not a draft: import before you create.\n\nThe agent must own an active phone number (`409 agent_has_no_phone_number`), the list must be\nthis agent's, the profile must be active and this agent's, and the plan must allow outbound\ncampaigns (`403 plan_limit_reached`).\n\nRate class `calls` (20/min). Requires `calls:write`. A sandbox key is refused.\n","x-uirix-scope":"calls:write","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CampaignCreate"},"example":{"agent_id":"agt_301","list_id":"lst_88","profile_id":"prf_12","name":"October renewal reminders","days":[0,1,2,3,4],"window":{"from":"10:00","to":"17:00"},"max_calls_per_hour":40}}}},"responses":{"201":{"description":"The draft campaign.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CampaignCreated"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"`insufficient_scope` · `forbidden_account` · `sandbox_unsupported` · `plan_limit_reached` (Guest plan, or the monthly outbound quota is used up).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"`agent_not_found` · `list_not_found` · `profile_not_found` — unknown, or another account's.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"`agent_has_no_phone_number`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}},"get":{"tags":["Campaigns"],"operationId":"listCampaigns","summary":"List campaigns","description":"Newest first, keyset paged, each with its `progress`. Archived campaigns are left out unless\n`status=archived`. Requires `calls:read`.\n","x-uirix-scope":"calls:read","parameters":[{"name":"agent_id","in":"query","required":false,"schema":{"type":"string"}},{"name":"status","in":"query","required":false,"schema":{"$ref":"#/components/schemas/CampaignStatus"}},{"$ref":"#/components/parameters/limit"},{"$ref":"#/components/parameters/cursor"}],"responses":{"200":{"description":"A page of campaigns.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ListEnvelope"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Campaign"}}}}]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"`agent_not_found` (the `agent_id` filter).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/campaigns/{cmp}":{"parameters":[{"$ref":"#/components/parameters/cmp"}],"get":{"tags":["Campaigns"],"operationId":"getCampaign","summary":"Read a campaign — settings, progress and report","description":"`progress` counts the campaign's contacts by state right now; `report` carries the same\nfigures as the dashboard's campaign report (outcomes, average duration, cost in USD).\nRequires `calls:read`.\n","x-uirix-scope":"calls:read","responses":{"200":{"description":"The campaign.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Campaign"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"`campaign_not_found`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/campaigns/{cmp}/calls":{"parameters":[{"$ref":"#/components/parameters/cmp"}],"get":{"tags":["Campaigns"],"operationId":"listCampaignCalls","summary":"The contacts of a campaign and what happened to each","description":"One row per contact, in queue order, keyset paged. `conversation_id` (`conv_u…`) is present once the\ncall has a transcript; read it with `GET /conversations/{conv}`. Requires `calls:read`.\n","x-uirix-scope":"calls:read","parameters":[{"name":"status","in":"query","required":false,"schema":{"$ref":"#/components/schemas/CampaignCallStatus"}},{"$ref":"#/components/parameters/limit"},{"$ref":"#/components/parameters/cursor"}],"responses":{"200":{"description":"A page of rows.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ListEnvelope"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/CampaignCall"}}}}]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"`campaign_not_found`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/campaigns/{cmp}/start":{"parameters":[{"$ref":"#/components/parameters/cmp"}],"post":{"tags":["Campaigns"],"operationId":"startCampaign","summary":"Start (or resume) a campaign — this places real calls","description":"Needs `\"confirmed\": true` in the body (`400 confirmation_required` otherwise): it calls real people and\nspends money. Allowed from `draft` and `paused`; asking for what already is answers 200 with\n`changed: false` and touches nothing. Any other state is `409 campaign_state_conflict`.\n\nBefore the dialer is told, the plan and the agent's phone number are checked again (`403\nplan_limit_reached`, `409 agent_has_no_phone_number`), the account balance gate applies\n(`402 payment_required`), the `start_date` / `end_date` are honoured (`409`), and contacts that have\ngone on the do-not-call list since the campaign was created are cancelled (`dnc_cancelled`).\nA dialer that does not answer is `503 dialer_unavailable` and the campaign is unchanged.\n\nCalls go out only inside the campaign's `days` and `window`, in each contact's own time zone.\nRate class `calls`. Requires `calls:write`. A sandbox key is refused.\n","x-uirix-scope":"calls:write","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["confirmed"],"properties":{"account_id":{"type":"string"},"confirmed":{"type":"boolean","const":true}}}}}},"responses":{"200":{"description":"The campaign status after the command.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CampaignActionResult"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"description":"`payment_required` — the account balance or subscription does not allow placing calls.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"`campaign_not_found`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"`campaign_state_conflict` · `agent_has_no_phone_number`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"description":"`dialer_unavailable`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/campaigns/{cmp}/pause":{"parameters":[{"$ref":"#/components/parameters/cmp"}],"post":{"tags":["Campaigns"],"operationId":"pauseCampaign","summary":"Pause a running campaign","description":"Allowed from `running`. Contacts already waiting stay queued; resume with `start`.\nRequires `calls:write`. A sandbox key is refused.\n","x-uirix-scope":"calls:write","requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"account_id":{"type":"string"}}}}}},"responses":{"200":{"description":"The campaign status after the command.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CampaignActionResult"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"`campaign_not_found`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"`campaign_state_conflict`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"description":"`dialer_unavailable`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/campaigns/{cmp}/stop":{"parameters":[{"$ref":"#/components/parameters/cmp"}],"post":{"tags":["Campaigns"],"operationId":"stopCampaign","summary":"Stop a campaign for good","description":"Allowed from `draft`, `running` and `paused`. A stopped campaign cannot be started again — create a\nnew one. Calls in progress are ended. Requires `calls:write`. A sandbox key is refused.\n","x-uirix-scope":"calls:write","requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"account_id":{"type":"string"}}}}}},"responses":{"200":{"description":"The campaign status after the command.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CampaignActionResult"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"`campaign_not_found`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"`campaign_state_conflict`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"description":"`dialer_unavailable`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}}},"webhooks":{"conversation.started":{"post":{"operationId":"webhookConversationStarted","summary":"First user turn","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookPayload"}}}},"responses":{"200":{"description":"Acknowledged (any 2xx within 10 s)"}}}},"conversation.ended":{"post":{"operationId":"webhookConversationEnded","summary":"Conversation completed / abandoned / failed — full object with transcript","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookPayload"}}}},"responses":{"200":{"description":"Acknowledged"}}}},"conversation.escalated":{"post":{"operationId":"webhookConversationEscalated","summary":"Handoff to a human","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookPayload"}}}},"responses":{"200":{"description":"Acknowledged"}}}},"summary.ready":{"post":{"operationId":"webhookSummaryReady","summary":"Summary finished (ready or failed)","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookPayload"}}}},"responses":{"200":{"description":"Acknowledged"}}}},"conversation.deleted":{"post":{"operationId":"webhookConversationDeleted","summary":"Conversation erased (user request / retention / account closed)","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookPayload"}}}},"responses":{"200":{"description":"Acknowledged"}}}},"call.completed":{"post":{"operationId":"webhookCallCompleted","summary":"Outbound call completed","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookPayload"}}}},"responses":{"200":{"description":"Acknowledged"}}}},"call.failed":{"post":{"operationId":"webhookCallFailed","summary":"Outbound call failed","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookPayload"}}}},"responses":{"200":{"description":"Acknowledged"}}}},"call.no_answer":{"post":{"operationId":"webhookCallNoAnswer","summary":"Outbound call not answered after the last attempt","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookPayload"}}}},"responses":{"200":{"description":"Acknowledged"}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"uirix_mk_live_… | uirix_sk_live_… | uirix_sk_test_…","description":"Master key or personal live key. Scopes per operation are in `x-uirix-scope`."}},"headers":{"X-Request-Id":{"description":"Echoed on every response; quote it in support requests.","schema":{"type":"string","pattern":"^req_[0-9A-HJKMNP-TV-Z]{26}$"}},"X-RateLimit-Limit":{"schema":{"type":"integer"}},"X-RateLimit-Remaining":{"schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Unix seconds","schema":{"type":"integer"}},"Retry-After":{"description":"Seconds (429 only)","schema":{"type":"integer"}},"X-Uirix-Key-Expires-In":{"description":"Present when the key expires within 30 days, e.g. `21d`","schema":{"type":"string"}}},"parameters":{"limit":{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},"cursor":{"name":"cursor","in":"query","description":"Opaque cursor from `pagination.next_cursor`; valid only with the same filters.","schema":{"type":"string"}},"usr":{"name":"usr","in":"path","required":true,"schema":{"type":"string","pattern":"^usr_"},"example":"usr_17"},"key":{"name":"key","in":"path","required":true,"schema":{"type":"string","pattern":"^key_"},"example":"key_42"},"acc":{"name":"acc","in":"path","required":true,"schema":{"type":"string","pattern":"^acc_"},"example":"acc_17"},"agt":{"name":"agt","in":"path","required":true,"schema":{"type":"string","examples":["agt_245"]}},"pv":{"name":"pv","in":"path","required":true,"schema":{"type":"string","pattern":"^pv_"},"example":"pv_12"},"doc":{"name":"doc","in":"path","required":true,"schema":{"type":"string","pattern":"^doc_[fc]"},"example":"doc_f101"},"conv":{"name":"conv","in":"path","required":true,"schema":{"type":"string","pattern":"^conv_[pud]"},"example":"conv_p9876"},"call":{"name":"call","in":"path","required":true,"schema":{"type":"string","pattern":"^call_"},"example":"call_88"},"dnc":{"name":"dnc","in":"path","required":true,"schema":{"type":"string","pattern":"^dnc_"},"example":"dnc_5"},"whk":{"name":"whk","in":"path","required":true,"schema":{"type":"string","pattern":"^whk_"},"example":"whk_7"},"dlv":{"name":"dlv","in":"path","required":true,"schema":{"type":"string","pattern":"^dlv_"},"example":"dlv_01J8Z9M4T7QX3R"},"accountIdQuery":{"name":"account_id","in":"query","required":false,"schema":{"oneOf":[{"type":"string","pattern":"^(acc|usr)_[0-9]+$"},{"type":"array","items":{"type":"string"}}]},"description":"Repeatable. Defaults to the credential's accounts. A master key must name exactly one account here or in `X-Uirix-Account`."},"prf":{"name":"prf","in":"path","required":true,"description":"Outbound profile id, `prf_{n}`. Opaque — do not parse it.","schema":{"type":"string","pattern":"^prf_[1-9][0-9]*$"},"example":"prf_42"},"lst":{"name":"lst","in":"path","required":true,"description":"A contact list id, `lst_{n}`.","schema":{"type":"string","pattern":"^lst_[1-9][0-9]{0,9}$"}},"cmp":{"name":"cmp","in":"path","required":true,"description":"A campaign id, `cmp_{n}`.","schema":{"type":"string","pattern":"^cmp_[1-9][0-9]{0,9}$"}}},"responses":{"BadRequest":{"description":"Validation error","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"},"example":{"error":{"code":"invalid_date_range","message":"'from' must be earlier than 'to'.","field":"from","request_id":"req_01J8Z9K2QW"},"meta":{"request_id":"req_01J8Z9K2QW","environment":"production"}}}}},"Unauthorized":{"description":"`missing_credentials` · `invalid_credentials` · `expired_token`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"PaymentRequired":{"description":"`payment_required` — subscription inactive or balance exhausted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Forbidden":{"description":"forbidden_account / insufficient_scope / ip_not_allowed / dnc_blocked / plan_limit_reached / signature_invalid / url_expired","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"},"example":{"error":{"code":"forbidden_account","message":"Account acc_99 is not on this credential.","field":"account_id","request_id":"req_01J8Z9K2QW"},"meta":{"request_id":"req_01J8Z9K2QW","environment":"production"}}}}},"NotFound":{"description":"not_found / conversation_not_found / agent_not_found / recording_not_found","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"Conflict":{"description":"turn_in_progress / conversation_already_ended / email_exists / version_label_exists / agent_has_no_phone_number / from_number_not_owned / call_not_cancellable / call_deferral_refused","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"Gone":{"description":"conversation_deleted / transcript_expired / recording_expired (body may carry retention_expires_at)","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"PayloadTooLarge":{"description":"payload_too_large","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"TooManyRequests":{"description":"rate_limited / call_quota_exceeded — retry after Retry-After; does not consume quota","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"Retry-After":{"$ref":"#/components/headers/Retry-After"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"RateLimited":{"description":"`rate_limited` — the `write` class allows 60 requests/min per credential.","headers":{"Retry-After":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"examples":{"ConversationListExample":{"summary":"One page of the conversation list","value":{"data":[{"conversation_id":"conv_p9876","account_id":"acc_17","agent_type":"chat","uirix_agent_id":"agt_245","agent_version":"agt245-v3","prompt_version":"chat-v3","model":"gpt-5-nano","status":"completed","started_at":"2026-08-20T09:14:02Z","ended_at":"2026-08-20T09:21:47Z","updated_at":"2026-08-20T09:22:10Z","duration_seconds":465,"turn_count":14,"language":"en-US","channel_detail":{"channel":"widget","widget_version":"3.7.0","device":"desktop"},"summary":{"summary":"Visitor asked whether the widget alone satisfies the EAA; agent explained widget plus audit and remediation; visitor left a work email and asked for enterprise pricing.","intent":"pricing_enterprise","topics":["EAA","e-commerce","pricing"],"sentiment":"positive","sentiment_score":0.6,"outcome":"lead_captured","action_items":[{"text":"Send enterprise pricing","assignee":"sales","due":null}],"escalated_to_human":{"value":false,"at":null,"to":null},"resolved":true,"handoff_reason":null,"summary_status":"ready","summary_model":"uirix-summary-v1@gpt-5-nano"},"identity":{"account_id":"acc_17","equalweb_agent_id":"1619","visitor_id":"vis_9f2c81a0","session_id":"ses_7723ab19","email":"procurement@example-shop.de","email_source":"in_conversation","name":null,"company":null,"phone_e164":null,"caller_id_name":null,"called_number_e164":null,"auth_user_id":null,"auth_user_email":null,"page_url":"https://www.equalweb.com/pricing/","page_title":"Pricing | EqualWeb","referrer":"https://www.google.com/","landing_page":"https://www.equalweb.com/european-accessibility-act/","utm":{"utm_source":"google","utm_medium":"cpc","utm_campaign":"eaa-eu-enterprise","utm_term":"european accessibility act compliance","utm_content":"rsa_v3","gclid":"Cj0KCQjw_example","fbclid":null,"li_fat_id":null,"msclkid":null},"country":"DE","user_agent":"Mozilla/5.0","custom":{"plan":"free","site_id":"55231"},"ip_country_only":true,"do_not_sell":false},"metrics":{"containment":true,"escalation":false,"avg_agent_latency_ms":1840,"p95_agent_latency_ms":3120,"low_confidence_turns":null,"unanswered_turns":0,"rephrase_count":1,"abandoned_at_turn_index":null,"abandoned_after_ms":null,"csat":null,"csat_comment":null},"has_recording":false,"retention_expires_at":"2028-08-20T09:14:02Z","links":{"self":"https://api.uirix.com/v1/conversations/conv_p9876","dashboard":"https://dashboard.uirix.com/conversations/conv_p9876"}}],"pagination":{"limit":50,"has_more":true,"next_cursor":"eyJ2IjoxLCJzIjoiMjAyNi0wOC0yMFQwOToxNDowMloiLCJzdCI6IlAiLCJpIjo5ODc2fQ.p9QxL2kV"},"meta":{"request_id":"req_01J8Z9K3AA","environment":"production","applied_default_range":false,"from":"2026-08-01T00:00:00Z","to":"2026-09-01T00:00:00Z"}}},"ConversationDetailExample":{"summary":"Conversation detail with structured transcript","value":{"conversation_id":"conv_p9876","account_id":"acc_17","agent_type":"chat","uirix_agent_id":"agt_245","agent_version":"agt245-v3","prompt_version":"chat-v3","model":"gpt-5-nano","status":"completed","started_at":"2026-08-20T09:14:02Z","ended_at":"2026-08-20T09:21:47Z","updated_at":"2026-08-20T09:22:10Z","duration_seconds":465,"turn_count":14,"language":"en-US","channel_detail":{"channel":"widget","widget_version":"3.7.0","device":"desktop"},"summary":{"summary":"Visitor asked whether the widget alone satisfies the EAA; agent explained widget plus audit and remediation; visitor left a work email and asked for enterprise pricing.","intent":"pricing_enterprise","topics":["EAA","e-commerce","pricing"],"sentiment":"positive","sentiment_score":0.6,"outcome":"lead_captured","action_items":[{"text":"Send enterprise pricing","assignee":"sales","due":null}],"escalated_to_human":{"value":false,"at":null,"to":null},"resolved":true,"handoff_reason":null,"summary_status":"ready","summary_model":"uirix-summary-v1@gpt-5-nano"},"identity":{"account_id":"acc_17","equalweb_agent_id":"1619","visitor_id":"vis_9f2c81a0","session_id":"ses_7723ab19","email":"procurement@example-shop.de","email_source":"in_conversation","name":null,"company":null,"phone_e164":null,"caller_id_name":null,"called_number_e164":null,"auth_user_id":null,"auth_user_email":null,"page_url":"https://www.equalweb.com/pricing/","page_title":"Pricing | EqualWeb","referrer":"https://www.google.com/","landing_page":"https://www.equalweb.com/european-accessibility-act/","utm":{"utm_source":"google","utm_medium":"cpc","utm_campaign":"eaa-eu-enterprise","utm_term":"european accessibility act compliance","utm_content":"rsa_v3","gclid":"Cj0KCQjw_example","fbclid":null,"li_fat_id":null,"msclkid":null},"country":"DE","user_agent":"Mozilla/5.0","custom":{"plan":"free","site_id":"55231"},"ip_country_only":true,"do_not_sell":false},"metrics":{"containment":true,"escalation":false,"avg_agent_latency_ms":1840,"p95_agent_latency_ms":3120,"low_confidence_turns":null,"unanswered_turns":0,"rephrase_count":1,"abandoned_at_turn_index":null,"abandoned_after_ms":null,"csat":null,"csat_comment":null},"has_recording":false,"retention_expires_at":"2028-08-20T09:14:02Z","links":{"self":"https://api.uirix.com/v1/conversations/conv_p9876","dashboard":"https://dashboard.uirix.com/conversations/conv_p9876"},"external_session_id":null,"transcript":[{"turn_index":0,"speaker":"agent","text":"Hi! I'm the EqualWeb assistant. How can I help with accessibility today?","timestamp":"2026-08-20T09:14:03Z","confidence":null,"audio_offset_ms":null,"audio_duration_ms":null,"latency_ms":null,"turn_type":"greeting","detected_intent":null,"detected_language":null,"sources":null,"unanswered":false,"human_agent_id":null},{"turn_index":1,"speaker":"user","text":"Does your widget alone make us compliant with the EAA?","timestamp":"2026-08-20T09:14:41Z","confidence":null,"audio_offset_ms":null,"audio_duration_ms":null,"latency_ms":null,"turn_type":"question","detected_intent":null,"detected_language":null,"sources":null,"unanswered":false,"human_agent_id":null},{"turn_index":2,"speaker":"agent","text":"The widget covers a large share of WCAG 2.1 AA issues automatically, but full EAA conformance also requires an audit and source-level remediation.","timestamp":"2026-08-20T09:14:44Z","confidence":null,"audio_offset_ms":null,"audio_duration_ms":null,"latency_ms":2100,"turn_type":"answer","detected_intent":null,"detected_language":null,"sources":null,"unanswered":false,"human_agent_id":null}],"redacted":false,"redaction_rules_applied":[],"meta":{"request_id":"req_01J8Z9K4BB","environment":"production"}}}},"schemas":{"Meta":{"type":"object","required":["request_id","environment"],"properties":{"request_id":{"type":"string"},"environment":{"type":"string","enum":["production"]}}},"Pagination":{"type":"object","required":["limit","has_more","next_cursor"],"properties":{"limit":{"type":"integer"},"has_more":{"type":"boolean"},"next_cursor":{"type":["string","null"]}}},"ListEnvelope":{"type":"object","required":["data","pagination","meta"],"properties":{"data":{"type":"array","items":{}},"pagination":{"$ref":"#/components/schemas/Pagination"},"meta":{"$ref":"#/components/schemas/Meta"}}},"ConversationListMeta":{"allOf":[{"$ref":"#/components/schemas/Meta"},{"type":"object","required":["applied_default_range","from","to"],"properties":{"applied_default_range":{"type":"boolean"},"from":{"type":"string","format":"date-time"},"to":{"type":"string","format":"date-time","description":"Exclusive."}}}]},"ConversationList":{"type":"object","required":["data","pagination","meta"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Conversation"}},"pagination":{"$ref":"#/components/schemas/Pagination"},"meta":{"$ref":"#/components/schemas/ConversationListMeta"}}},"Error":{"type":"object","required":["error","meta"],"properties":{"error":{"type":"object","required":["code","message","field","request_id"],"properties":{"code":{"type":"string"},"message":{"type":"string"},"field":{"type":["string","null"]},"request_id":{"type":"string"}}},"meta":{"$ref":"#/components/schemas/Meta"}}},"ErrorEnvelope":{"type":"object","required":["error","meta"],"properties":{"error":{"$ref":"#/components/schemas/Error"},"meta":{"$ref":"#/components/schemas/Meta"}}},"Health":{"type":"object","required":["status","version","time","components"],"properties":{"status":{"type":"string","enum":["ok","degraded","down"]},"version":{"type":"string"},"time":{"type":"string","format":"date-time"},"components":{"type":"object","required":["api","db","transcripts","summaries","webhooks","media"],"properties":{"api":{"$ref":"#/components/schemas/ComponentStatus"},"db":{"$ref":"#/components/schemas/ComponentStatus"},"transcripts":{"$ref":"#/components/schemas/ComponentStatus"},"summaries":{"$ref":"#/components/schemas/ComponentStatus"},"webhooks":{"$ref":"#/components/schemas/ComponentStatus"},"media":{"$ref":"#/components/schemas/ComponentStatus"}}}}},"ComponentStatus":{"type":"string","enum":["ok","degraded","down"]},"AccountStatus":{"type":"string","enum":["active","suspended","trial"]},"AgentType":{"type":"string","enum":["chat","phone","phone_outbound","dashboard"]},"Channel":{"type":"string","enum":["widget","api","dashboard_preview","phone","dashboard"]},"ConversationStatus":{"type":"string","enum":["in_progress","completed","abandoned","failed"],"description":"Computed at read time with a 30-minute idle threshold:\n`failed` when the close reason is `failed`/`error`/`max_duration_exceeded`\nor the call status is `failed`/`busy`/`no-answer`; else `completed` when\n`ended_at` is set; else `in_progress` when there was activity in the last\n30 minutes; else `abandoned`.\n"},"Outcome":{"type":"string","enum":["resolved","lead_captured","demo_requested","escalated","abandoned","no_intent","spam","unresolved"]},"SummaryStatus":{"type":"string","enum":["pending","ready","failed"]},"PromptType":{"type":"string","enum":["incoming","chat","voice"],"description":"`incoming` → phone, `chat` → widget/API chat, `voice` → widget voice."},"CallStatus":{"type":"string","description":"`queued` accepted, waiting for a dialer slot · `scheduled` waiting for\n`scheduled_at` · `ringing` dialling · `in_progress` connected ·\n`completed` · `no_answer` · `busy` · `voicemail` (answered by a machine\nand hung up per `policy.voicemail: \"hang_up\"`) · `failed` · `cancelled`.\n","enum":["queued","scheduled","ringing","in_progress","completed","no_answer","busy","voicemail","failed","cancelled"]},"AnsweredBy":{"type":["string","null"],"description":"Answer detection. `unknown` means detection was inconclusive and the call was treated as human. `null` until answered.","enum":["human","voicemail","machine","unknown",null]},"WebhookEvent":{"type":"string","enum":["conversation.started","conversation.ended","conversation.escalated","summary.ready","conversation.deleted","call.completed","call.failed","call.no_answer"],"description":"`ping` is delivered by `POST …/test` only and is not subscribable."},"Scope":{"type":"string","enum":["accounts:read","agents:read","kb:read","conversations:read","conversations:backfill","summaries:read","audio:read","metrics:read","usage:read","calls:read","dnc:read","accounts:write","agents:write","agents:admin","kb:write","conversations:write","calls:write","dnc:write","webhooks:manage","credentials:manage","admin:all_accounts","users:manage","audit:read"]},"Limits":{"type":"object","required":["max_concurrent_calls","max_calls_per_day"],"properties":{"max_concurrent_calls":{"type":"integer","minimum":1,"default":2},"max_calls_per_day":{"type":"integer","minimum":1,"default":200}}},"Retention":{"type":"object","required":["transcript_days","summary_days","recording_days"],"properties":{"transcript_days":{"type":"integer","default":730},"summary_days":{"type":"integer","default":1095},"recording_days":{"type":"integer","default":90}}},"User":{"type":"object","required":["user_id","account_id","email","name","company_name","plan","status","language","timezone","currency_code","external_ids","limits","retention","default_redaction","created_at","updated_at"],"properties":{"user_id":{"type":"string","examples":["usr_17"]},"account_id":{"type":"string","examples":["acc_17"],"description":"Same tenant as `user_id`."},"email":{"type":"string","format":"email"},"name":{"type":"string"},"company_name":{"type":["string","null"]},"plan":{"type":"string","description":"Plan slug — lower-cased plan name (`starter`, `guest`, `enterprise_uirix`). Trial-ness is reported by `status`."},"status":{"$ref":"#/components/schemas/AccountStatus"},"language":{"type":["string","null"]},"timezone":{"type":["string","null"]},"currency_code":{"type":"string","examples":["USD"]},"external_ids":{"type":"object","additionalProperties":{"type":["string","null"]}},"limits":{"$ref":"#/components/schemas/Limits"},"retention":{"$ref":"#/components/schemas/Retention"},"default_redaction":{"type":"boolean"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"},"meta":{"$ref":"#/components/schemas/Meta"}}},"UserCreate":{"type":"object","required":["email","name","plan"],"additionalProperties":false,"properties":{"email":{"type":"string","format":"email","maxLength":255},"name":{"type":"string","minLength":1,"maxLength":255},"password":{"type":"string","writeOnly":true,"minLength":8,"maxLength":200},"plan":{"type":"string","enum":["trial","starter","payg"],"description":"`payg` = pay-as-you-go: $10 once (no monthly charge), wallet-funded, plan_expires_at 2036 (2026-09-06)"},"wallet_credit_usd":{"type":"number","minimum":0,"maximum":100000,"description":"Only with plan payg — wallet credit written at creation; default = the configured partner credit (model-pricing.json payg.partnerCreditUsd); 0 = none. 400 validation_error on any other plan."},"company_name":{"type":"string","maxLength":255},"language":{"type":"string","minLength":2,"maxLength":10},"timezone":{"type":"string","maxLength":100},"currency_code":{"type":"string","minLength":3,"maxLength":3,"default":"USD"},"external_ids":{"type":"object","additionalProperties":{"type":"string","maxLength":400}}}},"UserPatch":{"type":"object","additionalProperties":false,"minProperties":1,"properties":{"status":{"type":"string","enum":["active","suspended"],"description":"`trial` is derived from the plan and is rejected with `400 validation_error`."},"external_ids":{"type":"object","additionalProperties":{"type":["string","null"]}},"limits":{"type":"object","additionalProperties":false,"properties":{"max_concurrent_calls":{"type":"integer","minimum":1,"maximum":100},"max_calls_per_day":{"type":"integer","minimum":1,"maximum":100000},"max_agent_creates_per_day":{"type":"integer","minimum":0,"maximum":100000,"description":"Daily quota, 0 = none (2026-09-06)"},"max_builds_per_day":{"type":"integer","minimum":0,"maximum":100000},"max_kb_documents_per_day":{"type":"integer","minimum":0,"maximum":100000},"max_chat_messages_per_day":{"type":"integer","minimum":0,"maximum":1000000},"max_webhook_tests_per_day":{"type":"integer","minimum":0,"maximum":100000},"max_concurrent_builds":{"type":"integer","minimum":0,"maximum":50}}},"retention":{"type":"object","additionalProperties":false,"properties":{"transcript_days":{"type":"integer","minimum":30,"maximum":730},"summary_days":{"type":"integer","minimum":30,"maximum":1095},"recording_days":{"type":"integer","minimum":1,"maximum":365}}},"default_redaction":{"type":"boolean"}}},"ApiKey":{"type":"object","required":["key_id","user_id","name","key_prefix","scopes","account_ids","environment","ip_allowlist","status","expires_at","last_used_at","rotated_from","created_at","revoked_at"],"properties":{"key_id":{"type":"string","examples":["key_42"]},"user_id":{"type":"string","examples":["usr_17"]},"name":{"type":"string"},"key_prefix":{"type":"string","description":"First 24 characters of the secret — the only part ever shown again.","examples":["uirix_sk_live_k9Q2mX7p"]},"scopes":{"type":"array","items":{"$ref":"#/components/schemas/Scope"}},"account_ids":{"type":"array","items":{"type":"string"},"description":"Defaults to the owner's own account."},"environment":{"type":"string","enum":["live"]},"ip_allowlist":{"type":"array","items":{"type":"string"},"description":"IPv4/IPv6 addresses or CIDRs; `[]` = any IP."},"status":{"type":"string","enum":["active","revoked"]},"expires_at":{"type":"string","format":"date-time"},"last_used_at":{"type":["string","null"],"format":"date-time"},"rotated_from":{"type":["string","null"],"description":"`key_id` this key replaced."},"created_at":{"type":"string","format":"date-time"},"revoked_at":{"type":["string","null"],"format":"date-time"},"meta":{"$ref":"#/components/schemas/Meta"}}},"ApiKeyWithSecret":{"allOf":[{"$ref":"#/components/schemas/ApiKey"},{"type":"object","required":["secret"],"properties":{"secret":{"type":"string","description":"Shown once, at issue and at rotate. `prefix + 43 base64url characters`.","examples":["uirix_sk_live_k9Q2mX7pL4vT1bN8rJ3cW6yF0hA5dZ2sE9uG4iK1oM8"]}}}]},"ApiKeyCreate":{"type":"object","required":["name","scopes"],"additionalProperties":false,"properties":{"name":{"type":"string","minLength":1,"maxLength":200},"scopes":{"type":"array","minItems":1,"items":{"$ref":"#/components/schemas/Scope"}},"account_ids":{"type":"array","items":{"type":"string"},"description":"Master key only when it names an account other than the owner's."},"environment":{"type":"string","enum":["live"],"default":"live"},"expires_in_days":{"type":"integer","minimum":1,"maximum":365,"default":365},"ip_allowlist":{"type":"array","items":{"type":"string"}}}},"AuditRow":{"type":"object","required":["request_id","credential_type","credential_id","account_ids","cross_account","method","path","status_code","error_code","ip","user_agent","duration_ms","created_at"],"properties":{"request_id":{"type":"string"},"credential_type":{"type":"string","enum":["master","key","none"]},"credential_id":{"type":["string","null"],"description":"`key_42` for a personal key, the literal `master` for the master key, `null` for unauthenticated requests."},"account_ids":{"type":"array","items":{"type":"string"}},"cross_account":{"type":"boolean","description":"Always `true` for master-key rows."},"method":{"type":"string"},"path":{"type":"string","description":"Path only — the query string is never stored."},"status_code":{"type":"integer"},"error_code":{"type":["string","null"],"description":"The catalogue code for non-2xx responses."},"ip":{"type":["string","null"]},"user_agent":{"type":["string","null"]},"duration_ms":{"type":"integer"},"created_at":{"type":"string","format":"date-time"}}},"Account":{"type":"object","required":["account_id","name","domains","status","plan","created_at","updated_at","default_language","agent_types_enabled","external_ids","default_redaction","retention"],"properties":{"account_id":{"type":"string","examples":["acc_17"]},"name":{"type":"string","description":"The tenant's company name, falling back to the user's name."},"domains":{"type":"array","items":{"type":"string"},"description":"Widget allowed domains across the account's agents, normalised to bare hosts and de-duplicated."},"status":{"$ref":"#/components/schemas/AccountStatus"},"plan":{"type":"string"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"},"default_language":{"type":["string","null"]},"agent_types_enabled":{"type":"array","items":{"$ref":"#/components/schemas/AgentType"},"description":"Derived: `chat` when an agent has public chat/voice or an enabled\nwidget · `phone` from an active phone number · `phone_outbound` from\nan outbound profile · `dashboard` when the account has any agent.\n"},"external_ids":{"type":"object","additionalProperties":{"type":["string","null"]}},"default_redaction":{"type":"boolean"},"retention":{"$ref":"#/components/schemas/Retention"},"meta":{"$ref":"#/components/schemas/Meta"}}},"AccountPatch":{"type":"object","additionalProperties":false,"minProperties":1,"properties":{"external_ids":{"type":"object","additionalProperties":{"type":["string","null"]}},"default_redaction":{"type":"boolean"},"transcript_retention_days":{"type":"integer","minimum":30,"maximum":730},"summary_retention_days":{"type":"integer","minimum":30,"maximum":1095},"recording_retention_days":{"type":"integer","minimum":1,"maximum":365}}},"AccountLimits":{"type":"object","required":["account_id","max_concurrent_calls","max_calls_per_day","calls_today","calls_in_progress","rate_limits"],"properties":{"account_id":{"type":"string"},"max_concurrent_calls":{"type":"integer"},"max_calls_per_day":{"type":"integer"},"calls_today":{"type":"integer","description":"Calls created since 00:00 UTC through POST /calls."},"calls_in_progress":{"type":"integer"},"calls_remaining_today":{"type":["integer","null"],"description":"`max(0, max_calls_per_day − calls_today)` — what `POST /calls` still accepts until 00:00 UTC.\n`null` when the account has no daily cap (`max_calls_per_day` 0).\n"},"counts_campaign_calls":{"type":"boolean","const":false,"description":"Always `false`: the counters read the calls placed through `POST /calls`; calls a dashboard\ncampaign places are not in `calls_today` and do not reduce `calls_remaining_today`.\n"},"outbound_plan":{"$ref":"#/components/schemas/OutboundPlanUsage"},"quotas":{"type":"object","description":"Daily quotas per account (UTC day) and build concurrency, since 2026-09-06. Each kind\nis `{ limit, used }`; `limit` 0 means no quota. Exceeding one answers `429 quota_exceeded`.\n","properties":{"day":{"type":"string","format":"date"},"resets_at":{"type":"string","format":"date-time"},"agent_creates":{"$ref":"#/components/schemas/QuotaCounter"},"builds":{"$ref":"#/components/schemas/QuotaCounter"},"kb_documents":{"$ref":"#/components/schemas/QuotaCounter"},"chat_messages":{"$ref":"#/components/schemas/QuotaCounter"},"webhook_tests":{"$ref":"#/components/schemas/QuotaCounter"},"max_concurrent_builds":{"type":"integer"},"builds_in_progress":{"type":"integer"}}},"rate_limits":{"type":"object","required":["read","metrics","audio","backfill","write","calls"],"properties":{"read":{"type":"integer"},"metrics":{"type":"integer"},"audio":{"type":"integer"},"backfill":{"type":"integer"},"write":{"type":"integer"},"calls":{"type":"integer"}}},"meta":{"$ref":"#/components/schemas/Meta"}}},"UsageRow":{"type":"object","required":["conversations","voice_seconds","tokens","cost_usd"],"description":"Carries one key per dimension named by `group_by`.","properties":{"agent_id":{"type":["string","null"],"description":"Present when `group_by=agent`."},"agent_type":{"$ref":"#/components/schemas/AgentType"},"date":{"type":"string","description":"UTC day, `YYYY-MM-DD`. Present when `group_by` includes `date`."},"month":{"type":"string","description":"UTC month, `YYYY-MM`. Present when `group_by` includes `month`."},"channel":{"type":"string","enum":["phone_inbound","phone_outbound","widget_chat","widget_voice","widget_mixed","api_chat","dashboard_chat","dashboard_voice","ai_operations"],"description":"Present when `group_by` includes `channel`."},"conversations":{"type":"integer"},"voice_seconds":{"type":"integer"},"tokens":{"type":"integer"},"cost_usd":{"type":"number"}}},"UsageReport":{"type":"object","required":["data","totals","meta"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/UsageRow"}},"totals":{"type":"object","required":["conversations","voice_seconds","tokens","cost_usd"],"properties":{"conversations":{"type":"integer"},"voice_seconds":{"type":"integer"},"tokens":{"type":"integer"},"cost_usd":{"type":"number"}}},"meta":{"allOf":[{"$ref":"#/components/schemas/Meta"},{"type":"object","required":["from","to","group_by","currency_code","account_currency_code"],"properties":{"from":{"type":"string","format":"date-time"},"to":{"type":"string","format":"date-time"},"group_by":{"type":"string","description":"The dimensions applied, comma-separated."},"currency_code":{"type":"string","description":"Always `USD`: the amounts are cost_usd."},"account_currency_code":{"type":"string","description":"The account's own payment currency."}}}]}}},"Balance":{"type":"object","required":["account_id","wallet_balance","currency_code","monthly_spending","plan_limit","status","remaining","can_place_calls","calls_blocked_reason","min_remaining_for_calls"],"properties":{"account_id":{"type":"string"},"wallet_balance":{"type":"number","description":"Can be negative when usage outran a top-up."},"currency_code":{"type":"string"},"monthly_spending":{"type":"number"},"plan_limit":{"type":["number","null"],"description":"Plan price; spending above it is charged to the wallet."},"status":{"type":"string","description":"Subscription status gating billable actions."},"remaining":{"type":"number","description":"`max(plan_limit − monthly_spending, 0) + wallet_balance` (a missing plan_limit counts as 0), since 2026-09-30.\nThe wallet is already net of spending beyond the plan, so nothing is counted twice.\n"},"can_place_calls":{"type":"boolean","description":"`true` when the subscription is neither canceled nor suspended AND `remaining` is above `min_remaining_for_calls`. The gate behind `402 payment_required` on `POST /calls`."},"calls_blocked_reason":{"type":["string","null"],"enum":["subscription_canceled","subscription_suspended","insufficient_balance",null],"description":"`null` when calls are allowed."},"min_remaining_for_calls":{"type":"number","description":"The buffer (5): `remaining` must be above it."},"meta":{"$ref":"#/components/schemas/Meta"}}},"AgentSummary":{"type":"object","required":["agent_id","account_id","agent_type","channels","name","enabled","agent_version","prompt_version","model","languages","escalation","knowledge_bases","lead_capture","created_at","updated_at"],"properties":{"agent_id":{"type":"string","examples":["agt_245"]},"account_id":{"type":"string","examples":["acc_17"]},"agent_type":{"type":"string","enum":["chat","phone"],"description":"`phone` when the agent owns an active phone number, otherwise `chat`."},"channels":{"type":"array","items":{"type":"string"}},"name":{"type":["string","null"],"description":"The agent's name: shown in the dashboard and the name the agent uses when it introduces itself to a caller or visitor."},"display_name":{"type":["string","null"],"description":"The dashboard's \"Display Name (Internal Use)\" (Settings → Basic Information): a label for your own reference, e.g. \"Client X Agent\". Never spoken or shown to a caller or visitor. `null` when not set."},"business_name":{"type":["string","null"],"description":"The dashboard's \"Business Name\" (Settings → Basic Information): the business the agent represents, e.g. \"Acme Dental\" — what tells two agents with the same persona name apart. Also on the full object. `null` when not set."},"enabled":{"type":"boolean","description":"IMPLEMENTATION: `config_status = complete`."},"agent_version":{"type":"string","examples":["agt245-v3"]},"prompt_version":{"type":["string","null"],"description":"Active label for the primary channel's prompt slot."},"model":{"type":"string","description":"Chat model resolved from `use_large_model`."},"languages":{"type":"array","items":{"type":"string"}},"escalation":{"$ref":"#/components/schemas/Escalation"},"knowledge_bases":{"type":"array","items":{"$ref":"#/components/schemas/KnowledgeBaseSummary"}},"lead_capture":{"$ref":"#/components/schemas/LeadCapture"},"phone_number":{"type":["string","null"]},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":["string","null"],"format":"date-time"}}},"AgentPatch":{"type":"object","additionalProperties":false,"description":"The 43-field whitelist — send any subset; an unlisted key is `400 unsupported_field`\nand `{}` is `400 empty_update`. Fields marked **bump** mint a new `agent_version`,\nand the three prompt fields additionally mint a `prompt_version`.\n","properties":{"name":{"type":"string","description":"The agent's name: shown in the dashboard and the name the agent uses when it introduces itself to a caller or visitor."},"display_name":{"type":["string","null"],"description":"The dashboard's \"Display Name (Internal Use)\" (Settings → Basic Information): a label for your own reference, e.g. \"Client X Agent\". Never spoken or shown to a caller or visitor. `null` when not set."},"description":{"type":["string","null"],"description":"Free-text note about what this agent is for. Never spoken."},"business_name":{"type":["string","null"],"description":"Business the agent represents; fills `{{businessName}}` in greetings and prompts."},"business_type":{"type":["string","null"],"description":"Industry label used to steer the prompt builder (e.g. `clinic`, `law_firm`)."},"greeting":{"type":["string","null"],"description":"Opening line for INBOUND calls, and the fallback for outbound when `outbound_greeting` is unset. Spoken in the agent's `language`, translated on the fly if written in another one."},"outbound_greeting":{"type":["string","null"],"description":"Opening line for OUTBOUND calls. Null means fall back to `greeting`. Set it so a call the platform placed does not thank the customer for calling. A campaign profile's own greeting still takes precedence over this."},"public_chat_greeting":{"type":["string","null"],"maxLength":1000,"description":"Opening line of WEBSITE CHAT (widget and chat via API). Null or empty falls back to `greeting`. Mirrored into the compiled copy the widget reads first."},"public_voice_greeting":{"type":["string","null"],"maxLength":500,"description":"Opening line of VOICE CHAT (the voice widget), spoken aloud. Null or empty falls back to `public_chat_greeting`, then `greeting`. Mirrored into the compiled copy the widget reads first."},"role":{"type":["string","null"],"description":"One-line job description of the agent, injected into the prompt."},"tone_of_voice":{"type":["string","null"],"description":"Speaking style; also mirrored to the voice config as `style`."},"language":{"type":["string","null"],"description":"Primary language as a code (`en`, `he`, …). Also writes `agent_language`. Decides what language every opening line is spoken in."},"agent_language":{"type":["string","null"],"description":"The same language column, writable on its own; prefer `language`."},"channels":{"type":["array","null"],"items":{"type":"string"},"description":"Channels this agent serves, e.g. `[\"voice\",\"chat\"]`."},"interaction_mode":{"type":["string","null"],"description":"How the agent drives a conversation (e.g. `conversational`, `scripted`)."},"main_role":{"type":["string","null"],"description":"Primary role key from the role catalogue (e.g. `support`, `sales`)."},"subroles":{"type":["array","null"],"items":{},"description":"Secondary role keys refining `main_role`."},"goals":{"type":["array","null"],"items":{},"description":"What a successful conversation achieves; used by the prompt builder."},"policies":{"type":["array","null"],"items":{},"description":"Rules the agent must not break (escalation, refunds, disclosures)."},"allowed_actions":{"type":["array","null"],"items":{},"description":"Actions the agent may take, such as booking or sending a link."},"completions":{"type":["object","string","null"],"description":"Link and detail slots the agent can send (`#appointment_link`, `#payment_link`, …). An object; a JSON string is accepted and normalised."},"knowledge_areas":{"type":["array","object","string","null"],"description":"Topics the agent is expected to cover, used to grade knowledge coverage."},"knowledge_sources":{"type":["array","null"],"items":{},"description":"Declared origins of the knowledge base (site, uploads, manual entries)."},"fallback_answer":{"type":["string","null"],"description":"What the agent says when the knowledge base has no answer."},"instructions":{"type":["string","null"],"description":"System prompt for INBOUND voice. **bump** + new `incoming` prompt version."},"public_chat_instructions":{"type":["string","null"],"description":"System prompt for widget/API chat. Mirrored into `public_chat_build_output.systemPrompt` on write (the copy the widget runs) unless that field is sent in the same request. **bump** + new `chat` prompt version."},"public_voice_instructions":{"type":["string","null"],"description":"System prompt for widget voice. Mirrored into `public_voice_build_output.systemPrompt` on write; when no compiled copy exists the widget voice falls back to this field, then to `instructions`. **bump** + new `voice` prompt version."},"ai_build_output":{"type":["string","null"],"description":"Compiled inbound-voice prompt produced by the builder, and what the agent actually runs on. Unlike the refinement-rule fields, writing this DOES change behaviour immediately. A JSON document whose `systemPrompt` key carries the prompt; a value the runtime cannot parse degrades the agent. The next rebuild in the UiriX dashboard overwrites whatever you put here. **bump**"},"public_chat_build_output":{"type":["string","null"],"description":"Compiled chat prompt, live on write. Same shape and same caveats as `ai_build_output`. **bump**"},"public_voice_build_output":{"type":["string","null"],"description":"Compiled widget-voice prompt, live on write. Same shape and same caveats as `ai_build_output`. **bump**"},"incoming_refinement_rules":{"type":["array","string","null"],"items":{"type":"string","maxLength":1000},"description":"**Improve agent, inbound phone.** An array of plain-language rules (a JSON string of one is accepted). APPLIED on write: prepended to `instructions` and to `ai_build_output.systemPrompt` under the dashboard's `CUSTOM REFINEMENT INSTRUCTIONS (HIGHEST PRIORITY)` header, replacing any previous header; `[]` removes it. Mints a new `incoming` prompt version. Up to 50 rules. **bump**"},"public_chat_refinement_rules":{"type":["array","string","null"],"items":{"type":"string","maxLength":1000},"description":"**Improve agent, widget/API chat.** Same as `incoming_refinement_rules`, applied to `public_chat_instructions` and `public_chat_build_output.systemPrompt`. Mints a new `chat` prompt version. **bump**"},"public_voice_refinement_rules":{"type":["array","string","null"],"items":{"type":"string","maxLength":1000},"description":"**Improve agent, widget voice.** Same as `incoming_refinement_rules`, applied to `public_voice_instructions` and `public_voice_build_output.systemPrompt`. Mints a new `voice` prompt version. **bump**"},"temperature":{"type":["number","null"],"description":"Sampling temperature, 0–2. Lower is more repeatable. **bump**"},"max_tokens":{"type":["integer","null"],"description":"Cap on tokens in a single model reply. **bump**"},"use_large_model":{"type":"boolean","description":"Large model tier instead of the standard one — better quality, billed at a higher multiplier, and changes `model` in responses. **bump**"},"ai_provider":{"type":"string","enum":["openai","gemini","gpt_live"],"description":"Which provider serves this agent. **bump**"},"capabilities":{"type":["array","string","null"],"items":{"type":"string"},"description":"The ids from `GET /capabilities` the agent holds — `knowledge` entries shape how a subject is answered, `action` entries give the model a tool. VALIDATED: an unknown id is `400 validation_error` carrying `unknown_capability`. Read by inbound phone, widget chat, chat via API and widget voice; NOT by outbound calls (those use the profile's `allowed_capabilities`). **bump**"},"capabilities_status":{"type":["object","array","string","null"],"description":"Per-capability configuration and readiness state. **bump**"},"voice":{"type":["string","null"],"description":"Voice id from `GET /voices`; must belong to `ai_provider`. **bump**, mirrored to the voice config."},"speaking_rate":{"type":["number","null"],"description":"Speech speed multiplier, 1.0 is normal. **bump**, mirrored."},"barge_in":{"type":"boolean","description":"Whether the customer can interrupt the agent mid-sentence. **bump**, mirrored."},"voice_locale":{"type":["string","null"],"description":"BCP-47 locale driving speech-to-text (e.g. `he-IL`). **bump**, mirrored."},"onboarding_step":{"type":["integer","null"],"description":"How far the setup wizard has progressed. Bookkeeping only."},"config_status":{"type":["string","null"],"description":"Wizard lifecycle state, e.g. `draft`, `ready`. Bookkeeping only."}}},"Agent":{"allOf":[{"$ref":"#/components/schemas/AgentSummary"},{"type":"object","properties":{"description":{"type":["string","null"]},"business_name":{"type":["string","null"]},"business_type":{"type":["string","null"]},"role":{"type":["string","null"]},"main_role":{"type":["string","null"]},"subroles":{"type":"array","items":{}},"greeting":{"type":["string","null"]},"outbound_greeting":{"type":["string","null"],"description":"Outbound opening line. `null` means unset — the dialler falls back to `greeting`. Reported raw, so `null` is distinguishable from a copy of it."},"public_chat_greeting":{"type":["string","null"]},"public_voice_greeting":{"type":["string","null"]},"fallback_answer":{"type":["string","null"]},"tone_of_voice":{"type":["string","null"]},"language":{"type":["string","null"]},"agent_language":{"type":["string","null"]},"interaction_mode":{"type":["string","null"]},"onboarding_step":{"type":["integer","null"]},"config_status":{"type":["string","null"]},"template_id":{"type":["integer","null"]},"temperature":{"type":["number","null"]},"max_tokens":{"type":["integer","null"]},"use_large_model":{"type":"boolean"},"ai_provider":{"type":"string","examples":["openai","gemini","gpt_live"]},"instructions":{"type":["string","null"]},"public_chat_instructions":{"type":["string","null"]},"public_voice_instructions":{"type":["string","null"]},"ai_build_output":{"type":["string","null"]},"public_chat_build_output":{"type":["string","null"]},"public_voice_build_output":{"type":["string","null"]},"incoming_refinement_rules":{"type":["string","null"]},"public_chat_refinement_rules":{"type":["string","null"]},"public_voice_refinement_rules":{"type":["string","null"]},"capabilities":{"type":"array","items":{}},"capabilities_status":{"type":["string","null"]},"custom_capabilities":{"type":"array","description":"The customer-defined tools bound to the agent through a connector (created in the dashboard's capability builder), every status. Read-only. `live` says whether the runtimes inject the tool: `status = active` AND the connector `connected`.","items":{"$ref":"#/components/schemas/CustomCapability"}},"completions":{"type":["string","null"]},"knowledge_areas":{"type":["string","null"]},"knowledge_sources":{"type":["string","null"]},"goals":{"type":"array","items":{}},"policies":{"type":"array","items":{}},"allowed_actions":{"type":"array","items":{}},"quality_score":{"type":["integer","null"]},"accessibility_score":{"type":["integer","null"]},"prompt_versions":{"type":"object","description":"Active label per prompt slot.","properties":{"incoming":{"type":["string","null"]},"chat":{"type":["string","null"]},"voice":{"type":["string","null"]}}},"voice":{"$ref":"#/components/schemas/VoiceConfig"},"widget":{"type":["object","null"],"description":"Never carries `public_token` — see `GET /agents/{agt}/widget`.","properties":{"enabled":{"type":"boolean"},"allowed_domains":{"type":"array","items":{"type":"string"}},"widget_color":{"type":["string","null"]},"widget_position":{"type":["string","null"]},"has_public_token":{"type":"boolean"}}},"meta":{"$ref":"#/components/schemas/Meta"}}}]},"AgentCreate":{"allOf":[{"$ref":"#/components/schemas/AgentPatch"},{"type":"object","required":["name"],"properties":{"template_id":{"type":["string","integer"],"description":"Integer id of a public `agent_templates` row; `tpl_123` is also\naccepted. IMPLEMENTATION: templates are a catalogue, not a\ntenant-scoped resource, so `ids.ts` defines no `tpl_` prefix.\n"}}}]},"PromptVersion":{"type":"object","required":["prompt_version_id","agent_id","type","version_label","model","is_active","deployed_at","retired_at","restored_from","created_by","notes"],"properties":{"prompt_version_id":{"type":"string","examples":["pv_12"]},"agent_id":{"type":"string","examples":["agt_245"]},"type":{"$ref":"#/components/schemas/PromptType"},"version_label":{"type":"string","examples":["chat-v3"]},"model":{"type":["string","null"]},"is_active":{"type":"boolean"},"deployed_at":{"type":["string","null"],"format":"date-time"},"retired_at":{"type":["string","null"],"format":"date-time"},"restored_from":{"type":["string","null"],"examples":["pv_9"]},"created_by":{"type":["string","null"],"examples":["key_42","master","dashboard:usr_17"]},"notes":{"type":["string","null"]}}},"PromptDeploy":{"type":"object","required":["type","body"],"additionalProperties":false,"properties":{"type":{"$ref":"#/components/schemas/PromptType"},"body":{"type":"string","minLength":1,"maxLength":200000},"label":{"type":"string","minLength":1,"maxLength":80,"description":"Default `{type}-v{n}`. Must be unique per agent + type."},"model":{"type":"string","maxLength":80,"description":"Recorded on the version; does not change the agent's model."},"notes":{"type":"string","maxLength":4000}}},"KbDocument":{"type":"object","required":["doc_id","kind","title","source_url","status","indexed","word_count","language","last_indexed_at","error","created_at"],"properties":{"doc_id":{"type":"string","examples":["doc_f101","doc_c202"]},"kind":{"type":"string","enum":["file","text","page"]},"title":{"type":["string","null"]},"source_url":{"type":["string","null"],"description":"Crawled pages only."},"status":{"$ref":"#/components/schemas/DocumentStatus"},"indexed":{"type":"boolean"},"word_count":{"type":["integer","null"],"description":"IMPLEMENTATION: crawled pages only — `files` stores no word count."},"language":{"type":["string","null"],"description":"IMPLEMENTATION: always `null` — neither `files` nor `crawled_pages` stores a language."},"last_indexed_at":{"type":["string","null"],"format":"date-time"},"error":{"type":["string","null"]},"size_bytes":{"type":["integer","null"],"description":"Files only."},"chunk_count":{"type":["integer","null"],"description":"Files only."},"keep_verbatim":{"type":"boolean","description":"Files and text only. true = kept as written: not summarised, its full text follows the summary in the prompt (chat cap 1,000,000 / 2,000,000 characters per agent by tier; calls 270,000). Flagged automatically when the text opens with \"IMPORTANT INSTRUCTION FOR AI ASSISTANT\" or \"do not summarize\"."},"verbatim_in_calls":{"type":"boolean","description":"Files and text only. Placed first in the calls block."},"created_at":{"type":["string","null"],"format":"date-time"}}},"KbStatus":{"type":"object","required":["agent_id","crawl","summary","vector_sync","meta"],"properties":{"agent_id":{"type":"string","examples":["agt_245"]},"crawl":{"type":"object","properties":{"status":{"type":"string","enum":["idle","running","failed"]},"pages_done":{"type":"integer"},"pages_total":{"type":"integer"},"current_url":{"type":["string","null"]},"mode":{"type":["string","null"],"enum":["static","dynamic",null]},"last_run_at":{"type":["string","null"],"format":"date-time"},"error":{"type":["string","null"]},"failed_pages":{"type":"array","description":"Pages the last crawl of this process could not use, with the reason (capped at 20).","items":{"type":"object","properties":{"url":{"type":"string"},"error":{"type":"string"}}}}}},"summary":{"type":"object","properties":{"status":{"type":"string","enum":["ready","out_of_date","not_built","absent"],"description":"Describes the SUMMARY, not a job (the last summary job this\nbackend process ran is reported in `last_job`). Computed at read\ntime from `pending_sources`: `ready` = a summary exists and\nnothing is pending; `out_of_date` = a summary exists and sources\nare pending, and the agent is answering from the older one;\n`not_built` = no summary but sources are waiting, so the agent\ncarries no knowledge; `absent` = no summary and no sources.\n"},"stale":{"type":"boolean"},"version":{"type":["integer","null"]},"sources_count":{"type":["integer","null"]},"pending_sources":{"type":"integer"},"updated_at":{"type":["string","null"],"format":"date-time"},"last_job":{"type":["object","null"],"description":"The last summary job this backend process ran for the agent — `running`, `done` or\n`failed` with the error text — or null when none ran since the process started (a\nrestart forgets it). A failed job used to leave `not_built` with every source pending\nand no trace (2026-09-11).\n","properties":{"status":{"type":"string","enum":["running","done","failed"]},"error":{"type":["string","null"]},"started_at":{"type":["string","null"],"format":"date-time"},"ended_at":{"type":["string","null"],"format":"date-time"},"progress":{"type":["object","null"],"description":"While a batched summary runs, which batch of how many.","properties":{"batch":{"type":"integer"},"batches":{"type":"integer"}}}}}}},"vector_sync":{"type":"null","description":"IMPLEMENTATION: always null — the vector-store sync was retired in Sept 2026."},"meta":{"$ref":"#/components/schemas/Meta"}}},"Widget":{"type":"object","required":["agent_id","public_token","embed_snippet","enabled","allowed_domains","widget_color","widget_position","updated_at","meta"],"properties":{"agent_id":{"type":"string","examples":["agt_245"]},"public_token":{"type":["string","null"],"examples":["pk_7f3a9c…"]},"embed_snippet":{"type":["string","null"]},"enabled":{"type":"boolean"},"allowed_domains":{"type":"array","items":{"type":"string"}},"widget_color":{"type":["string","null"],"examples":["#3B82F6"]},"widget_position":{"type":["string","null"],"enum":["bottom-right","bottom-left","top-right","top-left",null]},"welcome_message":{"type":["string","null"]},"placeholder_text":{"type":["string","null"]},"updated_at":{"type":["string","null"],"format":"date-time"},"meta":{"$ref":"#/components/schemas/Meta"}}},"WidgetPatch":{"type":"object","additionalProperties":false,"minProperties":1,"properties":{"enabled":{"type":"boolean"},"allowed_domains":{"type":"array","maxItems":50,"items":{"type":"string","minLength":1,"maxLength":255}},"widget_color":{"type":"string","pattern":"^#[0-9A-Fa-f]{6}$"},"widget_position":{"type":"string","enum":["bottom-right","bottom-left","top-right","top-left"]},"welcome_message":{"type":["string","null"],"maxLength":2000},"placeholder_text":{"type":["string","null"],"maxLength":255}}},"Voice":{"type":"object","required":["voice_id","provider","name","gender","personality","description","active","requires_large_model","localized_names"],"properties":{"voice_id":{"type":"string","description":"The value to send as `voice` on `PATCH /agents/{agt}`.","example":"alloy"},"provider":{"type":"string","enum":["openai","gemini","gpt_live"]},"image_url":{"type":["string","null"],"format":"uri","description":"The voice's face on the CDN (WebP) — the same art the dashboard's voice page shows,\nso a partner wizard can display identical faces. The file is named after `name`.\n"},"image_jpg_url":{"type":["string","null"],"format":"uri","description":"The same picture as JPEG, for a client that cannot take WebP."},"video_url":{"type":["string","null"],"format":"uri","description":"A short talking loop — the older single clip where one exists (OpenAI), else the same file as video_talking_url; present for every voice since 29 Sept 2026."},"video_talking_url":{"type":["string","null"],"format":"uri","description":"The face talking — the clip the widget plays while the agent speaks (avatars/voices/<Name>/avatar-talking.mp4, versioned). Every OpenAI and Gemini voice."},"video_quiet_url":{"type":["string","null"],"format":"uri","description":"The face at rest — the clip the widget loops while the agent listens (avatar-quiet.mp4, versioned). Every OpenAI and Gemini voice."},"sample_url":{"type":["string","null"],"format":"uri","description":"An MP3 (5–8 s) of the voice saying its marketing line in English, on the CDN — public,\ncacheable, no key needed to play it. Present for every OpenAI and Gemini voice; null only\nuntil the file is rendered (GET /voices/{voice_id}/sample renders it). Keep the URL as\nreturned: the ?v= is a cache version.\n","example":"https://cdn.uirix.com/shared/samples/Alloy.en.mp3?v=1"},"name":{"type":"string","description":"The provider's own name for the voice.","example":"Alloy"},"gender":{"type":["string","null"],"example":"female"},"personality":{"type":["string","null"],"example":"Versatile & Balanced"},"description":{"type":["string","null"],"example":"Professional all-rounder for any business need"},"active":{"type":"boolean","description":"`false` means retired. Do not offer it as a new choice."},"requires_large_model":{"type":"boolean","description":"Available only when the agent has `use_large_model` set."},"localized_names":{"type":"object","description":"Marketing name per locale, keyed by language code.","additionalProperties":{"type":"string"},"example":{"en":"Ava","he":"נועה"}}}},"Links":{"type":"object","required":["self","dashboard"],"properties":{"self":{"type":"string","format":"uri"},"dashboard":{"type":"string","format":"uri"}}},"ChannelDetail":{"type":"object","required":["channel","origin","source","import","widget_version","device"],"properties":{"channel":{"$ref":"#/components/schemas/Channel"},"origin":{"type":"string","enum":["native","api","import"],"description":"Who started the conversation. `api` when the platform placed it (an outbound call from `POST /calls`, a conversation from `POST /conversations`), `native` when a person did, `import` when it was written by `POST /conversations/import` — a call the platform never ran. Independent of `channel`, which is the transport: an API-placed phone call is `channel: \"phone\"`, `origin: \"api\"`. Filter on it to exclude machine-placed and imported traffic from prompt-quality analysis."},"source":{"type":["string","null"],"enum":["import",null],"description":"`import` for a conversation written by `POST /conversations/import`; null otherwise."},"import":{"oneOf":[{"$ref":"#/components/schemas/ImportDetail"},{"type":"null"}],"description":"What the importer recorded about an imported call; null on every other conversation."},"widget_version":{"type":["string","null"],"description":"Always null — the widget version is not recorded on the conversation."},"device":{"type":["string","null"],"enum":["desktop","mobile",null],"description":"Derived from the stored user agent."}}},"Turn":{"type":"object","description":"Every turn carries all 15 keys, `null` where not applicable — never omitted. `tool` turns (since 2026-09-10) are the tools the agent ran, merged from the tool-call audit by time: `tool_call` = the function and its arguments, `tool_result` = what came back.","required":["turn_index","speaker","text","timestamp","confidence","language_mismatch","audio_offset_ms","audio_duration_ms","latency_ms","turn_type","detected_intent","detected_language","sources","unanswered","human_agent_id"],"properties":{"turn_index":{"type":"integer","description":"0-based, contiguous; tool turns are counted."},"speaker":{"type":"string","enum":["user","agent","human_agent","system","tool"]},"text":{"type":"string","description":"Verbatim message / transcribed speech; on a tool turn a one-line rendering such as `get_google_indexed_page_count(website_url=mertzig.lu)` or `… → {\"total_results\":68100}`."},"timestamp":{"type":["string","null"],"format":"date-time"},"language_mismatch":{"type":"boolean","description":"Phone: the transcript's script contradicts the call language. False elsewhere."},"confidence":{"type":"null","description":"No per-utterance confidence in v1; never substituted with 1.0."},"audio_offset_ms":{"type":["integer","null"],"description":"Phone only: `timestamp − started_at`, derived from wall-clock (≈ 1 s accuracy). Null for chat and dashboard."},"audio_duration_ms":{"type":"null","description":"Not recorded by any writer in v1."},"latency_ms":{"type":["integer","null"],"description":"Agent turns: milliseconds since the preceding user turn. Null on greetings and non-agent turns."},"turn_type":{"type":["string","null"],"description":"Heuristic in v1: `greeting`, `question`, `answer`, `handoff`, `closing`; `tool_call` / `tool_result` on tool turns."},"detected_intent":{"type":"null","description":"Always null. Conversation-level intent is on the summary."},"detected_language":{"type":"null","description":"Always null. Use the conversation-level language."},"sources":{"type":"null","description":"Attribution is not available for these engines; `[]` will mean \"answered without a source\"."},"unanswered":{"type":"boolean","description":"True when the agent emitted its configured fallback answer."},"human_agent_id":{"type":"null"}}},"ActionItem":{"type":"object","required":["text","assignee","due"],"properties":{"text":{"type":"string"},"assignee":{"type":["string","null"]},"due":{"type":["string","null"],"format":"date-time"}}},"Utm":{"type":"object","description":"Always all nine keys.","required":["utm_source","utm_medium","utm_campaign","utm_term","utm_content","gclid","fbclid","li_fat_id","msclkid"],"properties":{"utm_source":{"type":["string","null"]},"utm_medium":{"type":["string","null"]},"utm_campaign":{"type":["string","null"]},"utm_term":{"type":["string","null"]},"utm_content":{"type":["string","null"]},"gclid":{"type":["string","null"]},"fbclid":{"type":["string","null"]},"li_fat_id":{"type":["string","null"]},"msclkid":{"type":["string","null"]}}},"Identity":{"type":"object","description":"All 23 keys for every `agent_type`, `null` where unknown. Structured\nidentity is never redacted — it is protected by scope instead. Raw IP\naddresses are never exposed, so `ip_country_only` is always `true`.\n","required":["account_id","equalweb_agent_id","visitor_id","session_id","email","email_source","name","company","phone_e164","caller_id_name","called_number_e164","auth_user_id","auth_user_email","page_url","page_title","referrer","landing_page","utm","country","user_agent","custom","ip_country_only","do_not_sell"],"properties":{"account_id":{"type":"string","examples":["acc_1001"]},"equalweb_agent_id":{"type":["string","null"],"description":"Passed through verbatim as a string."},"visitor_id":{"type":["string","null"]},"session_id":{"type":["string","null"],"description":"Widget session, `api_…` for API chat, the call SID for phone."},"email":{"type":["string","null"]},"email_source":{"type":["string","null"],"enum":["in_conversation","lead_form","authenticated","crm_prefill",null]},"name":{"type":["string","null"]},"company":{"type":["string","null"]},"phone_e164":{"type":["string","null"],"description":"E.164 always. Phone: the caller (inbound) / the callee (outbound), derived from the call when no explicit identity was supplied."},"caller_id_name":{"type":["string","null"]},"called_number_e164":{"type":["string","null"],"description":"The UiriX number dialled (inbound) / used as caller id (outbound)."},"auth_user_id":{"type":["string","null"],"description":"Dashboard chat: the signed-in user."},"auth_user_email":{"type":["string","null"]},"page_url":{"type":["string","null"]},"page_title":{"type":["string","null"]},"referrer":{"type":["string","null"]},"landing_page":{"type":["string","null"]},"utm":{"$ref":"#/components/schemas/Utm"},"country":{"type":["string","null"],"description":"Phone: parsed from the number. Widget: null until an edge geo header is stored."},"user_agent":{"type":["string","null"]},"custom":{"type":["object","null"],"additionalProperties":true},"ip_country_only":{"type":"boolean","const":true},"do_not_sell":{"type":"boolean"}}},"Metrics":{"type":"object","description":"Computed from the parsed turns, so the latency roll-ups and\n`unanswered_turns` are only meaningful when `include=metrics` was\nrequested (which makes the read fetch transcripts). `rephrase_count` and\nthe `abandoned_*` pair are always `null` — they are not computed.\n","required":["containment","escalation","avg_agent_latency_ms","p95_agent_latency_ms","low_confidence_turns","unanswered_turns","rephrase_count","abandoned_at_turn_index","abandoned_after_ms","csat","csat_comment"],"properties":{"containment":{"type":["boolean","null"],"description":"`status = completed` and not escalated; null when the conversation is not completed."},"escalation":{"type":"boolean"},"avg_agent_latency_ms":{"type":["integer","null"]},"p95_agent_latency_ms":{"type":["integer","null"]},"low_confidence_turns":{"type":"null","description":"No confidence signal exists in v1."},"unanswered_turns":{"type":"integer"},"rephrase_count":{"type":["integer","null"]},"abandoned_at_turn_index":{"type":["integer","null"]},"abandoned_after_ms":{"type":["integer","null"]},"csat":{"type":["number","null"],"description":"The stored call rating."},"csat_comment":{"type":["string","null"]}}},"Conversation":{"type":"object","required":["conversation_id","account_id","agent_type","uirix_agent_id","agent_name","agent_version","prompt_version","model","status","started_at","ended_at","updated_at","duration_seconds","turn_count","language","channel_detail","has_recording","retention_expires_at","links"],"properties":{"conversation_id":{"type":"string","pattern":"^conv_[pud]\\d+$"},"account_id":{"type":"string"},"agent_type":{"$ref":"#/components/schemas/AgentType"},"uirix_agent_id":{"type":["string","null"],"description":"The agent whose log the conversation belongs to."},"agent_name":{"type":["string","null"],"description":"The agent's current name (agents.name), for display."},"agent_version":{"type":["string","null"],"description":"Stamped at start, immutable."},"prompt_version":{"type":["string","null"],"description":"Stamped at start, immutable."},"model":{"type":["string","null"],"description":"Chat: the model that answered the turns (recovered from the turn metadata for older conversations)."},"status":{"$ref":"#/components/schemas/ConversationStatus"},"started_at":{"type":"string","format":"date-time"},"ended_at":{"type":["string","null"],"format":"date-time","description":"Phone: when the call ended. Chat: when the session was closed, which can be long after the last message."},"updated_at":{"type":"string","format":"date-time","description":"Drives `updated_since`."},"duration_seconds":{"type":["integer","null"],"description":"Phone / dashboard: `ended_at − started_at`. Chat: last message − `started_at` (since 2026-09-10). Null while open."},"turn_count":{"type":"integer"},"language":{"type":["string","null"],"description":"Detected by the summariser once ready; the agent's configured language before that."},"channel_detail":{"$ref":"#/components/schemas/ChannelDetail"},"summary":{"$ref":"#/components/schemas/ConversationSummary"},"identity":{"$ref":"#/components/schemas/Identity"},"metrics":{"$ref":"#/components/schemas/Metrics"},"has_recording":{"type":"boolean"},"retention_expires_at":{"type":["string","null"],"format":"date-time"},"links":{"$ref":"#/components/schemas/Links"}}},"Tombstone":{"type":"object","required":["conversation_id","account_id","deleted_at","reason"],"properties":{"conversation_id":{"type":"string"},"account_id":{"type":"string"},"deleted_at":{"type":"string","format":"date-time"},"reason":{"type":"string","enum":["user_request","retention","account_closed"]}}},"Recording":{"type":"object","required":["conversation_id","url","expires_at","format","sample_rate_hz","duration_ms","size_bytes","language","locale","channels","channel_map","redacted","retention_expires_at"],"properties":{"conversation_id":{"type":"string","examples":["conv_u555"]},"url":{"type":"string","format":"uri"},"expires_at":{"type":"string","format":"date-time"},"format":{"type":"string","const":"mp3"},"sample_rate_hz":{"type":"integer","const":8000},"duration_ms":{"type":["integer","null"]},"size_bytes":{"type":["integer","null"],"description":"null when the storage backend did not answer in time."},"language":{"type":["string","null"]},"locale":{"type":["string","null"]},"channels":{"type":"string","enum":["mono","dual"]},"channel_map":{"type":["object","null"],"description":"`{\"0\":\"caller\",\"1\":\"agent\"}` for dual-channel recordings, `null` for mono.","additionalProperties":{"type":"string"}},"redacted":{"type":"boolean","description":"Follows the account redaction policy."},"retention_expires_at":{"type":["string","null"],"format":"date-time"},"meta":{"$ref":"#/components/schemas/Meta"}}},"IdentifyRequest":{"type":"object","additionalProperties":false,"description":"Every key is optional; an unknown key is `400 unsupported_field`. Types\nare checked by the identity normaliser, which produces the precise\nerror code for the offending field.\n","properties":{"equalweb_agent_id":{"type":"string","description":"Must be a JSON string — a number is `400 invalid_type`."},"visitor_id":{"type":"string"},"session_id":{"type":"string"},"email":{"type":"string"},"email_source":{"type":"string","enum":["in_conversation","lead_form","authenticated","crm_prefill"]},"name":{"type":"string"},"company":{"type":"string"},"phone_e164":{"type":"string","description":"Strict E.164 (`+972501234567`)."},"auth_user_id":{"type":"string"},"auth_user_email":{"type":"string"},"page_url":{"type":"string"},"page_title":{"type":"string"},"referrer":{"type":"string"},"landing_page":{"type":"string"},"user_agent":{"type":"string"},"utm":{"type":"object","additionalProperties":{"type":"string"}},"country":{"type":"string","description":"ISO-3166-1 alpha-2."},"custom":{"type":"object","additionalProperties":true,"description":"Flat key/value bag: string, number or boolean, at most 50 keys."},"do_not_sell":{"type":"boolean"}}},"CallPolicy":{"type":"object","additionalProperties":false,"properties":{"scheduled_at":{"type":["string","null"],"format":"date-time","description":"Dial at exactly this instant. `null` or absent = now. Not rounded into any window."},"earliest_local_time":{"type":"string","pattern":"^([01]\\d|2[0-3]):[0-5]\\d$","description":"NO DEFAULT — sending it is how you ask for a calling window, which a\none-off API call otherwise does not have. In the RECIPIENT time zone,\nhalf-open `[earliest, latest)`, and must be earlier than\n`latest_local_time` (overnight windows are not supported in v1).\nSend only one edge and the other becomes the end of the local day.\nOn a call placed WITHOUT a window this reads back as `00:00` /\n`23:59` — the whole local day, i.e. no window.\n"},"latest_local_time":{"type":"string","pattern":"^([01]\\d|2[0-3]):[0-5]\\d$","description":"No default. See `earliest_local_time`."},"max_attempts":{"type":"integer","minimum":1,"maximum":3,"default":1,"description":"Retries apply to `no_answer` / `busy` only, and honour whatever window the call was placed with — none, if none was asked for."},"retry_after_minutes":{"type":"integer","minimum":30,"default":240},"voicemail":{"type":"string","enum":["hang_up"],"default":"hang_up","description":"`leave_message` is `400 unsupported_policy` in v1."},"recording":{"type":"boolean","default":true,"description":"Dual-channel recording (caller / agent). When true the agent is\ninstructed to open with the recording disclosure and\n`disclosure_played` is recorded.\n"},"voicemail_script":{"type":"string","description":"Reserved. Sending it is `400 unsupported_policy` in v1."}}},"Call":{"type":"object","required":["call_id","account_id","agent_id","profile_id","profile","profile_source","to_e164","from_e164","status","scheduled_at","recipient_timezone","attempts","max_attempts","next_attempt_at","answered_by","disclosure_played","dnc_requested","dnc_flagged","conversation_id","matter_key","context","policy","callback_url","error","created_at","started_at","ended_at","duration_seconds","links"],"properties":{"call_id":{"type":"string"},"account_id":{"type":"string"},"agent_id":{"type":["string","null"]},"profile_id":{"type":["string","null"],"description":"The stored profile the call was placed with."},"profile":{"oneOf":[{"$ref":"#/components/schemas/InlineCallProfile"},{"type":"null"}],"description":"The inline profile the call was placed with, as accepted."},"profile_source":{"type":"string","enum":["agent","profile","inline","profile+inline"]},"to_e164":{"type":"string"},"from_e164":{"type":["string","null"]},"status":{"$ref":"#/components/schemas/CallStatus"},"scheduled_at":{"type":["string","null"],"format":"date-time"},"recipient_timezone":{"type":["string","null"]},"attempts":{"type":"integer"},"max_attempts":{"type":"integer"},"next_attempt_at":{"type":["string","null"],"format":"date-time","description":"Set after `no_answer` / `busy` while attempts remain; the call returns to `scheduled`."},"answered_by":{"$ref":"#/components/schemas/AnsweredBy"},"disclosure_played":{"type":"boolean"},"dnc_requested":{"type":"boolean","description":"The recipient asked not to be called again."},"dnc_flagged":{"type":"boolean","description":"The number was auto-appended to the do-not-call list from this call."},"conversation_id":{"type":["string","null"],"description":"`conv_u…` once the call produced a conversation."},"matter_key":{"type":["string","null"]},"context":{"type":"object","additionalProperties":true},"policy":{"$ref":"#/components/schemas/CallPolicy"},"callback_url":{"type":["string","null"]},"error":{"type":["object","null"],"properties":{"code":{"type":"string"},"message":{"type":"string"}}},"created_at":{"type":"string","format":"date-time"},"started_at":{"type":["string","null"],"format":"date-time"},"ended_at":{"type":["string","null"],"format":"date-time"},"duration_seconds":{"type":["integer","null"]},"links":{"type":"object","properties":{"self":{"type":"string","format":"uri"},"conversation":{"type":["string","null"],"format":"uri"}}},"meta":{"$ref":"#/components/schemas/Meta"}}},"DncEntry":{"type":"object","required":["dnc_id","account_id","phone_e164","reason","source","source_call_id","created_at"],"properties":{"dnc_id":{"type":"string","examples":["dnc_5"]},"account_id":{"type":"string"},"phone_e164":{"type":"string"},"reason":{"type":["string","null"]},"source":{"type":"string","enum":["api","agent_request","import"]},"source_call_id":{"type":["string","null"],"description":"The call whose analysis produced the entry, for `source: \"agent_request\"`."},"created_at":{"type":"string","format":"date-time"},"meta":{"$ref":"#/components/schemas/Meta"}}},"WebhookEndpoint":{"type":"object","required":["endpoint_id","account_id","url","description","events","enabled","environment","api_version","previous_secret_expires_at","consecutive_failures","disabled_reason","created_at","updated_at"],"properties":{"endpoint_id":{"type":"string","pattern":"^whk_[0-9]+$"},"account_id":{"type":"string","pattern":"^acc_[0-9]+$"},"url":{"type":"string","format":"uri"},"description":{"type":["string","null"]},"events":{"type":"array","items":{"$ref":"#/components/schemas/WebhookEvent"}},"enabled":{"type":"boolean"},"api_version":{"type":"string","const":"v1"},"previous_secret_expires_at":{"type":["string","null"],"format":"date-time"},"consecutive_failures":{"type":"integer","minimum":0},"disabled_reason":{"type":["string","null"],"enum":["consecutive_failures","user",null]},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"},"meta":{"$ref":"#/components/schemas/Meta"}}},"WebhookEndpointWithSecret":{"allOf":[{"$ref":"#/components/schemas/WebhookEndpoint"},{"type":"object","required":["secret"],"properties":{"secret":{"type":"string","pattern":"^whsec_[A-Za-z0-9_-]{43}$","description":"Returned on create and rotate only."}}}]},"WebhookEndpointCreate":{"type":"object","additionalProperties":false,"required":["url","events"],"properties":{"url":{"type":"string","format":"uri","maxLength":1000},"events":{"type":"array","minItems":1,"items":{"$ref":"#/components/schemas/WebhookEvent"}},"description":{"type":["string","null"],"maxLength":500},"account_id":{"type":"string","pattern":"^(acc|usr)_[0-9]+$"}}},"WebhookEndpointPatch":{"type":"object","additionalProperties":false,"minProperties":1,"properties":{"url":{"type":"string","format":"uri","maxLength":1000},"events":{"type":"array","minItems":1,"items":{"$ref":"#/components/schemas/WebhookEvent"}},"description":{"type":["string","null"],"maxLength":500},"enabled":{"type":"boolean"}}},"WebhookPayload":{"type":"object","description":"Signed with `X-Uirix-Signature: sha256=HMAC-SHA256(secret, \"{X-Uirix-Timestamp}.{raw_body}\")`.\n`data` shape depends on `event`; it always carries `updated_at`, and every conversation event carries the full identity block.\n","required":["event","delivery_id","emitted_at","api_version","account_id","data"],"properties":{"event":{"$ref":"#/components/schemas/WebhookEvent"},"delivery_id":{"type":"string"},"emitted_at":{"type":"string","format":"date-time"},"api_version":{"type":"string","enum":["v1"]},"account_id":{"type":"string"},"data":{"type":"object","additionalProperties":true,"properties":{"updated_at":{"type":"string","format":"date-time"},"transcript_truncated":{"type":"boolean","description":"true when the inline transcript was capped at 500 turns"}}}}},"DocumentStatus":{"type":"string","enum":["processing","indexed","failed"]},"Escalation":{"type":"object","required":["enabled","targets","business_hours"],"properties":{"enabled":{"type":"boolean"},"targets":{"type":"array","items":{"type":"string"},"description":"Escalation capabilities on the agent: `transfer_call`, `escalate_issue`, `create_support_ticket`."},"business_hours":{"type":["string","null"]}}},"LeadCapture":{"type":"object","required":["enabled","fields"],"properties":{"enabled":{"type":"boolean"},"fields":{"type":"array","items":{"type":"string"},"description":"Parameters the lead-capture capability collects, from the capability catalogue."}}},"KnowledgeBaseSummary":{"type":"object","required":["kb_id","name","documents","last_indexed_at","sync_status"],"properties":{"kb_id":{"type":"string","examples":["kb_agt245"]},"name":{"type":"string"},"documents":{"type":"integer","description":"files + crawled pages"},"files":{"type":"integer"},"pages":{"type":"integer"},"last_indexed_at":{"type":["string","null"],"format":"date-time"},"sync_status":{"type":"string","enum":["pending","stale","synced"],"description":"`pending` = no KB summary yet, so the agent carries no knowledge;\n`stale` = the summary predates the current documents and the agent is\nanswering from the older one — it is still served, and nothing\nrebuilds it without being asked. See KbStatus.\n"}}},"AgentWriteResult":{"type":"object","required":["agent","agent_version","prompt_version","meta"],"properties":{"agent":{"$ref":"#/components/schemas/Agent"},"agent_version":{"type":"string","examples":["agt245-v4"]},"prompt_version":{"type":["string","null"],"examples":["incoming-v5"]},"meta":{"$ref":"#/components/schemas/Meta"}}},"PromptDeployResult":{"type":"object","required":["prompt_version_id","prompt_version","agent_version","type","deployed_at","meta"],"properties":{"prompt_version_id":{"type":"string","examples":["pv_13"]},"prompt_version":{"type":"string","examples":["chat-v4"]},"agent_version":{"type":"string","examples":["agt245-v5"]},"type":{"$ref":"#/components/schemas/PromptType"},"deployed_at":{"type":["string","null"],"format":"date-time"},"meta":{"$ref":"#/components/schemas/Meta"}}},"AgentVersionMarker":{"type":"object","required":["agent_id","agent_version","prompt_version","type","model","deployed_at","retired_at","is_active","created_by","change"],"properties":{"agent_id":{"type":"string","pattern":"^agt_[0-9]+$"},"agent_version":{"type":["string","null"],"example":"agt245-v5"},"prompt_version":{"type":"string","example":"chat-v4"},"type":{"type":"string","enum":["incoming","chat","voice"]},"model":{"type":["string","null"]},"deployed_at":{"type":["string","null"],"format":"date-time"},"retired_at":{"type":["string","null"],"format":"date-time"},"is_active":{"type":"boolean"},"created_by":{"type":["string","null"]},"change":{"type":"string","example":"prompt:chat"}}},"KbTextDocument":{"type":"object","required":["title","text"],"additionalProperties":false,"properties":{"title":{"type":"string","minLength":1,"maxLength":200},"text":{"type":"string","minLength":1,"maxLength":1000000},"language":{"type":"string","minLength":2,"maxLength":10},"keep_verbatim":{"type":"boolean","description":"Keep as written — not summarised."},"verbatim_in_calls":{"type":"boolean"}}},"KbUrlDocument":{"type":"object","required":["url","mode"],"additionalProperties":false,"properties":{"url":{"type":"string","maxLength":2000},"mode":{"type":"string","enum":["page","site"]},"max_pages":{"type":"integer","minimum":1,"maximum":200,"default":25,"description":"IMPLEMENTATION: `site` mode only; `page` mode always crawls exactly one URL."}}},"VoiceConfig":{"type":"object","required":["agent_id","voice_id","voice_locale","speaking_rate","barge_in","provider","model","updated_at"],"properties":{"agent_id":{"type":"string","examples":["agt_245"]},"voice_id":{"type":["string","null"],"examples":["marin"]},"voice_locale":{"type":["string","null"],"examples":["en-US"]},"style":{"type":["string","null"],"examples":["service_friendly"]},"speaking_rate":{"type":["number","null"]},"barge_in":{"type":"boolean"},"vad_sensitivity":{"type":["number","null"]},"turn_detection_prefix_padding_ms":{"type":["integer","null"]},"turn_detection_silence_duration_ms":{"type":["integer","null"]},"provider":{"type":"string","enum":["openai","gemini","gpt_live"]},"model":{"type":["string","null"],"examples":["gpt-realtime-2.1"]},"updated_at":{"type":["string","null"],"format":"date-time"}}},"VoiceConfigPatch":{"type":"object","additionalProperties":false,"minProperties":1,"properties":{"voice_id":{"type":"string","minLength":1,"maxLength":100},"voice_locale":{"type":"string","minLength":2,"maxLength":20},"speaking_rate":{"type":"number","minimum":0.5,"maximum":2},"barge_in":{"type":"boolean"},"style":{"type":"string","minLength":1,"maxLength":100},"turn_detection_silence_duration_ms":{"type":"integer","minimum":200,"maximum":2000},"turn_detection_prefix_padding_ms":{"type":"integer","minimum":0,"maximum":1000},"vad_sensitivity":{"type":"number","minimum":0,"maximum":1}}},"CreateCallRequest":{"type":"object","required":["agent_id","to_e164"],"additionalProperties":false,"properties":{"account_id":{"type":"string","examples":["acc_17"]},"agent_id":{"type":"string","examples":["agt_301"]},"profile_id":{"type":["string","null"],"description":"A stored outbound profile belonging to THIS agent: its script, gating and\ntone run the call. Another agent's profile is `400 validation_error`; an\nunknown or inactive one is `404 profile_not_found` / `400`.\n","examples":["prf_42"]},"profile":{"oneOf":[{"$ref":"#/components/schemas/InlineCallProfile"},{"type":"null"}],"description":"The INLINE profile: the same things a stored profile carries, sent on the\ncall itself. With `profile_id`, each field sent replaces the profile's;\nwithout it, the fields replace the agent's phone prompt and outbound\ngreeting. Not stored on the agent or on any profile.\n"},"to_e164":{"type":"string","examples":["+12125550147"]},"from_e164":{"type":["string","null"],"description":"Caller id; must be a number the ACCOUNT owns (`409 from_number_not_owned`). Default: the agent number."},"context":{"type":"object","description":"Free-form case context, echoed back verbatim on the call object and\non `call.*` payloads. Reserved keys: `purpose`, `matter_key`\n(≤128), `briefing` (≤8,000 chars, reaches the agent prompt as the\nper-call instruction; stored with the call, not in the TwiML), `language`\n(BCP-47), `timezone` (IANA, overrides the derived zone),\n`customer_name`, `display_name`.\n","additionalProperties":true},"policy":{"$ref":"#/components/schemas/CallPolicy"},"callback_url":{"type":["string","null"],"description":"https URL that also receives this call's `call.*` events, as a retried delivery."}}},"CreateCallResponse":{"type":"object","required":["call_id","status","profile_id","profile","profile_source","scheduled_at","scheduled_reason","recipient_timezone","links","meta"],"properties":{"call_id":{"type":"string","examples":["call_88"]},"status":{"type":"string","enum":["queued","scheduled"]},"profile_id":{"type":["string","null"]},"profile":{"oneOf":[{"$ref":"#/components/schemas/InlineCallProfile"},{"type":"null"}],"description":"The inline profile as accepted (normalised), or `null`."},"profile_source":{"type":"string","enum":["agent","profile","inline","profile+inline"],"description":"Where the role of this call came from."},"scheduled_at":{"type":["string","null"],"format":"date-time","description":"UTC instant the call will be dialled; `null` when it goes now."},"scheduled_reason":{"type":["string","null"],"enum":["requested_time","outside_requested_window",null],"description":"Which part of THIS request put the call in the future.\n`requested_time` = `policy.scheduled_at`; `outside_requested_window`\n= the window the caller supplied excludes now. `null` exactly when\n`status` is `queued` — a call is never deferred for a reason the\ncaller did not send.\n"},"recipient_timezone":{"type":"string","examples":["America/New_York"]},"links":{"type":"object","properties":{"self":{"type":"string","format":"uri"}}},"warnings":{"type":"array","description":"Present ONLY when non-empty: the tool gate this call runs on (the inline\n`profile.allowed_capabilities`, else the stored profile's) grants a capability whose\n`required_connector` is not connected on the agent — e.g. `schedule_calendar_meeting`\nwith no calendar. The call is queued regardless.\n","items":{"$ref":"#/components/schemas/ConnectorWarning"}},"meta":{"$ref":"#/components/schemas/Meta"}}},"SendMessageRequest":{"type":"object","additionalProperties":false,"required":["text"],"properties":{"text":{"type":"string","minLength":1,"maxLength":10000},"idempotency_key":{"type":"string","maxLength":128,"description":"Honoured for 24 h per conversation."}}},"Usage":{"type":"object","required":["tokens","model"],"properties":{"tokens":{"type":"object","required":["input","output","total"],"properties":{"input":{"type":"integer"},"output":{"type":"integer"},"total":{"type":"integer"}}},"model":{"type":"string","description":"The chat model the turn ran on."}}},"TurnResponse":{"type":"object","required":["turn","usage","meta"],"properties":{"turn":{"$ref":"#/components/schemas/Turn"},"usage":{"$ref":"#/components/schemas/Usage"},"meta":{"$ref":"#/components/schemas/Meta"}}},"EndedConversation":{"type":"object","required":["conversation_id","status","ended_at","duration_seconds","turn_count","meta"],"properties":{"conversation_id":{"type":"string","pattern":"^conv_p\\d+$"},"status":{"type":"string","const":"completed"},"ended_at":{"type":"string","format":"date-time"},"duration_seconds":{"type":["integer","null"]},"turn_count":{"type":"integer"},"meta":{"$ref":"#/components/schemas/Meta"}}},"EscalatedToHuman":{"type":"object","required":["value","at","to"],"properties":{"value":{"type":"boolean"},"at":{"type":["string","null"],"format":"date-time"},"to":{"type":["string","null"]}}},"ConversationSummary":{"type":"object","description":"Never omitted. Until the structured summariser has run for this conversation, only\n`summary` may be populated — from the existing free-text call summary —\nand every other member is present and null.\n","required":["summary","intent","topics","sentiment","sentiment_score","outcome","action_items","escalated_to_human","resolved","handoff_reason","answered_by","human_requested","identity_extracted","summary_status","summary_model"],"properties":{"summary":{"type":["string","null"],"description":"Written in the ACCOUNT's language (`users.language`, then the agent's) whatever language the conversation was held in; `language` reports the customer's."},"intent":{"type":["string","null"]},"topics":{"type":"array","items":{"type":"string"},"maxItems":8},"sentiment":{"type":["string","null"],"enum":["positive","neutral","negative","mixed",null]},"sentiment_score":{"type":["number","null"],"minimum":-1,"maximum":1},"outcome":{"oneOf":[{"$ref":"#/components/schemas/Outcome"},{"type":"null"}]},"action_items":{"type":"array","items":{"$ref":"#/components/schemas/ActionItem"}},"escalated_to_human":{"$ref":"#/components/schemas/EscalatedToHuman"},"resolved":{"type":["boolean","null"]},"handoff_reason":{"type":["string","null"],"enum":["low_confidence","user_requested_human","out_of_scope","angry_user","pricing_authority","technical_bug","after_hours","other",null]},"answered_by":{"type":["string","null"],"enum":["human","voicemail","ivr","silence","unknown",null],"description":"Who or what was on the other end, decided by rule before any model call (since 2026-09-10). `voicemail` / `ivr` on phone conversations only; `silence` = no user turn, a looped utterance, or under 40 characters of real words; `unknown` on a summary written before the classifier existed; null while pending."},"human_requested":{"type":["boolean","null"],"description":"The customer asked for a human / representative / live agent (phrase match, en / he / es, user turns only). Null on older summaries."},"identity_extracted":{"type":"object","description":"Contact details the customer STATED in the conversation, never inferred. Also merged into the identity block fill-only (nulls filled, verified values never replaced).","required":["name","email","phone","company","website"],"properties":{"name":{"type":["string","null"]},"email":{"type":["string","null"]},"phone":{"type":["string","null"]},"company":{"type":["string","null"]},"website":{"type":["string","null"]}}},"summary_status":{"$ref":"#/components/schemas/SummaryStatus"},"summary_model":{"type":["string","null"],"description":"`uirix-summary-v1@<model>`; `uirix-summary-v1@rules` when written by rule without a model call."}}},"ConversationSummaryResponse":{"allOf":[{"type":"object","required":["conversation_id","summary_status","updated_at"],"properties":{"conversation_id":{"type":"string"},"updated_at":{"type":"string","format":"date-time"},"meta":{"$ref":"#/components/schemas/Meta"}}},{"$ref":"#/components/schemas/ConversationSummary"}]},"ConversationMetricsRow":{"type":"object","description":"The `group_by` dimensions requested (if any) plus every requested metric\nkey. `date` is `YYYY-MM-DD` in the applied time zone; `account_id` and\n`agent_id` are opaque ids; `intent`, `outcome`, `language` and\n`agent_version` can be `null`, which is a real bucket (a pending\nsummary), not a missing row.\n","properties":{"date":{"type":["string","null"],"pattern":"^\\d{4}-\\d{2}-\\d{2}$"},"account_id":{"type":["string","null"],"pattern":"^acc_[0-9]+$"},"agent_id":{"type":["string","null"],"pattern":"^agt_[0-9]+$"},"agent_type":{"type":["string","null"],"enum":["chat","phone","phone_outbound","dashboard",null]},"intent":{"type":["string","null"]},"outcome":{"type":["string","null"]},"language":{"type":["string","null"]},"agent_version":{"type":["string","null"]},"conversations":{"type":"integer","description":"Conversations whose `started_at` falls in the bucket, every status."},"unique_visitors":{"type":"integer","description":"Distinct `visitor_id`, falling back to the caller's number for phone."},"messages":{"type":"integer","description":"Total turns (user + agent + system)."},"escalations":{"type":"integer","description":"Conversations with `summary.escalated_to_human.value = true`."},"escalation_rate":{"type":["number","null"],"minimum":0,"maximum":1},"containment_rate":{"type":["number","null"],"description":"`completed` conversations with no escalation ÷ all `completed` conversations."},"resolved_rate":{"type":["number","null"],"description":"`summary.resolved = true` ÷ conversations whose `summary_status` is `ready`."},"leads_captured":{"type":"integer","description":"Conversations whose summary says lead_captured (needs the conversation ended and summarised)."},"leads_created":{"type":"integer","description":"Successful create_lead actions from the tool log — exact and immediate; a conversation with two leads counts twice."},"avg_handling_time_seconds":{"type":["integer","null"]},"median_handling_time_seconds":{"type":["integer","null"],"description":"IMPLEMENTATION — computed with `ROW_NUMBER()` + `COUNT(*) OVER (…)`,\naveraging the one or two middle rows. `PERCENTILE_CONT` (review item\nR19) is unavailable: this database runs at compatibility level 100\nand the function requires 110+.\n"},"avg_agent_latency_ms":{"type":"null","description":"IMPLEMENTATION — always `null`. Per-turn latency needs the\ntranscript of every conversation in the window; plan A3 sources it\nfrom the `conversation_metrics` side table filled by the summary\nworker, which is not implemented.\n"},"abandonment_rate":{"type":["number","null"],"minimum":0,"maximum":1},"unanswered_rate":{"type":"null","description":"IMPLEMENTATION — always `null`, same reason as `avg_agent_latency_ms`."},"avg_csat":{"type":["number","null"]}}},"MetricsMeta":{"allOf":[{"$ref":"#/components/schemas/Meta"},{"type":"object","required":["from","to","granularity","timezone","timezone_applied","group_by"],"properties":{"from":{"type":"string","format":"date-time"},"to":{"type":"string","format":"date-time"},"granularity":{"type":"string","enum":["day","week","month"]},"timezone":{"type":"string","description":"Echoes the request."},"timezone_applied":{"type":"string","description":"What was actually used — `UTC` when the requested zone was not recognised."},"group_by":{"type":"array","items":{"type":"string"}}}}]},"WebhookDelivery":{"type":"object","required":["delivery_id","endpoint_id","event","account_id","status","attempt","next_attempt_at","last_attempt_at","last_response_code","last_error","delivered_at","dead_at","created_at"],"properties":{"delivery_id":{"type":"string","pattern":"^dlv_[0-9A-HJKMNP-TV-Z]{26}$"},"endpoint_id":{"type":"string","pattern":"^whk_[0-9]+$"},"event":{"type":"string"},"account_id":{"type":"string","pattern":"^acc_[0-9]+$"},"conversation_id":{"type":["string","null"],"description":"IMPLEMENTATION — stored on the delivery so `?conversation_id=` is an indexed filter."},"status":{"type":"string","enum":["pending","delivered","failed","replaying"]},"attempt":{"type":"integer","minimum":0,"maximum":7},"next_attempt_at":{"type":["string","null"],"format":"date-time"},"last_attempt_at":{"type":["string","null"],"format":"date-time"},"last_response_code":{"type":["integer","null"]},"last_error":{"type":["string","null"],"description":"`timeout` · `connection_refused` · `dns_error` · `redirect` · `blocked_host` · `http_500`…"},"delivered_at":{"type":["string","null"],"format":"date-time"},"dead_at":{"type":["string","null"],"format":"date-time"},"created_at":{"type":"string","format":"date-time"},"meta":{"$ref":"#/components/schemas/Meta"}}},"CreateConversationRequest":{"type":"object","additionalProperties":false,"required":["agent_id"],"properties":{"agent_id":{"type":"string","pattern":"^agt_\\d+$"},"account_id":{"type":"string","pattern":"^(acc|usr)_\\d+$","description":"Optional; defaults to the credential's home account. Also settable with `X-Uirix-Account`."},"send_greeting":{"type":"boolean","default":false},"identity":{"$ref":"#/components/schemas/IdentifyRequest"},"custom":{"type":"object","additionalProperties":{"type":["string","number","boolean"]},"description":"Free-form key/value merged into `identity.custom`."},"channel":{"type":"string","enum":["chat","voice","phone"],"default":"chat","description":"Which surface's prompt the turns run on. `voice` = the widget-voice prompt and\ngreeting, `phone` = the phone prompt (`instructions`) and the agent's greeting;\nsame knowledge block and capability gate. A text approximation for testing the\nvoice/phone instructions without a call. Echoed as `channel` / `prompt_surface`.\n"},"external_session_id":{"type":"string","maxLength":128,"description":"Your own correlation id. **IMPLEMENTATION**: stored as\n`identity.custom.external_session_id` until a dedicated column exists\n(see the status note), and echoed on this response.\n"},"language":{"type":"string","maxLength":35,"description":"BCP-47 hint; recorded on the identity block."}}},"CreatedConversation":{"type":"object","required":["conversation_id","account_id","agent_type","status","session_id","external_session_id","agent_version","prompt_version","model","started_at","transcript","links","meta"],"properties":{"conversation_id":{"type":"string","pattern":"^conv_p\\d+$"},"account_id":{"type":"string"},"agent_type":{"type":"string","const":"chat"},"status":{"type":"string","const":"in_progress"},"session_id":{"type":"string","pattern":"^api_[0-9A-HJKMNP-TV-Z]{26}$"},"external_session_id":{"type":["string","null"]},"agent_version":{"type":["string","null"]},"prompt_version":{"type":["string","null"]},"model":{"type":["string","null"]},"started_at":{"type":"string","format":"date-time"},"transcript":{"type":"array","items":{"$ref":"#/components/schemas/Turn"},"description":"The greeting when `send_greeting` was true, otherwise empty."},"links":{"$ref":"#/components/schemas/Links"},"meta":{"$ref":"#/components/schemas/Meta"}}},"ConversationDetail":{"allOf":[{"$ref":"#/components/schemas/Conversation"},{"type":"object","required":["external_session_id","redacted","redaction_rules_applied"],"properties":{"external_session_id":{"type":["string","null"],"description":"API-chat conversations only."},"transcript":{"type":"array","items":{"$ref":"#/components/schemas/Turn"}},"transcript_text":{"type":"string","description":"Present only with `transcript_format=text`."},"redacted":{"type":"boolean","description":"True when this response was masked, either by `?redact=true` or the account default."},"redaction_rules_applied":{"type":"array","items":{"type":"string"}},"meta":{"$ref":"#/components/schemas/Meta"}}}]},"MetricName":{"type":"string","enum":["conversations","unique_visitors","messages","escalations","escalation_rate","containment_rate","resolved_rate","leads_captured","leads_created","avg_handling_time_seconds","median_handling_time_seconds","avg_agent_latency_ms","abandonment_rate","unanswered_rate","avg_csat"]},"RedactionRule":{"type":"string","description":"`CARD` is applied at INGESTION on every channel — a Luhn-valid 13-19\ndigit sequence is never persisted in the clear, so it cannot be\nrecovered with `?redact=false`. The others are read-time only.\n","enum":["EMAIL","PHONE","CARD","ID","ACCOUNT"]},"RedactionFields":{"type":"object","description":"Present on conversation detail and summary responses. `?redact=true`\nmasks `turn.text`, `transcript_text` and `summary.summary`; the identity\nblock is never masked (it is protected by scope). When the account's\n`default_redaction` is on, `?redact=false` still returns masked text —\nthe account policy wins. `turn_index` and `turn_count` are unchanged by\nredaction.\n","required":["redacted","redaction_rules_applied"],"properties":{"redacted":{"type":"boolean"},"redaction_rules_applied":{"type":"array","items":{"$ref":"#/components/schemas/RedactionRule"}}}},"WebhookEnvelope":{"type":"object","description":"The body POSTed to a subscriber. The bytes are frozen when the delivery\nis created: every retry and every replay sends exactly these bytes with\na fresh `X-Uirix-Timestamp` and a fresh signature.\n","required":["event","delivery_id","emitted_at","api_version","account_id","data"],"properties":{"event":{"type":"string"},"delivery_id":{"type":"string","pattern":"^dlv_[0-9A-HJKMNP-TV-Z]{26}$"},"emitted_at":{"type":"string","format":"date-time"},"api_version":{"type":"string","const":"v1"},"account_id":{"type":"string","pattern":"^acc_[0-9]+$"},"data":{"type":"object","description":"Per-event body. `conversation.ended` carries the full conversation\nobject (`transcript` capped at 500 turns plus `transcript_truncated`,\n`identity`, `summary`, `metrics`, `links`); `conversation.started`\ncarries the identity block and the version markers;\n`conversation.escalated` adds `handoff_reason` and `escalated_to`;\n`summary.ready` carries the summary object;\n`conversation.deleted` carries the tombstone. Every one of them\ncarries `updated_at`.\n"}}},"OutboundProfile":{"type":"object","description":"The briefing an outbound call runs under.\n\nOn a call the profile REPLACES the agent's stored persona — the runtime\nuses `system_instruction_template` OR the agent's prompt, never both.\nThe agent still supplies voice, language and knowledge base.\n`context.briefing` on `POST /calls` layers ABOVE the profile: the\nprofile is the job, the briefing is the facts for that one call.\n","required":["profile_id","account_id","agent_id","profile_code","label","category","description","system_instruction_template","greeting_message","focuses","allowed_capabilities","tone","behavior","completions","refinement_rules","override_voice","override_realtime_params","is_default_for_category","is_active","created_at","updated_at"],"properties":{"profile_id":{"type":"string","examples":["prf_42"]},"account_id":{"type":["string","null"],"examples":["acc_1001"]},"agent_id":{"type":"string","description":"The one agent this profile may be used with. `POST /calls` refuses it for any other agent.","examples":["agt_308"]},"profile_code":{"type":["string","null"],"description":"Server-generated, opaque, `{category}_{slug}_{epoch_ms}`. Not unique in\nthe database — never use it as a key. Not accepted on write.\n","examples":["sales_lead_qualify_1770040063267"]},"label":{"type":"string","maxLength":255,"description":"Your own text. Searchable with `?q=`. No uniqueness requirement.","examples":["Lead Qualify"]},"category":{"type":"string","maxLength":50,"description":"The profile TYPE: `collections`, `sales`, `events` or `appointments`\n(hidden). Validated on write since 2026-09-05. It decides which\ncapabilities the profile may grant (its group + communication), which\nfocuses the builder writes around, and which prompt template the builder\nuses. A row written before the rule may read back with a legacy free-text\nvalue; it cannot be built until set to a type.\n","examples":["sales"]},"description":{"type":["string","null"],"description":"For humans. Not sent to the model."},"system_instruction_template":{"type":["string","null"],"description":"THE ROLE. The complete system prompt for the call. Replaces the agent's\nstored prompt outright. Delivered whole — live templates run to ~11,000\ncharacters.\n"},"greeting_message":{"type":["string","null"],"description":"The opening line; first in the outbound greeting chain, ahead of the agent's `outbound_greeting` and `greeting`."},"allowed_capabilities":{"type":["array","null"],"items":{"type":"string"},"description":"THE GATING. `null` = no restriction (the runtime's full tool set); `[]` =\nnothing but talking; a list = exactly those tools. `send_sms` is never\nimplied and must be listed. Validated against the profile's type: an id\noutside the type's group (plus communication) is `400 validation_error`\ncarrying `unknown_capability` and `allowed_capabilities`. See\n`GET /outbound-capabilities`.\n","examples":[["send_email","end_call"]]},"completions":{"type":["object","null"],"additionalProperties":true,"description":"Campaign-level facts injected into the prompt as authoritative data. Per-recipient values belong in `context.briefing`."},"focuses":{"type":["array","null"],"items":{"type":"string"},"description":"The goals of the call, from the type's list (`GET /outbound-capabilities`).\nBuilder input: the script is written around them and they become the\nend-of-call rating rubric. Changes a call only through `POST /profiles/{prf}/build`.\n","examples":[["qualify_lead","promote_product_service"]]},"tone":{"oneOf":[{"$ref":"#/components/schemas/ToneSettings"},{"type":"null"}],"description":"Builder input (the script is written in this register) AND, on a call\nplaced through `POST /calls`, rendered as a `TONE FOR THIS CALL` prompt\nblock with concrete behaviour per level. Campaign calls dialled from the\ndashboard do not get the block (API-placed calls only).\n"},"behavior":{"oneOf":[{"$ref":"#/components/schemas/BehaviorSettings"},{"type":"null"}],"description":"Builder input: pacing guidance in the script (target duration, when to\noffer a callback). Retry pacing for an API call is `policy` on `POST /calls`.\n"},"refinement_rules":{"type":["array","null"],"items":{"type":"string","maxLength":1000},"maxItems":50,"description":"Plain-language rules appended to the script as highest-priority\ninstructions by the builder — the profile's \"Improve\" step. Builder input;\nchanges a call only through `POST /profiles/{prf}/build`.\n","examples":[["Never quote a price on the call"]]},"override_realtime_params":{"type":["object","null"],"additionalProperties":true,"description":"READ-ONLY and inert: no builder and no runtime reads it. Refused on write (400 validation_error)."},"override_voice":{"type":["string","null"],"maxLength":50,"description":"The voice for a call placed through `POST /calls` with this profile, ahead\nof the agent's voice. Campaign calls dialled from the dashboard keep the\nagent's voice (API-placed calls only). Not read on the Gemini outbound path.\nA `voice_id` valid for the agent's provider (`GET /voices`).\n"},"is_default_for_category":{"type":"boolean","description":"Preselects this profile in the dashboard for its `(agent, category)` pair. Setting it clears the flag on the account's other profiles in that pair. `POST /calls` never applies it implicitly."},"is_active":{"type":"boolean","description":"`false` makes the profile unusable on `POST /calls` (`404 profile_not_found`). Prefer this to deletion while a campaign still refers to it."},"created_at":{"type":["string","null"],"format":"date-time"},"updated_at":{"type":["string","null"],"format":"date-time"}}},"OutboundProfileResponse":{"allOf":[{"$ref":"#/components/schemas/OutboundProfile"},{"type":"object","required":["meta"],"properties":{"warnings":{"type":"array","description":"Only on the answers to `POST /agents/{agt}/profiles`, `PATCH /profiles/{prf}` and\n`POST /profiles/{prf}/clone`, and ONLY when non-empty: the saved profile grants a\ncapability whose `required_connector` is not connected on the agent. Nothing was refused.\n","items":{"$ref":"#/components/schemas/ConnectorWarning"}},"meta":{"$ref":"#/components/schemas/Meta"}}}]},"OutboundProfilePatch":{"type":"object","minProperties":1,"additionalProperties":false,"description":"Only the keys present are written. `null` clears a nullable field. An unknown key is `400 unsupported_field`.","properties":{"label":{"type":"string","minLength":1,"maxLength":255},"category":{"type":"string","enum":["collections","sales","events","appointments"]},"description":{"type":["string","null"]},"system_instruction_template":{"type":["string","null"]},"greeting_message":{"type":["string","null"]},"focuses":{"type":["array","null"],"items":{"type":"string"},"description":"Validated against the type's focus list."},"allowed_capabilities":{"type":["array","null"],"items":{"type":"string"},"description":"Validated against the type's tool group plus communication."},"tone":{"oneOf":[{"$ref":"#/components/schemas/ToneSettings"},{"type":"null"}]},"behavior":{"oneOf":[{"$ref":"#/components/schemas/BehaviorSettings"},{"type":"null"}]},"completions":{"type":["object","null"],"additionalProperties":true,"description":"Values are stored as strings."},"refinement_rules":{"type":["array","null"],"items":{"type":"string","maxLength":1000},"maxItems":50},"override_voice":{"type":["string","null"],"maxLength":50},"is_default_for_category":{"type":"boolean"},"is_active":{"type":"boolean"}}},"OutboundProfileCreate":{"allOf":[{"$ref":"#/components/schemas/OutboundProfilePatch"},{"type":"object","required":["label","category"],"properties":{"account_id":{"type":"string","description":"Only needed when the master key names the account in the body rather than the header.","examples":["acc_1001"]}}}]},"KbSummary":{"type":"object","required":["agent_id","text","version","sources_count","characters","stale","updated_at","meta"],"properties":{"agent_id":{"type":"string","examples":["agt_245"]},"text":{"type":["string","null"],"description":"The exact text the model receives; `null` when the agent has no summary."},"version":{"type":["integer","null"],"description":"Increments on every build and every write."},"sources_count":{"type":["integer","null"],"description":"Sources folded in by the LAST BUILD — not by a hand write."},"characters":{"type":"integer"},"stale":{"type":"boolean"},"updated_at":{"type":["string","null"],"format":"date-time"},"meta":{"$ref":"#/components/schemas/Meta"}}},"KbSummaryWriteResponse":{"allOf":[{"$ref":"#/components/schemas/KbSummary"},{"type":"object","required":["created"],"properties":{"created":{"type":"boolean","description":"`true` when the write created the summary (201)."}}}]},"ToneSettings":{"type":"object","additionalProperties":false,"description":"How the agent speaks. Each level is rendered as concrete behaviour on an API-placed call.","properties":{"formality":{"type":["string","null"],"enum":["formal","neutral","casual",null]},"empathy":{"type":["string","null"],"enum":["high","medium","low",null]},"persistence":{"type":["string","null"],"enum":["high","medium","low",null]}}},"InlineCallProfile":{"type":"object","additionalProperties":false,"description":"Everything in the request. Each key has the semantics of the matching\nprofile field and sits above the stored profile, below `context.briefing`.\n","properties":{"instructions":{"type":["string","null"],"maxLength":20000,"description":"THE ROLE — becomes the system prompt, replacing the stored template or the agent's phone prompt. Write the whole role."},"greeting":{"type":["string","null"],"maxLength":1000,"description":"The opening line, first in the greeting chain. `[Customer Name]` is filled from `context.customer_name`."},"voice":{"type":["string","null"],"maxLength":50,"description":"A `voice_id` valid for the agent's provider (`GET /voices`); otherwise `400 validation_error` on `profile.voice`."},"tone":{"oneOf":[{"$ref":"#/components/schemas/ToneSettings"},{"type":"null"}]},"allowed_capabilities":{"type":["array","null"],"items":{"type":"string"},"description":"The tool gate — any active id from `GET /outbound-capabilities`; `[]` = talk only; unknown ids are `400` with `unknown_capability`."},"completions":{"type":["object","null"],"additionalProperties":{"type":["string","number","boolean","null"]},"description":"Facts injected as authoritative data and the payloads of the `send_*` tools. Merged over the profile's; stored as strings."}}},"RequiredField":{"type":"object","description":"A value the capability needs before it can do anything, keyed by `id` in the relevant `completions` object.","required":["id","label","question","placeholder","help_text","input_type"],"properties":{"id":{"type":"string","examples":["#appointment_link","payment_link_base_url"]},"label":{"type":["string","null"]},"question":{"type":["string","null"]},"placeholder":{"type":["string","null"]},"help_text":{"type":["string","null"]},"input_type":{"type":["string","null"],"examples":["url"]}}},"AgentCapability":{"type":"object","required":["id","kind","label","category","description","active","requires_function_calling","required_field","required_connector"],"properties":{"id":{"type":"string","examples":["send_email"]},"kind":{"type":"string","enum":["knowledge","action"],"description":"`knowledge` shapes how a subject is answered; `action` is a tool the model may call."},"label":{"type":"string"},"category":{"type":["string","null"]},"description":{"type":["string","null"]},"active":{"type":"boolean","description":"`false` means retired; do not offer it as a new choice."},"requires_function_calling":{"type":"boolean"},"required_field":{"oneOf":[{"$ref":"#/components/schemas/RequiredField"},{"type":"null"}]},"required_connector":{"oneOf":[{"$ref":"#/components/schemas/RequiredConnector"},{"type":"null"}]}}},"OutboundCapability":{"type":"object","required":["id","group","label","description","active","required_field","required_connector"],"properties":{"id":{"type":"string","examples":["send_payment_link"]},"group":{"type":"string","examples":["payment","communication"]},"label":{"type":"string"},"description":{"type":["string","null"]},"active":{"type":"boolean"},"required_field":{"oneOf":[{"$ref":"#/components/schemas/RequiredField"},{"type":"null"}]},"required_connector":{"oneOf":[{"$ref":"#/components/schemas/RequiredConnector"},{"type":"null"}]}}},"OutboundFocus":{"type":"object","required":["id","label","description","active"],"properties":{"id":{"type":"string","examples":["qualify_lead"]},"label":{"type":"string"},"description":{"type":["string","null"]},"active":{"type":"boolean"}}},"OutboundRole":{"type":"object","required":["id","label","description"],"properties":{"id":{"type":"string","examples":["sales_lead_qualification"]},"label":{"type":["string","null"]},"description":{"type":["string","null"]}}},"OutboundCategory":{"type":"object","required":["id","active","groups","focuses","capabilities","roles"],"properties":{"id":{"type":"string","enum":["collections","sales","events","appointments"]},"active":{"type":"boolean","description":"`false` for `appointments`, which the product hides until calendar integration lands."},"groups":{"type":"array","items":{"type":"string"},"description":"The tool groups this type unlocks."},"focuses":{"type":"array","items":{"$ref":"#/components/schemas/OutboundFocus"}},"capabilities":{"type":"array","items":{"$ref":"#/components/schemas/OutboundCapability"}},"roles":{"type":"array","items":{"$ref":"#/components/schemas/OutboundRole"},"description":"Starting scripts the dashboard offers; informational."}}},"BehaviorSettings":{"type":"object","additionalProperties":false,"description":"Pacing guidance for the builder. Defaults when unset — 3 attempts, 24 h recall, 180 s target.","properties":{"max_attempts":{"type":["integer","null"],"minimum":1,"maximum":10},"recall_after_hours":{"type":["integer","null"],"minimum":0,"maximum":720},"optimal_call_duration":{"type":["integer","null"],"minimum":30,"maximum":3600,"description":"Seconds."}}},"ProfileBuildState":{"type":"object","required":["status","started_at","finished_at","requested_by","use_knowledge_base","warnings","error","result"],"properties":{"status":{"type":"string","enum":["building","done","error"]},"started_at":{"type":"string","format":"date-time"},"finished_at":{"type":["string","null"],"format":"date-time"},"requested_by":{"type":["string","null"],"description":"The credential that asked (`key_42`, `master`)."},"use_knowledge_base":{"type":"boolean"},"warnings":{"type":"array","items":{"type":"string"},"description":"Non-fatal notes from the builder (a language retry, a greeting shortened) and the refinement-rule report: `refinement rules: refinement rules conflict — \"…\" vs \"…\": why`, `refinement rules: refinement rule unclear — \"…\": why`."},"error":{"type":["string","null"]},"result":{"type":["object","null"],"properties":{"instruction_characters":{"type":"integer"},"greeting_characters":{"type":"integer"}}}}},"AgentBuildRequest":{"type":"object","additionalProperties":false,"required":["website_url","language"],"properties":{"website_url":{"type":"string","description":"Public http(s) URL the agent learns from; a bare host gets `https://`.","examples":["https://www.equalweb.com"]},"language":{"type":"string","description":"Agent language code (en, he, ar, es, fr, de, it, pt, ru, zh, ja, ko, hi, tr, nl, pl, sv).","examples":["he"]},"provider":{"type":"string","enum":["gemini","openai","gpt_live"],"default":"gemini"},"voice":{"type":"object","additionalProperties":false,"description":"`gender` is required unless `name` names a voice. The default voice is the first active voice of the gender in the provider's catalogue (gemini zephyr / puck, openai shimmer / ash).","properties":{"gender":{"type":"string","enum":["female","male"]},"name":{"type":"string","description":"A voice id of the provider (GET /voices?provider=…).","examples":["zephyr"]}}},"name":{"type":"string","maxLength":100,"description":"Agent name; default the voice's marketing name in the agent's language."},"main_role":{"type":"string","default":"secretary","description":"An active role id — secretary, customer_service, technical_support, sales, general, general_consultant, custom."},"addons":{"type":"array","items":{"type":"string","enum":["customer_support","orders","scheduling"]},"description":"Each adds its capabilities and knowledge areas to the base set."},"max_pages":{"type":"integer","minimum":1,"maximum":200,"default":15}}},"AgentBuildStep":{"type":"object","required":["status","started_at","finished_at","detail","error"],"properties":{"status":{"type":"string","enum":["pending","running","done","failed","skipped"]},"started_at":{"type":["string","null"],"format":"date-time"},"finished_at":{"type":["string","null"],"format":"date-time"},"detail":{"type":["object","null"],"description":"What the step produced — e.g. `pages_saved` (crawl), `summary_characters` (knowledge), `greeting`, `instruction_characters`. The `instructions` step also carries `refinement` (per surface `phone` / `chat` / `voice`: the normalised `rules` injected, `conflicts` as pairs of the stored rules with a one-line explanation, `unclear` rules with the reason, `source`), `prompt_sizes` (total / platform / body / budget / bodyBudget / overBudget / ceiling / overCeiling / compressionPasses per surface — a prompt over its budget after two compression passes is kept whole, accepted under the ceiling with a warning, named above it) and `warnings`.","additionalProperties":true},"error":{"type":["string","null"]}}},"AgentBuildState":{"type":"object","required":["status","step","steps","input","started_at","finished_at","requested_by","warnings","error","result"],"properties":{"status":{"type":"string","enum":["building","done","error"]},"step":{"type":"string","enum":["create","crawl","knowledge","greeting","instructions","ready"],"description":"The step running now, or the one the build stopped on."},"steps":{"type":"object","required":["create","crawl","knowledge","greeting","instructions","ready"],"properties":{"create":{"$ref":"#/components/schemas/AgentBuildStep"},"crawl":{"$ref":"#/components/schemas/AgentBuildStep"},"knowledge":{"$ref":"#/components/schemas/AgentBuildStep"},"greeting":{"$ref":"#/components/schemas/AgentBuildStep"},"instructions":{"$ref":"#/components/schemas/AgentBuildStep"},"ready":{"$ref":"#/components/schemas/AgentBuildStep"}}},"input":{"type":"object","description":"The request after defaults were applied.","properties":{"website_url":{"type":"string"},"language":{"type":"string"},"provider":{"type":"string"},"voice":{"type":"object","properties":{"gender":{"type":"string"},"name":{"type":"string"}}},"name":{"type":"string"},"main_role":{"type":"string"},"addons":{"type":"array","items":{"type":"string"}},"max_pages":{"type":"integer"}}},"started_at":{"type":"string","format":"date-time"},"finished_at":{"type":["string","null"],"format":"date-time"},"requested_by":{"type":["string","null"],"description":"The credential that asked (key_42, master)."},"warnings":{"type":"array","items":{"type":"string"},"description":"Non-fatal notes — the greeting fell back to the stock line; a refinement-rule conflict or an unclear rule (prefixed with the surface, e.g. `phone prompt: refinement rules conflict — …`); a prompt still over its character budget after one compression; a widget prompt kept from the previous build."},"error":{"type":["string","null"]},"result":{"type":["object","null"],"properties":{"pages_crawled":{"type":["integer","null"]},"summary_characters":{"type":["integer","null"]},"greeting":{"type":["string","null"]},"description":{"type":["string","null"],"description":"The agent's short description, generated with the greeting (agents.description)."},"instruction_characters":{"type":["integer","null"]}}}}},"AgentBuildSnapshot":{"type":"object","description":"The agent row's own record of the build — survives restarts.","properties":{"website_url":{"type":["string","null"]},"config_status":{"type":["string","null"],"description":"draft → building → complete | error"},"onboarding_step":{"type":["integer","null"],"description":"1 after create, 7 when built."},"greeting":{"type":["string","null"]},"instruction_characters":{"type":["integer","null"]},"has_chat_prompt":{"type":"boolean"},"has_voice_prompt":{"type":"boolean"},"kb":{"$ref":"#/components/schemas/KbStatus"}}},"QuotaCounter":{"type":"object","required":["limit","used"],"properties":{"limit":{"type":"integer","description":"0 = no quota"},"used":{"type":"integer","description":"Consumed since 00:00 UTC"}}},"PartnerMemberCreate":{"type":"object","additionalProperties":false,"required":["company_name","email","password","website_url","language","voice"],"properties":{"company_name":{"type":"string","minLength":1,"maxLength":255},"email":{"type":"string","format":"email","maxLength":255},"password":{"type":"string","writeOnly":true,"minLength":8,"maxLength":200},"website_url":{"type":"string","maxLength":2048,"description":"Public http(s) site; validated before anything is created"},"language":{"type":"string","minLength":2,"maxLength":10,"example":"he","description":"One of the 17 agent language codes — en, he, es, fr, de, it, pt, ru, ar, zh, ja, ko, hi, tr, nl, pl, sv (enums.md). A region suffix such as pt-BR or zh-TW is accepted and reduced to its language."},"voice":{"type":"object","required":["gender"],"properties":{"gender":{"type":"string","enum":["female","male"]},"name":{"type":"string","description":"A voice id from GET /voices"}}},"wallet_credit_usd":{"type":"number","minimum":0,"maximum":100000,"description":"Overrides the configured partner credit (default 89 USD); 0 = none"},"widget_enabled":{"type":"boolean","default":true,"description":"false leaves the widget switched off; the domain and token are still set"},"partner":{"type":"string","maxLength":100,"description":"Which partner this member belongs to; stamped on external_ids.partner. Default PUBLIC_API_DEFAULT_PARTNER."},"external_ids":{"type":"object","additionalProperties":{"type":"string","maxLength":400}},"timezone":{"type":"string","maxLength":100},"currency_code":{"type":"string","minLength":3,"maxLength":3},"max_pages":{"type":"integer","minimum":1,"maximum":200}}},"PartnerMember":{"type":"object","required":["user","agent_id","build","widget","wallet_balance_usd","login_url","login_url_expires_at","login_lands_on","demo_url"],"properties":{"user":{"$ref":"#/components/schemas/User"},"agent_id":{"type":["string","null"]},"build":{"type":["object","null"],"description":"The build's step states (as GET /agents/{agt}/build); null when not in memory","properties":{"status":{"type":"string","enum":["building","done","error","unknown"]}}},"agent":{"type":["object","null"],"description":"The agent row's own record of the build"},"widget":{"type":["object","null"],"description":"The widget, ready to paste (2026-09-06): `allowed_domains` is the apex host of\n`website_url` (the origin check accepts it and every subdomain), `public_token` is\nminted and `enabled` is true unless `widget_enabled: false` was sent.\n","properties":{"public_token":{"type":["string","null"]},"embed_snippet":{"type":["string","null"]},"enabled":{"type":"boolean"},"allowed_domains":{"type":"array","items":{"type":"string"}}}},"wallet_balance_usd":{"type":"number"},"login_url":{"type":"string","format":"uri","description":"Single use; treat as a secret"},"demo_url":{"type":["string","null"],"format":"uri","description":"The agent demo page: the customer's site with the widget open, no login. Ready when build.status is done. Null only when the widget has no token yet."},"login_url_expires_at":{"type":"string","format":"date-time"},"login_lands_on":{"type":"string"},"meta":{"$ref":"#/components/schemas/Meta"}}},"CustomCapability":{"type":"object","description":"One customer-defined tool (`custom_capabilities`), as listed on the full agent object since 2026-09-10.","required":["id","capability_key","function_name","name","description","status","live","connector_status","scopes","usage_count","last_used_at","created_at"],"properties":{"id":{"type":"string","examples":["ccap_40"]},"capability_key":{"type":"string","examples":["custom_serpapi_get_google_indexed_page_count_1772719544134_0"]},"function_name":{"type":"string","description":"The tool name the model sees — the name a `tool` turn carries in the transcript.","examples":["serpapi_get_google_indexed_page_count"]},"name":{"type":["string","null"]},"description":{"type":["string","null"]},"status":{"type":"string","enum":["active","draft"]},"live":{"type":"boolean","description":"True only when `status` is `active` AND the connector is `connected` — the rule every runtime applies before injecting the tool."},"connector_status":{"type":["string","null"],"description":"The bound connector's state; null when none is bound."},"scopes":{"type":"array","items":{"type":"string"},"description":"`[\"all\"]`, or any of `incoming_calls`, `outbound_calls`, `public_chat`, `public_voice`."},"usage_count":{"type":"integer"},"last_used_at":{"type":["string","null"],"format":"date-time"},"created_at":{"type":["string","null"],"format":"date-time"}}},"KbDocumentVerbatimPatch":{"type":"object","additionalProperties":false,"properties":{"keep_verbatim":{"type":"boolean"},"verbatim_in_calls":{"type":"boolean","description":"Only counts while keep_verbatim is true."}}},"ImportDetail":{"type":"object","required":["external_session_id","direction","outcome","imported_at","updated_at","revision","recording_sid"],"properties":{"external_session_id":{"type":["string","null"],"description":"The importer's own id of the call (the idempotency key)."},"direction":{"type":"string","enum":["outbound","inbound"]},"outcome":{"type":["string","null"],"description":"The importer's free-text outcome, verbatim."},"imported_at":{"type":["string","null"],"format":"date-time"},"updated_at":{"type":["string","null"],"format":"date-time","description":"Last upsert; null on a record written once."},"revision":{"type":"integer","description":"Number of writes (1 = imported once)."},"recording_sid":{"type":["string","null"],"description":"Twilio recording sid when a recording was sent."}}},"ImportTurn":{"type":"object","required":["role","text"],"properties":{"role":{"type":"string","enum":["agent","user"]},"text":{"type":"string","maxLength":4000,"description":"The words as spoken."},"at":{"type":"string","format":"date-time","description":"Wall-clock time of the turn. Optional — without it the turn has no timestamp, latency or audio offset."}}},"ConversationImportRequest":{"type":"object","required":["agent_id","identity","started_at","ended_at","external_session_id"],"properties":{"agent_id":{"type":"string","description":"The agent whose call log the call joins (agt_…)."},"account_id":{"type":"string","description":"Optional when the credential has one account."},"channel":{"type":"string","enum":["phone"],"default":"phone"},"direction":{"type":"string","enum":["outbound","inbound"],"default":"outbound","description":"`outbound` — you called the number (agent_type `phone_outbound`); `inbound` — the number called you (agent_type `phone`)."},"identity":{"type":"object","required":["phone_e164"],"properties":{"phone_e164":{"type":"string","description":"The customer's number, E.164 — the key the returning-call lookup matches on."},"email":{"type":"string","format":"email"},"email_source":{"type":"string","enum":["in_conversation","lead_form","authenticated","crm_prefill"]},"name":{"type":"string"},"company":{"type":"string"},"caller_id_name":{"type":"string"},"called_number_e164":{"type":"string","description":"Your own number on the call (the caller id you used, or the line that was called)."},"country":{"type":"string","description":"ISO-3166-1 alpha-2; derived from the number when omitted."},"custom":{"type":"object","additionalProperties":{"type":["string","number","boolean"]},"description":"Free-form key/value, e.g. your customer id or domain (at most 20 keys)."},"do_not_sell":{"type":"boolean"},"equalweb_agent_id":{"type":"string"}}},"started_at":{"type":"string","format":"date-time"},"ended_at":{"type":"string","format":"date-time"},"duration_s":{"type":"integer","minimum":0,"description":"Defaults to ended_at − started_at."},"turns":{"type":"array","maxItems":500,"items":{"$ref":"#/components/schemas/ImportTurn"},"description":"The transcript as spoken, in order."},"summary":{"type":"string","maxLength":4000,"description":"Short text — what was offered, what the customer said, the next step, any code given. This is what the agent reads on the next call. Omitted → the platform writes one from the turns within minutes (then `summary_status` is `pending` at first)."},"outcome":{"type":"string","maxLength":80,"description":"Free text, kept verbatim in channel_detail.import.outcome; when it is one of the summary outcome enum values it is set on the summary too."},"language":{"type":"string","maxLength":35,"description":"BCP-47 of the call."},"external_session_id":{"type":"string","minLength":1,"maxLength":128,"description":"Your id of the call — the idempotency key. A repeat is 409 import_exists unless upsert is true."},"upsert":{"type":"boolean","default":false,"description":"Replace an earlier import with the same external_session_id in place (same conversation_id, answers 200). Identity fields are merged; everything else follows the request."},"recording_url":{"type":"string","maxLength":500,"description":"Twilio RecordingSid (RE…) or RecordingUrl of the call. Only a recording in the platform's Twilio account can be played (the dashboard and GET /conversations/{conv}/recording stream it like a native call); another account is 400."},"twilio_call_sid":{"type":"string","maxLength":64,"description":"Twilio call sid (CA…) of the call, when it went through Twilio."}}},"ConversationImportResponse":{"allOf":[{"$ref":"#/components/schemas/ConversationDetail"},{"type":"object","required":["warnings"],"properties":{"warnings":{"type":"array","description":"Empty, or what the importer should know — e.g. a summary longer than the 600 characters the agent reads.","items":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","enum":["summary_truncated_for_agent"]},"message":{"type":"string"}}}}}}]},"AdminServiceProbe":{"type":"object","description":"One process's `GET /health`, probed with a 2-second timeout.","properties":{"name":{"type":"string","enum":["dashboard","admin","twilio","outbound"]},"port":{"type":"integer"},"url":{"type":"string"},"status":{"type":"string","enum":["up","down"]},"http_status":{"type":["integer","null"]},"ms":{"type":"integer"},"detail":{"type":["object","null"],"additionalProperties":true,"description":"The health body, secrets scrubbed"},"error":{"type":["string","null"]}}},"AdminOverview":{"type":"object","properties":{"customers":{"type":["object","null"],"properties":{"total":{"type":"integer"},"active":{"type":"integer"},"trial":{"type":"integer"},"suspended":{"type":"integer"},"payg":{"type":"integer"},"new_last_30d":{"type":"integer"},"partner_members":{"type":"integer"},"logged_in_last_30d":{"type":"integer"}}},"agents":{"type":["object","null"],"properties":{"total":{"type":"integer"},"complete":{"type":"integer"},"large_tier":{"type":"integer"},"widget_enabled":{"type":"integer"},"with_phone_number":{"type":"integer"},"new_last_30d":{"type":"integer"},"by_provider":{"type":"object","additionalProperties":{"type":"integer"}},"by_language_top5":{"type":"object","additionalProperties":{"type":"integer"}},"by_config_status":{"type":"object","additionalProperties":{"type":"integer"}}}},"conversations":{"type":["object","null"],"description":"Over `vw_ext_conversations`; channels `phone`, `widget`, `api`, `dashboard`.","properties":{"last_24h":{"$ref":"#/components/schemas/AdminConversationWindow"},"last_7d":{"$ref":"#/components/schemas/AdminConversationWindow"},"last_30d":{"$ref":"#/components/schemas/AdminConversationWindow"},"active_now":{"type":"integer"}}},"usage":{"type":["object","null"],"properties":{"last_7d":{"$ref":"#/components/schemas/AdminUsageWindow"},"last_30d":{"$ref":"#/components/schemas/AdminUsageWindow"}}},"revenue":{"type":["object","null"],"properties":{"last_30d":{"type":"object","properties":{"usd":{"type":"number"},"transactions":{"type":"integer"},"wallet_topups_usd":{"type":"number"},"plans_usd":{"type":"number"}}},"status_counted":{"type":"string","example":"completed"}}},"wallet":{"type":["object","null"],"properties":{"total_balance_usd":{"type":"number"},"accounts_positive":{"type":"integer"},"accounts_negative":{"type":"integer"},"negative_total_usd":{"type":"number"}}},"top_accounts_30d":{"type":"array","items":{"type":"object","properties":{"account_id":{"type":"string","example":"acc_1001"},"email":{"type":["string","null"]},"name":{"type":["string","null"]},"company_name":{"type":["string","null"]},"billed_usd":{"type":"number"},"cost_usd":{"type":"number"},"sessions":{"type":"integer"}}}},"open_items":{"type":["object","null"],"properties":{"webhook_outbox_pending":{"type":"integer"},"webhook_outbox_oldest_at":{"type":["string","null"],"format":"date-time"},"webhook_deliveries_failed_24h":{"type":"integer"},"webhook_deliveries_dead_24h":{"type":"integer"},"summaries_pending_over_15_min":{"type":"integer"},"agents_building":{"type":"integer"},"webhook_endpoints_auto_disabled":{"type":"integer"}}},"services":{"type":"array","items":{"$ref":"#/components/schemas/AdminServiceProbe"}},"api_health":{"oneOf":[{"$ref":"#/components/schemas/Health"},{"type":"null"}]},"problems":{"type":"array","items":{"type":"string"},"description":"Sections that timed out or failed"},"generated_at":{"type":"string","format":"date-time"},"meta":{"$ref":"#/components/schemas/Meta"}}},"AdminConversationWindow":{"type":"object","properties":{"total":{"type":"integer"},"by_channel":{"type":"object","additionalProperties":{"type":"integer"}}}},"AdminUsageWindow":{"type":"object","properties":{"cost_usd":{"type":"number","description":"What the model calls cost"},"billed_usd":{"type":"number","description":"What accounts were charged (`billing_cost_usd`, falling back to the cost)"},"sessions":{"type":"integer"}}},"AdminCustomerRow":{"allOf":[{"$ref":"#/components/schemas/User"},{"type":"object","properties":{"plan_id":{"type":["integer","null"]},"plan_name":{"type":["string","null"]},"plan_price":{"type":["number","null"]},"plan_expires_at":{"type":["string","null"],"format":"date-time"},"subscription_status":{"type":["string","null"]},"payment_gateway":{"type":["string","null"],"description":"`cardcom` when a card is on file"},"wallet_balance":{"type":"number"},"monthly_spending":{"type":"number"},"agents_count":{"type":"integer"},"last_conversation_at":{"type":["string","null"],"format":"date-time"},"last_login":{"type":["string","null"],"format":"date-time"}}}]},"AdminCustomerOverview":{"type":"object","properties":{"user":{"allOf":[{"$ref":"#/components/schemas/User"},{"type":"object","properties":{"last_login":{"type":["string","null"],"format":"date-time"},"plan_expires_at":{"type":["string","null"],"format":"date-time"}}}]},"balance":{"$ref":"#/components/schemas/Balance"},"limits":{"$ref":"#/components/schemas/AccountLimits"},"partner":{"type":"object","properties":{"partner":{"type":["string","null"]},"member_since":{"type":["string","null"]},"equalweb_customer_id":{"type":["string","null"]},"is_member":{"type":"boolean"}}},"agents":{"type":"array","items":{"allOf":[{"$ref":"#/components/schemas/AgentSummary"},{"type":"object","properties":{"ai_provider":{"type":"string"},"use_large_model":{"type":"boolean"},"agent_language":{"type":["string","null"]},"voice":{"type":["string","null"]},"config_status":{"type":["string","null"]},"public_chat_enabled":{"type":"boolean"},"public_voice_enabled":{"type":"boolean"},"widget_enabled":{"type":"boolean"},"knowledge":{"type":"object","properties":{"files":{"type":"integer"},"pages":{"type":"integer"},"has_summary":{"type":"boolean"},"summary_stale":{"type":"boolean"}}}}}]}},"api_keys":{"type":"array","items":{"$ref":"#/components/schemas/ApiKey"}},"webhook_endpoints":{"type":"array","items":{"$ref":"#/components/schemas/WebhookEndpoint"}},"usage":{"type":"object","properties":{"last_7d":{"type":"object","additionalProperties":true},"last_30d":{"type":"object","additionalProperties":true},"by_agent_type_30d":{"type":"array","items":{"type":"object","additionalProperties":true}}}},"wallet_history":{"type":"array","items":{"$ref":"#/components/schemas/AdminWalletHistoryEntry"}},"subscription":{"type":["object","null"],"additionalProperties":true,"description":"The newest subscriptions row with its plan — no card data"},"transactions":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"The last 5 — no gateway payloads, no card token"},"invoices":{"type":"array","items":{"type":"object","additionalProperties":true}},"generated_at":{"type":"string","format":"date-time"},"meta":{"$ref":"#/components/schemas/Meta"}}},"AdminWalletHistoryEntry":{"type":"object","properties":{"entry_id":{"type":"integer"},"amount_usd":{"type":"number"},"type":{"type":["string","null"],"example":"topup"},"balance_before":{"type":["number","null"]},"balance_after":{"type":["number","null"]},"description":{"type":["string","null"]},"reference_id":{"type":["integer","null"]},"created_at":{"type":["string","null"],"format":"date-time"}}},"AdminAgentRow":{"type":"object","properties":{"agent_id":{"type":"string","example":"agt_245"},"account_id":{"type":"string","example":"acc_1001"},"name":{"type":["string","null"]},"display_name":{"type":["string","null"]},"owner_email":{"type":["string","null"]},"owner_company":{"type":["string","null"]},"ai_provider":{"type":"string","enum":["openai","gemini","gpt_live"]},"use_large_model":{"type":"boolean"},"tier":{"type":"string","enum":["standard","large"]},"agent_language":{"type":["string","null"]},"voice":{"type":["string","null"]},"config_status":{"type":["string","null"]},"enabled":{"type":"boolean","description":"`config_status = complete`"},"public_chat_enabled":{"type":"boolean"},"public_voice_enabled":{"type":"boolean"},"widget_enabled":{"type":"boolean"},"phone_number":{"type":["string","null"]},"agent_version":{"type":["string","null"]},"cost_30d":{"type":"object","properties":{"billed_usd":{"type":"number"},"cost_usd":{"type":"number"},"sessions":{"type":"integer"}}},"created_at":{"type":["string","null"],"format":"date-time"},"updated_at":{"type":["string","null"],"format":"date-time"}}},"AdminSystemStatus":{"type":"object","properties":{"services":{"type":"array","items":{"$ref":"#/components/schemas/AdminServiceProbe"}},"api_health":{"oneOf":[{"$ref":"#/components/schemas/Health"},{"type":"null"}]},"workers":{"type":"object","properties":{"env":{"type":"object","additionalProperties":true},"phase2":{"type":"object","properties":{"enabled":{"type":"boolean"},"tick_ms":{"type":"integer"},"last_tick":{"type":["object","null"],"additionalProperties":true,"description":"The last completed tick of this process — `at`, `duration_ms`, `ticks_run`, `stats`"}}},"phase3":{"type":"object","properties":{"enabled":{"type":"boolean"},"tick_ms":{"type":"integer"},"retention_sweep":{"type":"boolean"}}}}},"queues":{"type":["object","null"],"properties":{"webhook_outbox":{"type":"object","additionalProperties":true},"webhook_deliveries":{"type":"object","additionalProperties":true},"summaries":{"type":"object","additionalProperties":true},"api_calls":{"type":"object","additionalProperties":true},"builds_in_progress":{"type":"object","additionalProperties":true}}},"config_problems":{"type":"object","properties":{"master_key_problem":{"type":"boolean"},"master_key_message":{"type":["string","null"]},"missing_secrets":{"type":"array","items":{"type":"string"},"description":"Names only"},"missing_optional_secrets":{"type":"array","items":{"type":"string"}},"recording_secret_configured":{"type":"boolean"},"cursor_secret_configured":{"type":"boolean"},"sandbox_account_configured":{"type":"boolean"}}},"db":{"type":"object","properties":{"ok":{"type":"boolean"},"latency_ms":{"type":["integer","null"]}}},"process":{"type":"object","additionalProperties":true},"generated_at":{"type":"string","format":"date-time"},"meta":{"$ref":"#/components/schemas/Meta"}}},"AdminErrorTail":{"type":"object","properties":{"service":{"type":"string"},"folder":{"type":"string"},"log_root":{"type":"string"},"minutes":{"type":"integer"},"limit":{"type":"integer"},"available":{"type":"boolean","description":"The service folder exists"},"file":{"type":["string","null"],"description":"The file read, relative to the service folder"},"file_modified_at":{"type":"string","format":"date-time"},"file_size_bytes":{"type":"integer"},"scanned_bytes":{"type":"integer"},"scanned_whole_file":{"type":"boolean"},"files_available":{"type":"array","items":{"type":"string"}},"count":{"type":"integer"},"truncated":{"type":"boolean"},"lines":{"type":"array","items":{"type":"object","properties":{"at":{"type":["string","null"],"format":"date-time"},"text":{"type":"string","description":"Secrets scrubbed"}}}},"meta":{"$ref":"#/components/schemas/Meta"}}},"AdminConfigSnapshot":{"type":"object","properties":{"flags":{"type":"object","properties":{"GPT_LIVE_ENABLED":{"type":"boolean"},"EXT_WORKERS":{"type":"boolean"},"EXT_RETENTION_SWEEP":{"type":"boolean"},"OUTBOUND_SESSION_POOL":{"type":"boolean"},"IMAP_ENABLED":{"type":"boolean"},"EQUALWEB_OAUTH":{"type":"boolean","description":"Both client id and secret present"}}},"flags_note":{"type":"string"},"ai_config":{"type":"object","description":"Per tier, per section — `model`, `reasoningEffort` and the `*Model` family.","properties":{"standard":{"type":"object","additionalProperties":{"type":"object","additionalProperties":true}},"large":{"type":"object","additionalProperties":{"type":"object","additionalProperties":true}}}},"model_pricing":{"type":"object","additionalProperties":true},"env":{"type":"object","description":"Allow-listed values only — never a secret.","additionalProperties":{"type":["string","null"]}},"versions":{"type":"object","properties":{"services":{"type":"object","additionalProperties":{"type":"object","properties":{"name":{"type":["string","null"]},"version":{"type":["string","null"]}}}},"git":{"type":"object","properties":{"commit":{"type":["string","null"]},"branch":{"type":["string","null"]}}}}},"files":{"type":"object","additionalProperties":true},"generated_at":{"type":"string","format":"date-time"},"meta":{"$ref":"#/components/schemas/Meta"}}},"AdminAction":{"type":"object","properties":{"action_id":{"type":"string","example":"adm_17"},"request_id":{"type":"string","description":"Joins `GET /audit`"},"action":{"type":"string","example":"wallet_credit"},"target_type":{"type":"string","enum":["user","agent","credential","config"]},"target_id":{"type":["string","integer","null"],"example":"usr_1001"},"before":{"description":"State before the write, or null"},"after":{"description":"State after the write / the answer that was returned"},"reason":{"type":["string","null"]},"idempotency_key":{"type":["string","null"]},"created_at":{"type":["string","null"],"format":"date-time"}}},"AdminMigrations":{"type":"object","properties":{"legacy":{"type":"object","properties":{"ledger":{"type":"string","example":"dbo.schema_migrations"},"runner":{"type":"string"},"dir":{"type":"string"},"applied":{"type":"array","items":{"type":"object","additionalProperties":true}},"pending":{"type":"array","items":{"type":"string"}},"drifted":{"type":"array","items":{"type":"string"},"description":"Applied files whose checksum no longer matches the ledger"},"recorded_but_missing_on_disk":{"type":"array","items":{"type":"string"}}}},"ext_api":{"type":"object","properties":{"ledger":{"type":"string","example":"dbo.ext_api_migrations"},"runner":{"type":"string"},"dir":{"type":"string"},"applied":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"applied_at":{"type":["string","null"],"format":"date-time"}}}},"pending":{"type":"array","items":{"type":"string"}},"recorded_but_missing_on_disk":{"type":"array","items":{"type":"string"}}}},"generated_at":{"type":"string","format":"date-time"},"meta":{"$ref":"#/components/schemas/Meta"}}},"AdminConfirmRequest":{"type":"object","required":["confirm"],"properties":{"reason":{"type":"string","minLength":5,"maxLength":500},"confirm":{"type":"boolean","const":true,"description":"Must be `true` — else `400 confirmation_required` and nothing happens"}}},"AdminWalletCreditRequest":{"type":"object","required":["amount_usd","reason","confirm"],"properties":{"amount_usd":{"type":"number","minimum":0.01,"description":"Up to 1,000 (PUBLIC_API_ADMIN_MAX_CREDIT_USD overrides the cap); never negative"},"reason":{"type":"string","minLength":5,"maxLength":500},"idempotency_key":{"type":"string","minLength":1,"maxLength":120,"description":"Unique across the journal; a replay answers the original result"},"confirm":{"type":"boolean","const":true}}},"AdminWalletCreditResult":{"type":"object","properties":{"user_id":{"type":"string","example":"usr_1001"},"amount_usd":{"type":"number"},"balance_before":{"type":"number"},"balance_after":{"type":"number"},"wallet_history_id":{"type":"integer"},"action_id":{"type":"string","example":"adm_17"},"replayed":{"type":"boolean"},"credited_at":{"type":"string","format":"date-time"},"meta":{"$ref":"#/components/schemas/Meta"}}},"AdminPlanChangeRequest":{"type":"object","required":["plan_id","reason","confirm"],"properties":{"plan_id":{"type":"integer","description":"`plans.id`"},"reason":{"type":"string","minLength":5,"maxLength":500},"confirm":{"type":"boolean","const":true}}},"AdminStatusRequest":{"type":"object","required":["status","reason","confirm"],"properties":{"status":{"type":"string","enum":["active","suspended"]},"reason":{"type":"string","minLength":5,"maxLength":500},"confirm":{"type":"boolean","const":true}}},"AdminLoginLinkRequest":{"type":"object","required":["confirm"],"properties":{"agent_id":{"type":"string","pattern":"^agt_[0-9]+$","description":"Land on this agent's Website-code page (must belong to the customer)"},"next":{"type":"string","maxLength":500,"description":"An in-app path; default `/dashboard`"},"reason":{"type":"string","minLength":5,"maxLength":500},"confirm":{"type":"boolean","const":true}}},"AdminLoginLink":{"type":"object","properties":{"user_id":{"type":"string"},"login_url":{"type":"string","description":"Single use, expires in minutes; never journaled"},"expires_at":{"type":"string","format":"date-time"},"lands_on":{"type":"string"},"action_id":{"type":"string"},"meta":{"$ref":"#/components/schemas/Meta"}}},"AdminCredentialPatch":{"type":"object","required":["confirm"],"properties":{"name":{"type":"string","minLength":1,"maxLength":200},"scopes":{"type":"array","minItems":1,"items":{"$ref":"#/components/schemas/Scope"},"description":"Grantable scopes only"},"expires_in_days":{"type":"integer","minimum":1,"maximum":365,"description":"Measured from now"},"ip_allowlist":{"type":"array","maxItems":50,"items":{"type":"string"},"description":"IPs / CIDRs; `[]` clears it"},"reason":{"type":"string","minLength":5,"maxLength":500},"confirm":{"type":"boolean","const":true}}},"AdminAiModelPatch":{"type":"object","required":["tier","section","model","confirm"],"properties":{"tier":{"type":"string","enum":["standard","large"]},"section":{"type":"string","description":"A section of the tier's file that has a `model`"},"model":{"type":"string","description":"Must be priced in model-pricing.json"},"reasoningEffort":{"type":"string","enum":["minimal","low","medium","high"],"description":"Only for sections that carry it"},"reason":{"type":"string","minLength":5,"maxLength":500},"confirm":{"type":"boolean","const":true}}},"AdminAiModelResult":{"type":"object","properties":{"tier":{"type":"string"},"file":{"type":"string","example":"ai-config-large.json"},"section":{"type":"string"},"before":{"type":"object","properties":{"model":{"type":"string"},"reasoningEffort":{"type":["string","null"]}}},"after":{"type":"object","properties":{"model":{"type":"string"},"reasoningEffort":{"type":["string","null"]}}},"backup":{"type":"string","example":"ai-config-large.json.bak-20260930T101500Z"},"reloaded":{"type":"array","items":{"type":"string"},"example":["dashboard:3000"]},"restart_required":{"type":"array","items":{"type":"string"},"example":["twilio:3002","outbound:3003","admin:3001"]},"action_id":{"type":"string"},"meta":{"$ref":"#/components/schemas/Meta"}}},"AdminAgentActionRequest":{"type":"object","required":["action","reason","confirm"],"properties":{"action":{"type":"string","enum":["set_provider","set_tier","set_language","set_voice","widget_enable","widget_disable","reindex_kb"]},"value":{"type":["string","number","boolean"],"description":"Required by `set_*`"},"voice":{"type":"string","maxLength":100,"description":"`set_provider` only — a voice id of the new provider, applied in the same write"},"reason":{"type":"string","minLength":5,"maxLength":500},"confirm":{"type":"boolean","const":true}}},"AdminAgentActionResult":{"type":"object","properties":{"agent_id":{"type":"string"},"account_id":{"type":"string"},"action":{"type":"string"},"value":{"description":"The value that was applied, or null"},"before":{"$ref":"#/components/schemas/AdminAgentSnapshot"},"after":{"$ref":"#/components/schemas/AdminAgentSnapshot"},"result":{"type":"object","additionalProperties":true,"description":"`agent_version` for the set_* actions, `widget_enabled` for the widget ones, `status: rebuild_requested` for reindex_kb"},"action_id":{"type":"string"},"meta":{"$ref":"#/components/schemas/Meta"}}},"AdminAgentSnapshot":{"type":"object","properties":{"ai_provider":{"type":"string"},"use_large_model":{"type":"boolean"},"agent_language":{"type":["string","null"]},"voice":{"type":["string","null"]},"widget_enabled":{"type":"boolean"},"config_status":{"type":["string","null"]},"agent_version":{"type":["string","null"]}}},"RequiredConnector":{"type":"object","description":"A connected connector the capability cannot work without (a calendar, today). `types` are\nALTERNATIVES: any one connected connector of those types satisfies it (`any_of` is always\n`true`). Read what the agent has with `GET /agents/{agt}/connectors`. Nothing is validated\nagainst it — granting the capability without the connector is accepted and answered with a\n`connector_not_connected` warning.\n","required":["types","any_of","message"],"properties":{"types":{"type":"array","items":{"type":"string"},"examples":[["google-calendar","microsoft-365-calendar"]]},"any_of":{"type":"boolean","const":true},"message":{"type":["string","null"],"description":"What to tell the user","from the catalogue.":null}}},"AgentConnector":{"type":"object","required":["connector_id","type","name","category","status","connected","calendar","connected_at","last_sync"],"properties":{"connector_id":{"type":"string","examples":["con_41"],"description":"Opaque, read-only — no endpoint takes it."},"type":{"type":"string","examples":["google-calendar"],"description":"The connector type: `google-calendar`, `microsoft-365-calendar`, `hubspot`, `http_api`, … Compare with `required_connector.types` on a capability."},"name":{"type":["string","null"],"description":"Display name; free text."},"category":{"type":["string","null"],"examples":["calendar","crm"]},"status":{"type":["string","null"],"description":"The stored state. Calendars: `active` (connected) or `pending` (OAuth started, not finished).\nHubSpot and HTTP-API connectors: `connected`. Anything else means not connected.\n"},"connected":{"type":"boolean","description":"`true` for `active` or `connected` — the test the runtime applies."},"calendar":{"description":"Calendar connectors only, and only while connected: the calendar the agent books into. `null` otherwise.","oneOf":[{"$ref":"#/components/schemas/ConnectorCalendar"},{"type":"null"}]},"connected_at":{"type":["string","null"],"format":"date-time","description":"When the connection was set up (UTC); `null` while not connected."},"last_sync":{"type":["string","null"],"format":"date-time","description":"Last synchronisation, for connectors that sync; `null` for calendars."}}},"ConnectorCalendar":{"type":"object","required":["id","name"],"properties":{"id":{"type":["string","null"],"examples":["primary"],"description":"`primary` unless the user picked another calendar; `null` when it looks like an e-mail address."},"name":{"type":["string","null"],"examples":["Sales calendar"],"description":"`null` when it looks like an e-mail address (Google names the primary calendar after its owner)."}}},"AgentConnectorList":{"type":"object","required":["data","calendars_connected","meta"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AgentConnector"}},"calendars_connected":{"type":"boolean","description":"`true` when at least one `google-calendar` or `microsoft-365-calendar` connector is `active` — what `schedule_calendar_meeting` needs."},"meta":{"$ref":"#/components/schemas/Meta"}}},"ConnectorWarning":{"type":"object","required":["code","capability","message"],"description":"A non-blocking finding on a write that succeeded. The parent object carries a top-level\n`warnings` array ONLY when there is at least one — never an empty array.\n","properties":{"code":{"type":"string","enum":["connector_not_connected"],"description":"The profile / call grants a capability whose `required_connector` is not connected on the agent."},"capability":{"type":"string","examples":["schedule_calendar_meeting"]},"message":{"type":"string"}}},"OutboundPlanUsage":{"type":"object","description":"The plan's monthly outbound-call allowance, since 2026-09-30 — the same rule the dashboard applies when a\ncampaign is created or started. Context, not a promise: `POST /calls` does not check it\n(`enforced_on_api_calls` is always `false`); its gates are the daily cap and the balance.\n","required":["limit","used","remaining","period_start","campaigns_allowed","enforced_on_api_calls"],"properties":{"limit":{"type":["integer","null"],"description":"Monthly cap; `null` when the plan has none (or the account has no subscription)."},"used":{"type":"integer","description":"`outbound_campaign` usage rows since `period_start`; calls placed through `POST /calls` are written the same way, so they count."},"remaining":{"type":["integer","null"],"description":"`max(0, limit − used)`; `null` with no cap."},"period_start":{"type":["string","null"],"format":"date-time","description":"Start of the counting period (UTC): the subscription reset date, or its creation date while the reset date lies in the future."},"campaigns_allowed":{"type":"boolean","description":"`false` on the Guest plan, which the dashboard refuses campaigns for."},"enforced_on_api_calls":{"type":"boolean","const":false}}},"TimezoneCounts":{"type":"object","description":"Contacts per time zone: an IANA name, a legacy fixed offset (`UTC+2`) on lists made in the dashboard,\nor `unknown` for contacts whose zone had to be guessed (default country, account zone, UTC).\n","additionalProperties":{"type":"integer","minimum":0},"example":{"Asia/Jerusalem":41,"America/New_York":3,"unknown":2}},"ContactListCreate":{"type":"object","additionalProperties":false,"required":["name"],"properties":{"account_id":{"type":"string"},"name":{"type":"string","minLength":1,"maxLength":255},"description":{"type":["string","null"],"maxLength":2000}}},"ContactList":{"type":"object","required":["list_id","account_id","agent_id","name","items_count","created_at"],"properties":{"list_id":{"type":"string","pattern":"^lst_[0-9]+$"},"account_id":{"type":"string"},"agent_id":{"type":"string"},"name":{"type":"string"},"description":{"type":["string","null"]},"status":{"type":["string","null"],"description":"`processing` until the first import, then `ready`."},"items_count":{"type":"integer","minimum":0,"maximum":10000},"timezones":{"allOf":[{"$ref":"#/components/schemas/TimezoneCounts"}],"description":"Only on `GET /lists/{lst}`."},"created_at":{"type":"string","format":"date-time"},"meta":{"$ref":"#/components/schemas/Meta"}}},"ContactListItem":{"type":"object","required":["item_id","phone_e164","name","timezone","status","extra"],"properties":{"item_id":{"type":"string","pattern":"^itm_[0-9]+$"},"phone_e164":{"type":"string"},"name":{"type":["string","null"]},"timezone":{"type":["string","null"]},"status":{"type":"string","enum":["new","contacted","do_not_call"]},"extra":{"type":"object","additionalProperties":true,"description":"Every other key of the imported row; `first_name` / `last_name` when they were sent."}}},"ContactImportRequest":{"type":"object","additionalProperties":false,"required":["rows"],"properties":{"account_id":{"type":"string"},"rows":{"type":"array","minItems":1,"maxItems":1000,"items":{"type":"object","required":["phone"],"additionalProperties":true,"properties":{"phone":{"type":["string","integer"],"description":"Any common format; see the operation."},"name":{"type":"string","maxLength":255},"first_name":{"type":"string"},"last_name":{"type":"string"},"external_reference":{"type":"string","maxLength":255},"timezone":{"type":"string","description":"IANA name, for example `Asia/Jerusalem`. Overrides the zone the number implies."}}}},"default_country":{"type":"string","pattern":"^[A-Za-z]{2}$","description":"ISO 3166-1 alpha-2, for example `IL`. How to read numbers written without a country prefix."}}},"ContactImportInvalidRow":{"type":"object","required":["row","phone","reason"],"properties":{"row":{"type":"integer","minimum":1,"description":"1-based position in `rows`."},"phone":{"type":["string","null"]},"reason":{"type":"string"}}},"ContactImportResult":{"type":"object","required":["list_id","received","imported","duplicates","invalid","dnc_skipped","timezones","items_count"],"description":"`received = imported + duplicates + invalid.length + dnc_skipped`.","properties":{"list_id":{"type":"string"},"received":{"type":"integer"},"imported":{"type":"integer"},"duplicates":{"type":"integer","description":"Already in the list, or repeated in this request (the first one wins)."},"invalid":{"type":"array","items":{"$ref":"#/components/schemas/ContactImportInvalidRow"}},"dnc_skipped":{"type":"integer","description":"On the account do-not-call list; not imported."},"timezones":{"$ref":"#/components/schemas/TimezoneCounts"},"items_count":{"type":"integer","description":"The list size after this import."},"warnings":{"type":"array","items":{"type":"string"}},"meta":{"$ref":"#/components/schemas/Meta"}}},"CampaignStatus":{"type":"string","description":"`draft` created, not started · `running` · `paused` · `stopped` (final, by request) · `completed` (the\nqueue is drained) · `failed` · `cancelled` · `archived` (hidden in the dashboard) · `scheduled` (dashboard\nscheduling; the API never sets it).\n","enum":["draft","scheduled","running","paused","stopped","completed","failed","cancelled","archived"]},"CampaignCallStatus":{"type":"string","description":"`pending` waiting (including for a retry) · `in_progress` dialling or talking · `completed` answered\nand finished · `voicemail` an answering machine got the call · `no_answer` · `busy` · `failed` ·\n`cancelled` (stopped, or the number went on the do-not-call list).\n","enum":["pending","in_progress","completed","voicemail","no_answer","busy","failed","cancelled"]},"CampaignWindow":{"type":"object","additionalProperties":false,"required":["from","to"],"description":"The calling hours, in the RECIPIENT's local time. `from` is earlier than `to`; overnight windows are not supported.","properties":{"from":{"type":"string","pattern":"^([01]\\d|2[0-3]):[0-5]\\d$"},"to":{"type":"string","pattern":"^([01]\\d|2[0-3]):[0-5]\\d$"}}},"CampaignCreate":{"type":"object","additionalProperties":false,"required":["agent_id","list_id","profile_id","name"],"properties":{"account_id":{"type":"string"},"agent_id":{"type":"string"},"list_id":{"type":"string","description":"A list of this agent."},"profile_id":{"type":"string","description":"An active outbound profile (`prf_…`) of this agent — the script the calls run under."},"name":{"type":"string","minLength":1,"maxLength":255},"days":{"type":"array","minItems":1,"maxItems":7,"uniqueItems":true,"items":{"type":"integer","minimum":0,"maximum":6},"default":[1,2,3,4,5],"description":"0 = Sunday … 6 = Saturday, the recipient's local calendar."},"window":{"allOf":[{"$ref":"#/components/schemas/CampaignWindow"}],"default":{"from":"09:00","to":"18:00"}},"start_date":{"type":["string","null"],"format":"date","description":"`start` is refused before this day (UTC)."},"end_date":{"type":["string","null"],"format":"date","description":"Not in the past, not before `start_date`. `start` is refused after it. A deadline for starting, not a scheduler: nothing starts by itself."},"max_calls_per_hour":{"type":"integer","minimum":1,"maximum":300,"default":30},"max_concurrent_calls":{"type":"integer","minimum":1,"maximum":5,"default":1},"max_attempts":{"type":"integer","minimum":1,"maximum":3,"default":2,"description":"Attempts per contact; a retry follows a no answer, a busy line or a failure."}}},"CampaignProgress":{"type":"object","required":["total","pending","in_progress","completed","no_answer","busy","voicemail","failed","cancelled"],"properties":{"total":{"type":"integer"},"pending":{"type":"integer"},"in_progress":{"type":"integer"},"completed":{"type":"integer"},"no_answer":{"type":"integer"},"busy":{"type":"integer"},"voicemail":{"type":"integer"},"failed":{"type":"integer"},"cancelled":{"type":"integer"}}},"CampaignReport":{"type":"object","properties":{"total_calls":{"type":"integer"},"outcomes":{"type":"object","properties":{"completed":{"type":"integer"},"failed":{"type":"integer"},"no_answer":{"type":"integer"},"busy":{"type":"integer"},"voicemail":{"type":"integer"}}},"avg_duration_seconds":{"type":"number"},"avg_rating":{"type":["number","null"]},"cost_usd":{"type":"number"}}},"Campaign":{"type":"object","required":["campaign_id","status","agent_id","list_id","profile_id","name","days","window","max_calls_per_hour","max_concurrent_calls","max_attempts"],"properties":{"campaign_id":{"type":"string","pattern":"^cmp_[0-9]+$"},"account_id":{"type":"string"},"status":{"$ref":"#/components/schemas/CampaignStatus"},"agent_id":{"type":"string"},"list_id":{"type":["string","null"]},"profile_id":{"type":["string","null"]},"name":{"type":"string"},"days":{"type":"array","items":{"type":"integer","minimum":0,"maximum":6}},"window":{"$ref":"#/components/schemas/CampaignWindow"},"start_date":{"type":["string","null"],"format":"date"},"end_date":{"type":["string","null"],"format":"date"},"max_calls_per_hour":{"type":"integer"},"max_concurrent_calls":{"type":"integer"},"max_attempts":{"type":"integer"},"progress":{"$ref":"#/components/schemas/CampaignProgress"},"report":{"allOf":[{"$ref":"#/components/schemas/CampaignReport"}],"description":"Only on `GET /campaigns/{cmp}`."},"created_at":{"type":"string","format":"date-time"},"meta":{"$ref":"#/components/schemas/Meta"}}},"CampaignCreated":{"allOf":[{"$ref":"#/components/schemas/Campaign"},{"type":"object","required":["contacts","dnc_skipped","timezones"],"properties":{"contacts":{"type":"integer","description":"Contacts queued."},"dnc_skipped":{"type":"integer","description":"Contacts of the list left out because their number is on the do-not-call list."},"timezones":{"$ref":"#/components/schemas/TimezoneCounts"},"warnings":{"type":"array","items":{"type":"string"}}}}]},"CampaignCall":{"type":"object","required":["queued_call_id","phone_e164","name","status","attempts","outcome","called_at","duration_seconds","conversation_id"],"properties":{"queued_call_id":{"type":"string","pattern":"^qcl_[0-9]+$"},"phone_e164":{"type":"string"},"name":{"type":["string","null"]},"status":{"$ref":"#/components/schemas/CampaignCallStatus"},"attempts":{"type":"integer"},"outcome":{"type":["string","null"],"description":"The call outcome from its summary (`lead_captured`, …), or what the dialer recorded; null until there is one."},"answered_by":{"type":["string","null"]},"called_at":{"type":["string","null"],"format":"date-time"},"duration_seconds":{"type":["integer","null"]},"next_attempt_at":{"type":["string","null"],"format":"date-time","description":"When a waiting retry becomes due."},"conversation_id":{"type":["string","null"],"pattern":"^conv_u[0-9]+$"}}},"CampaignActionResult":{"type":"object","required":["campaign_id","status"],"properties":{"campaign_id":{"type":"string"},"status":{"$ref":"#/components/schemas/CampaignStatus"},"changed":{"type":"boolean","description":"false when the campaign was already in the requested state."},"dnc_cancelled":{"type":"integer","description":"Only on `start`, when contacts were cancelled because they went on the do-not-call list."},"meta":{"$ref":"#/components/schemas/Meta"}}}},"x-uirix-delivery-headers":{"Content-Type":"application/json","User-Agent":"uirix-webhooks/1.0","X-Uirix-Event":"conversation.ended","X-Uirix-Delivery":"dlv_01J8Z9M4T7QX3R","X-Uirix-Timestamp":"1787412127","X-Uirix-Signature":"sha256=<hex HMAC-SHA256 of \"{timestamp}.{rawBody}\">","X-Uirix-Signature-Previous":"sha256=… (only during a secret rotation)","X-Request-Id":"req_01J8Z9M4T8"},"x-uirix-retry-schedule":{"description":"After the first attempt: 1 min, 5 min, 15 min, 1 h, 6 h, 24 h, each with\n±20 % jitter — 7 attempts over roughly 31 hours. Overridable for tests\nwith `EXT_WEBHOOK_RETRY_SCHEDULE_MS` (comma-separated milliseconds).\nAfter attempt 7 the delivery is `failed` with `dead_at`; 50 consecutive\ndead deliveries disable the endpoint.\n","attempts":7,"delays_ms":[60000,300000,900000,3600000,21600000,86400000],"jitter":0.2,"auto_disable_after":50}}}