Back to app

API explorer: every endpoint on one page, with a "Test request" button. Paste your API key once, send real requests, copy the curl.

Open the API explorer

API reference

Generated by tools/gen_api_docs.py from app/api. Do not edit by hand.

Base URL: https://app.callqa.dev/api/v1. Version: 1.0.0. 124 endpoints.

  • Try every endpoint in your browser: API explorer. Paste your API key once, then send real requests and copy the curl of each one.
  • Machine readable: api/openapi.json (OpenAPI 3.1, import it in Postman, Insomnia or a client generator). Live schema: /api/v1/openapi.json.
  • Every request needs Authorization: Bearer <API key>. See Authentication.
  • Every endpoint can also answer 401, 422 (invalid input) and 429; endpoints that need a scope answer 403 when the key lacks it. See Errors.

Scope is the API key scope the endpoint needs (admin implies all). Roles are the dashboard roles allowed to do the same thing in the UI.

Index

Organization

Method Path Summary Scope Roles Success
GET /api/v1/audit-log Audit log (admin) admin admin 200
GET /api/v1/exports List exports admin admin 200
POST /api/v1/exports Request an export admin admin 200, 201
GET /api/v1/exports/{export_id} Get an export admin admin 200
POST /api/v1/invitations Invite a member admin admin 201
GET /api/v1/me Who am I read admin, member, guest 200
GET /api/v1/members List members read admin, member, guest 200
PATCH /api/v1/members/{member_id} Change a member's role admin admin 200
DELETE /api/v1/members/{member_id} Remove a member admin admin 204

GET /api/v1/audit-log

Audit log (admin).

Who did what in your organization, newest first: members, keys, webhooks, alert channels, billing, number verification, deletions. Never contains secrets. Filter by action (exact, or a prefix ending with . such as webhook.), actor_api_key_prefix and since / until.

Parameter In Type Required Default
action query string no
actor_api_key_prefix query string no
since query string (date-time) no
until query string (date-time) no
limit query integer no 50
offset query integer no 0

Responses: 200 Page_AuditOut_, 401, 403, 422, 429.

GET /api/v1/exports

List exports.

Parameter In Type Required Default
limit query integer no 20
offset query integer no 0

Responses: 200 Page_ExportOut_, 401, 403, 422, 429.

POST /api/v1/exports

Request an export.

A full export of your organization (admin scope): a zip of JSON files with the organization and members, numbers and attestations, scenarios and test cases, runs, one file per call (trace, transcript, result), releases, tags, audit log, legal acceptances, usage and wallet ledger. Secrets are never exported. The archive is built in the background: poll GET /exports/{id} until status is done, then download download_url (signed, valid 24 hours, no API key needed). Archives are deleted after 7 days. If an export is already being built, it is returned (200).

Responses: 200 ExportOut, 201 ExportOut, 401, 403, 409 Error, 422, 429.

GET /api/v1/exports/{export_id}

Get an export.

download_url is set when status is done (a fresh 24 h link on every call).

Parameter In Type Required Default
export_id path string (uuid) yes

Responses: 200 ExportOut, 401, 403, 404, 422, 429.

POST /api/v1/invitations

Invite a member.

Sends an invitation email valid 7 days. Seats are limited by the plan: when they are all taken (pending invitations count), 403 feature_not_in_plan with required_plan and upgrade_url.

Body (application/json): InvitationIn.

Responses: 201 object, 400 Error, 401, 403 Error, 422, 429.

GET /api/v1/me

Who am I.

The organization, plan and scopes of the API key used for the call.

Responses: 200 MeOut, 401, 403, 422, 429.

GET /api/v1/members

List members.

Responses: 200 list of MemberOut, 401, 403, 422, 429.

PATCH /api/v1/members/{member_id}

Change a member's role.

Parameter In Type Required Default
member_id path integer yes

Body (application/json): RoleIn.

Responses: 200 MemberOut, 400 Error, 401, 403, 404, 422, 429.

DELETE /api/v1/members/{member_id}

Remove a member.

Parameter In Type Required Default
member_id path integer yes

Responses: 204, 400 Error, 401, 403, 404, 422, 429.

Targets

Method Path Summary Scope Roles Success
GET /api/v1/targets List target numbers read admin, member, guest 200
POST /api/v1/targets Register a target number write admin, member 201
GET /api/v1/targets/{target_id} Get a target read admin, member, guest 200
PATCH /api/v1/targets/{target_id} Update a target write admin, member 200
DELETE /api/v1/targets/{target_id} Delete a target (admin) admin admin 204
GET /api/v1/targets/{target_id}/verification Verification status read admin, member, guest 200
POST /api/v1/targets/{target_id}/verification/attest Sign the attestation write admin, member 200
POST /api/v1/targets/{target_id}/verification/call Place the verification call run admin, member 202
POST /api/v1/targets/{target_id}/verification/retry Retry with a new code none 201
POST /api/v1/targets/{target_id}/verification/start Start a verification (new code) none 201
POST /api/v1/targets/{target_id}/verify Sign the attestation (legacy) write admin, member 200

GET /api/v1/targets

List target numbers.

Parameter In Type Required Default
include_archived query boolean no false

Responses: 200 list of TargetOut, 401, 403, 422, 429.

POST /api/v1/targets

Register a target number.

The number is classified (country, line type). Premium-rate, shared-cost and emergency numbers are refused. A new number starts unverified: verify it (POST /targets/{id}/verification/start) before running tests on it.

Body (application/json): TargetIn.

Responses: 201 TargetOut, 400 Error, 401, 403, 422, 429.

GET /api/v1/targets/{target_id}

Get a target.

Parameter In Type Required Default
target_id path string (uuid) yes

Responses: 200 TargetOut, 401, 403, 404, 422, 429.

PATCH /api/v1/targets/{target_id}

Update a target.

The phone number itself cannot change: register a new target.

Parameter In Type Required Default
target_id path string (uuid) yes

Body (application/json): TargetPatch.

Responses: 200 TargetOut, 401, 403, 404, 422, 429.

DELETE /api/v1/targets/{target_id}

Delete a target (admin).

Parameter In Type Required Default
target_id path string (uuid) yes

Responses: 204, 401, 403, 404, 409 Error, 422, 429.

GET /api/v1/targets/{target_id}/verification

Verification status.

Why: CallQA only calls numbers you control. Verification proves it; until then a number with a signed attestation only gets the sandbox (3 calls per 24 h, one at a time, 3 minutes max, no schedule or CI run).

Default flow (spoken_code): 1. POST /targets/{id}/verification/start (sign the attestation in the same request the first time). The response holds a 6-digit code, shown once and never stored in clear. 2. Add the phrase ("Verification code 4 8 2 1 9 3.") to the line's greeting, or to the agent prompt ("if someone asks for a verification code, answer 4 8 2 1 9 3"). 3. POST /targets/{id}/verification/call: we call the number, say "Verification, please." if the line waits for the caller to speak, and listen for 20 s.

IVR menu in front of the code: give dtmf_gate to start (for example "1w2"): after the answer we wait 1 s, press the keys (w = 0.5 s pause), then listen. The keys are remembered on the number for the next verification. renew: true verifies a verified number again without leaving verified. 4. Poll GET /targets/{id}/verification until status is verified. On failure it stays pending: last_attempt.failure_hint says what to fix, last_attempt.heard_digits what we understood, last_attempt.sip_status why a line that never answered did not (480 unavailable, 486 busy, 603 declined...); POST .../verification/retry issues a new code.

Limits: a code lives 24 h and is used by a single call; 3 failed attempts in 24 h lock the number for 24 h (a call the line does not answer counts: attempts_left, and attempts_reset_at tells when the oldest one stops counting); 10 verification calls per organization and 24 h, one at a time. A verification is valid 180 days, then the number is expired and must be verified again.

Attestation (version 2026-09-26-v2-draft), signed once per number and again when a new version becomes mandatory (attestation_outdated); the placeholders are filled in when you sign:

I, {signer_name}, confirm that I am authorized to act on behalf of {organization_name} (the "Organization") and I certify, on behalf of the Organization, that:

  1. The Organization is the subscriber of the phone number {phone_number}, or holds a prior written authorization from its subscriber to have it called by automated systems using artificial or synthetic voices, for testing purposes. The Organization will keep that authorization and provide it to CallQA on request.
  2. This number is not an emergency number, a premium rate or special number, or the personal line of a person who has not agreed in writing to receive these calls.
  3. The calls will only be used to test the telephone system that answers this number. They will not be used for marketing, sales, surveys, or to reach any third party.
  4. Where a person may answer or listen to these calls, the Organization has informed that person in advance and obtained any consent required by law for automated calls, for the use of an AI voice, and for the recording and transcription of the calls.
  5. The calls, their content and any test data comply with the laws that apply to them, including laws on automated and artificial voice calls (such as the US Telephone Consumer Protection Act), telemarketing, call recording and data protection, and with the CallQA Terms of Service and Acceptable Use Policy.
  6. If the Organization stops controlling this number, it will stop calling it and remove it from CallQA without delay.

I understand that CallQA relies on this attestation to place the calls, that it records my name, email, IP address and the date and time of this signature as evidence, that it may suspend calls to this number at any time, and that the Organization is responsible for any claim resulting from calls placed at its request, as set out in the Terms of Service.

Parameter In Type Required Default
target_id path string (uuid) yes

Responses: 200 VerificationOut, 401, 403, 404, 422, 429.

POST /api/v1/targets/{target_id}/verification/attest

Sign the attestation.

Required once per number before verifying, and again when a new attestation version becomes mandatory. On its own it only opens the sandbox. The signed text, your name, email or API key, IP and time are kept as evidence. Text: see GET /targets/{id}/verification.

Parameter In Type Required Default
target_id path string (uuid) yes

Body (application/json): AttestIn.

Responses: 200 VerificationOut, 400 Error, 401, 403 Error, 404, 409 Error, 422, 429 Error.

POST /api/v1/targets/{target_id}/verification/call

Place the verification call.

Calls the number once for the active spoken_code code (after pressing its dtmf_gate keys, if any). The call is free. Poll GET .../verification (or the call) for the result.

Parameter In Type Required Default
target_id path string (uuid) yes

Body (application/json): VerificationCallIn.

Responses: 202 VerificationCallOut, 400 Error, 401, 403 Error, 404, 409 Error, 422, 429 Error.

POST /api/v1/targets/{target_id}/verification/retry

Retry with a new code.

After a failed attempt (or to replace the active code): issues a new code and cancels the previous one. Put the new phrase in the greeting or prompt before calling again.

Parameter In Type Required Default
target_id path string (uuid) yes

Body (application/json): VerificationStartIn.

Responses: 201 VerificationCodeOut, 400 Error, 401, 403 Error, 404, 409 Error, 422, 429 Error.

POST /api/v1/targets/{target_id}/verification/start

Start a verification (new code).

Why: CallQA only calls numbers you control. Verification proves it; until then a number with a signed attestation only gets the sandbox (3 calls per 24 h, one at a time, 3 minutes max, no schedule or CI run).

Default flow (spoken_code): 1. POST /targets/{id}/verification/start (sign the attestation in the same request the first time). The response holds a 6-digit code, shown once and never stored in clear. 2. Add the phrase ("Verification code 4 8 2 1 9 3.") to the line's greeting, or to the agent prompt ("if someone asks for a verification code, answer 4 8 2 1 9 3"). 3. POST /targets/{id}/verification/call: we call the number, say "Verification, please." if the line waits for the caller to speak, and listen for 20 s.

IVR menu in front of the code: give dtmf_gate to start (for example "1w2"): after the answer we wait 1 s, press the keys (w = 0.5 s pause), then listen. The keys are remembered on the number for the next verification. renew: true verifies a verified number again without leaving verified. 4. Poll GET /targets/{id}/verification until status is verified. On failure it stays pending: last_attempt.failure_hint says what to fix, last_attempt.heard_digits what we understood, last_attempt.sip_status why a line that never answered did not (480 unavailable, 486 busy, 603 declined...); POST .../verification/retry issues a new code.

Limits: a code lives 24 h and is used by a single call; 3 failed attempts in 24 h lock the number for 24 h (a call the line does not answer counts: attempts_left, and attempts_reset_at tells when the oldest one stops counting); 10 verification calls per organization and 24 h, one at a time. A verification is valid 180 days, then the number is expired and must be verified again.

Attestation (version 2026-09-26-v2-draft), signed once per number and again when a new version becomes mandatory (attestation_outdated); the placeholders are filled in when you sign:

I, {signer_name}, confirm that I am authorized to act on behalf of {organization_name} (the "Organization") and I certify, on behalf of the Organization, that:

  1. The Organization is the subscriber of the phone number {phone_number}, or holds a prior written authorization from its subscriber to have it called by automated systems using artificial or synthetic voices, for testing purposes. The Organization will keep that authorization and provide it to CallQA on request.
  2. This number is not an emergency number, a premium rate or special number, or the personal line of a person who has not agreed in writing to receive these calls.
  3. The calls will only be used to test the telephone system that answers this number. They will not be used for marketing, sales, surveys, or to reach any third party.
  4. Where a person may answer or listen to these calls, the Organization has informed that person in advance and obtained any consent required by law for automated calls, for the use of an AI voice, and for the recording and transcription of the calls.
  5. The calls, their content and any test data comply with the laws that apply to them, including laws on automated and artificial voice calls (such as the US Telephone Consumer Protection Act), telemarketing, call recording and data protection, and with the CallQA Terms of Service and Acceptable Use Policy.
  6. If the Organization stops controlling this number, it will stop calling it and remove it from CallQA without delay.

I understand that CallQA relies on this attestation to place the calls, that it records my name, email, IP address and the date and time of this signature as evidence, that it may suspend calls to this number at any time, and that the Organization is responsible for any claim resulting from calls placed at its request, as set out in the Terms of Service.

Parameter In Type Required Default
target_id path string (uuid) yes

Body (application/json): VerificationStartIn.

Responses: 201 VerificationCodeOut, 400 Error, 401, 403 Error, 404, 409 Error, 422, 429 Error.

POST /api/v1/targets/{target_id}/verify

Sign the attestation (legacy).

Kept for existing clients: same as POST .../verification/attest, returns the target. The attestation does not verify the number; see POST .../verification/start.

Parameter In Type Required Default
target_id path string (uuid) yes

Body (application/json): VerifyIn.

Responses: 200 TargetOut, 400 Error, 401, 403, 404, 422, 429.

Scenarios

Method Path Summary Scope Roles Success
POST /api/v1/generation-jobs/from-call/{call_id} Suggest a regression variant from a call write admin, member 202
GET /api/v1/generation-jobs/{job_id} Get a generation job read admin, member, guest 200
GET /api/v1/scenarios List scenarios read admin, member, guest 200
POST /api/v1/scenarios Create a scenario write admin, member 201
POST /api/v1/scenarios/generate Generate scenarios from a description write admin, member 202
POST /api/v1/scenarios/import Import a scenario from YAML write admin, member 200, 201
GET /api/v1/scenarios/schema Scenario JSON Schema read admin, member, guest 200
POST /api/v1/scenarios/validate Validate a scenario without saving read admin, member, guest 200
GET /api/v1/scenarios/{scenario_id} Get a scenario read admin, member, guest 200
PUT /api/v1/scenarios/{scenario_id} Replace a scenario write admin, member 200
DELETE /api/v1/scenarios/{scenario_id} Delete a scenario (admin) admin admin 204
POST /api/v1/scenarios/{scenario_id}/archive Archive a scenario write admin, member 200
GET /api/v1/scenarios/{scenario_id}/export Export as YAML or JSON read admin, member, guest 200
GET /api/v1/scenarios/{scenario_id}/variants List variants read admin, member, guest 200
POST /api/v1/scenarios/{scenario_id}/variants Add a variant (test case) write admin, member 201
POST /api/v1/scenarios/{scenario_id}/variants/derive Auto-derive variants write admin, member 202
POST /api/v1/scenarios/{scenario_id}/variants/from-bundle Turn a call into a regression test write admin, member 201
PATCH /api/v1/variants/{variant_id} Update a variant write admin, member 200
DELETE /api/v1/variants/{variant_id} Delete a variant (admin) admin admin 204

POST /api/v1/generation-jobs/from-call/{call_id}

Suggest a regression variant from a call.

Proposes one MANUAL variant (not saved) that reproduces what went wrong in a judged call. Add it with POST /scenarios/{id}/variants.

Generation takes 10 to 60 seconds, so it runs in the background: the answer is 202 with a job; poll GET /generation-jobs/{id} (its url) until status is done (then read result) or error.

Parameter In Type Required Default
call_id path string (uuid) yes

Responses: 202 GenerationJobOut, 401, 403, 404, 409 Error, 422, 429, 503 Error.

GET /api/v1/generation-jobs/{job_id}

Get a generation job.

Parameter In Type Required Default
job_id path string (uuid) yes

Responses: 200 GenerationJobOut, 401, 403, 404, 422, 429.

GET /api/v1/scenarios

List scenarios.

Parameter In Type Required Default
tag query string no
include_archived query boolean no false
q query string no

Responses: 200 list of ScenarioSummary, 401, 403, 422, 429.

POST /api/v1/scenarios

Create a scenario.

Body: a scenario document. The full grammar is published as JSON Schema at GET /api/v1/scenarios/schema. The landing-page shorthand (phone, persona as text, must_include, must_not, max_latency_ms at top level) is accepted. Unknown fields are rejected unless they start with x_ (kept verbatim).

A target number that does not exist yet is registered as unverified.

Body (application/json): object.

Responses: 201 ScenarioOut, 401, 403 Error, 422 ScenarioInvalid, 429.

POST /api/v1/scenarios/generate

Generate scenarios from a description.

Proposes scenario documents (not saved). Review them, then POST the ones you keep to /scenarios. Invalid proposals are filtered out.

Generation takes 10 to 60 seconds, so it runs in the background: the answer is 202 with a job; poll GET /generation-jobs/{id} (its url) until status is done (then read result) or error.

Body (application/json): GenerateIn.

Responses: 202 GenerationJobOut, 401, 403, 422, 429, 503 Error.

POST /api/v1/scenarios/import

Import a scenario from YAML.

Send the YAML as the raw body (Content-Type: application/yaml). If a non-archived scenario with the same name exists it is updated (upsert), otherwise created.

Body (application/yaml): string.

Responses: 200 ScenarioOut, 201 ScenarioOut, 401, 403 Error, 422 ScenarioInvalid, 429.

GET /api/v1/scenarios/schema

Scenario JSON Schema.

JSON Schema of the scenario grammar (generated from callqa_spec).

Responses: 200 object, 401, 403, 422, 429.

POST /api/v1/scenarios/validate

Validate a scenario without saving.

Body: a scenario document. The full grammar is published as JSON Schema at GET /api/v1/scenarios/schema. The landing-page shorthand (phone, persona as text, must_include, must_not, max_latency_ms at top level) is accepted. Unknown fields are rejected unless they start with x_ (kept verbatim).

Body (application/json): object.

Responses: 200 object, 401, 403, 422 ScenarioInvalid, 429.

GET /api/v1/scenarios/{scenario_id}

Get a scenario.

Parameter In Type Required Default
scenario_id path string (uuid) yes

Responses: 200 ScenarioOut, 401, 403, 404, 422, 429.

PUT /api/v1/scenarios/{scenario_id}

Replace a scenario.

Body: a scenario document. The full grammar is published as JSON Schema at GET /api/v1/scenarios/schema. The landing-page shorthand (phone, persona as text, must_include, must_not, max_latency_ms at top level) is accepted. Unknown fields are rejected unless they start with x_ (kept verbatim).

Variants are matched by title: existing ones keep their id (and their history).

Parameter In Type Required Default
scenario_id path string (uuid) yes

Body (application/json): object.

Responses: 200 ScenarioOut, 401, 403 Error, 404, 422 ScenarioInvalid, 429.

DELETE /api/v1/scenarios/{scenario_id}

Delete a scenario (admin).

Parameter In Type Required Default
scenario_id path string (uuid) yes

Responses: 204, 401, 403, 404, 409 Error, 422, 429.

POST /api/v1/scenarios/{scenario_id}/archive

Archive a scenario.

Archived scenarios stop their schedule and are hidden from lists; their history is kept. Members may archive; only admins delete.

Parameter In Type Required Default
scenario_id path string (uuid) yes

Responses: 200 ScenarioSummary, 401, 403, 404, 422, 429.

GET /api/v1/scenarios/{scenario_id}/export

Export as YAML or JSON.

format=yaml (default) or format=json. Round-trips with /import.

Parameter In Type Required Default
scenario_id path string (uuid) yes
format query string no "yaml"

Responses: 200, 401, 403, 404, 422, 429.

GET /api/v1/scenarios/{scenario_id}/variants

List variants.

Parameter In Type Required Default
scenario_id path string (uuid) yes

Responses: 200 list of VariantOut, 401, 403, 404, 422, 429.

POST /api/v1/scenarios/{scenario_id}/variants

Add a variant (test case).

Parameter In Type Required Default
scenario_id path string (uuid) yes

Body (application/json): VariantIn.

Responses: 201 VariantOut, 401, 403 Error, 404, 422 ScenarioInvalid, 429.

POST /api/v1/scenarios/{scenario_id}/variants/derive

Auto-derive variants.

Adds AUTO test cases (edge cases, personas, accents, error paths) when the job completes. Requires the auto-derived coverage feature (Growth and up).

Generation takes 10 to 60 seconds, so it runs in the background: the answer is 202 with a job; poll GET /generation-jobs/{id} (its url) until status is done (then read result) or error.

Parameter In Type Required Default
scenario_id path string (uuid) yes

Body (application/json): DeriveIn.

Responses: 202 GenerationJobOut, 401, 403 Error, 404, 422, 429, 503 Error.

POST /api/v1/scenarios/{scenario_id}/variants/from-bundle

Turn a call into a regression test.

Imports a replay bundle (or a call of yours) as a MANUAL variant: the "Add to regression suite" button.

Parameter In Type Required Default
scenario_id path string (uuid) yes

Body (application/json): FromBundleIn.

Responses: 201 VariantOut, 400 Error, 401, 403, 404, 422 ScenarioInvalid, 429.

PATCH /api/v1/variants/{variant_id}

Update a variant.

Parameter In Type Required Default
variant_id path string (uuid) yes

Body (application/json): VariantPatch.

Responses: 200 VariantOut, 401, 403, 404, 422 ScenarioInvalid, 429.

DELETE /api/v1/variants/{variant_id}

Delete a variant (admin).

Parameter In Type Required Default
variant_id path string (uuid) yes

Responses: 204, 401, 403, 404, 422, 429.

Runs

Method Path Summary Scope Roles Success
GET /api/v1/runs List runs read admin, member, guest 200
GET /api/v1/runs/{run_id} Get a run read admin, member, guest 200
DELETE /api/v1/runs/{run_id} Delete a run and its calls admin admin 204
POST /api/v1/runs/{run_id}/cancel Cancel the calls not dialed yet run admin, member 200
POST /api/v1/scenarios/{scenario_id}/runs Trigger a run (CI friendly) run admin, member 200, 201, 202

GET /api/v1/runs

List runs.

Filter by scenario_id, status, since, environment (slug, name or id), version_id (runs attributed to the version) and tag.

Parameter In Type Required Default
scenario_id query string (uuid) no
status query string no
since query string (date-time) no
environment query string no
version_id query string (uuid) no
tag query string no
limit query integer no 50
offset query integer no 0

Responses: 200 Page_RunOut_, 401, 403, 422, 429.

GET /api/v1/runs/{run_id}

Get a run.

Parameter In Type Required Default
run_id path string (uuid) yes

Responses: 200 RunOut, 401, 403, 404, 422, 429.

DELETE /api/v1/runs/{run_id}

Delete a run and its calls.

Admin scope. Deletes the run and all its calls (trace, transcript, result, bundles) for good. Refused (409 run_not_finished) while a call of the run is queued or in progress: cancel it first. Billing lines are kept without the calls' content. Audited.

Parameter In Type Required Default
run_id path string (uuid) yes

Responses: 204, 401, 403, 404, 409 Error, 422, 429.

POST /api/v1/runs/{run_id}/cancel

Cancel the calls not dialed yet.

Parameter In Type Required Default
run_id path string (uuid) yes

Responses: 200 RunOut, 401, 403, 404, 422, 429.

POST /api/v1/scenarios/{scenario_id}/runs

Trigger a run (CI friendly).

Queues one call per enabled variant (or the listed ones).

CI usage: ?wait=true&threshold=80 holds the request until the run finishes (timeout seconds, default 600, capped at 900) and answers: * 200 finished and passed (score >= threshold, no failed, error or blocked call); * 412 finished and failed; * 202 still running at the timeout: poll GET /runs/{id}.

Without wait the answer is 201 with the queued run.

curl --fail alone is not a safe gate: it treats 202 as a success, so a slow run would pass. Check the status code:

code=$(curl -sS -o run.json -w '%{http_code}' -X POST --max-time 960 \
  -H "Authorization: Bearer $CALLQA_API_KEY" \
  "https://app.callqa.dev/api/v1/scenarios/$SCENARIO/runs?wait=true&timeout=900&threshold=80")
[ "$code" = "200" ] || exit 1

The wait is asynchronous on the server (no worker is held), but proxies in front of your runner may cut a request open for minutes: the CLI polls instead.

Parameter In Type Required Default
scenario_id path string (uuid) yes
wait query boolean no false
timeout query integer no 600
threshold query integer no

Body (application/json): TriggerIn.

Responses: 200 RunOut, 201 RunOut, 202 RunOut, 400 Error, 401, 403 Error, 404, 412 RunOut, 422, 429.

Calls

Method Path Summary Scope Roles Success
GET /api/v1/calls List calls read admin, member, guest 200
GET /api/v1/calls/{call_id} Get a call read admin, member, guest 200
DELETE /api/v1/calls/{call_id} Delete a call admin admin 204
GET /api/v1/calls/{call_id}/bundle Export a replay bundle read admin, member 200
GET /api/v1/calls/{call_id}/events Call trace read admin, member, guest 200
GET /api/v1/calls/{call_id}/recording Download the call audio read admin, member, guest 200, 206
POST /api/v1/replays Replay a call run admin, member 201

GET /api/v1/calls

List calls.

Filter by status (e.g. error, blocked_insufficient_credit), verdict, run_id, since, environment.

Parameter In Type Required Default
status query string no
verdict query string no
run_id query string (uuid) no
since query string (date-time) no
environment query string no
limit query integer no 50
offset query integer no 0

Responses: 200 Page_CallOut_, 401, 403, 422, 429.

GET /api/v1/calls/{call_id}

Get a call.

Includes the ResolvedRun, the result (verdicts with evidence, metrics, score) and the transcript.

Parameter In Type Required Default
call_id path string (uuid) yes

Responses: 200 CallDetailOut, 401, 403, 404, 422, 429.

DELETE /api/v1/calls/{call_id}

Delete a call.

Admin scope. Deletes the call, its trace, transcript, result and bundles for good. Refused with 409 while the call is not finished (call_not_finished) and for number verification calls, kept as evidence (verification_record). Billing lines are kept without the call's content. Audited.

Parameter In Type Required Default
call_id path string (uuid) yes

Responses: 204, 401, 403, 404, 409 Error, 422, 429.

GET /api/v1/calls/{call_id}/bundle

Export a replay bundle.

A self-contained JSON file (callqa_spec.ReplayBundle): resolved run, runtime (models, voices, settings), full trace and result. Replay it with POST /replays or import it as a regression test with POST /scenarios/{id}/variants/from-bundle.

Parameter In Type Required Default
call_id path string (uuid) yes

Responses: 200 object, 401, 403, 404, 422, 429.

GET /api/v1/calls/{call_id}/events

Call trace.

The append-only event trace (callqa_spec.CallEvent), by seq.

Parameter In Type Required Default
call_id path string (uuid) yes
after_seq query integer no -1
limit query integer no 200
offset query integer no 0

Responses: 200 Page_EventOut_, 401, 403, 404, 422, 429.

GET /api/v1/calls/{call_id}/recording

Download the call audio.

The stereo WAV of the call (16 bit PCM, 16 kHz): left track our test caller, right track your line. Sample 0 is the dialing event: a trace instant t_ms is at (t_ms - recording.start_ms) / 1000 seconds. Supports HTTP Range. 404 when the organization does not record audio, or once the recording expired (audio retention).

Parameter In Type Required Default
call_id path string (uuid) yes

Responses: 200, 206, 401, 403, 404, 422, 429.

POST /api/v1/replays

Replay a call.

Creates a new call with exactly the same ResolvedRun (and by default the same runtime). The bundle's target must be a number of your organization; normal checks apply (verification, credit, concurrency).

Body (application/json): ReplayIn.

Responses: 201 CallOut, 400 Error, 401, 403, 404 Error, 422, 429.

Tags

Method Path Summary Scope Roles Success
GET /api/v1/tags List or search tags read admin, member, guest 200
POST /api/v1/tags Create a tag write admin, member 201
POST /api/v1/tags/bulk Tag or untag many objects write admin, member 200
GET /api/v1/tags/{tag_id} Get a tag read admin, member, guest 200
PATCH /api/v1/tags/{tag_id} Update a tag write admin, member 200
DELETE /api/v1/tags/{tag_id} Delete a tag (admin) admin admin 204

GET /api/v1/tags

List or search tags.

Tags are free keywords (a code name, a customer, a team) on scenarios, runs, versions and numbers; CallQA gives them no meaning of its own. With q, the autocomplete: names starting with q first, then containing it, most used first, at most limit (default 10, max 50).

Parameter In Type Required Default
q query string no
limit query integer no

Responses: 200 list of TagSuggestion, 401, 403, 422, 429.

POST /api/v1/tags

Create a tag.

Body (application/json): TagIn.

Responses: 201 TagOut, 401, 403, 409 Error, 422, 429.

POST /api/v1/tags/bulk

Tag or untag many objects.

Adds add and removes remove on every listed scenario, run, version and number (by id). Tags to add are created when new. An id of another organization answers 404 unknown_object and nothing is changed.

Body (application/json): TagBulkIn.

Responses: 200 TagBulkOut, 400 Error, 401, 403, 404 Error, 422, 429.

GET /api/v1/tags/{tag_id}

Get a tag.

Parameter In Type Required Default
tag_id path integer yes

Responses: 200 TagOut, 401, 403, 404, 422, 429.

PATCH /api/v1/tags/{tag_id}

Update a tag.

Renaming a tag keeps it on its scenarios and runs.

Parameter In Type Required Default
tag_id path integer yes

Body (application/json): TagPatch.

Responses: 200 TagOut, 401, 403, 404, 409 Error, 422, 429.

DELETE /api/v1/tags/{tag_id}

Delete a tag (admin).

Parameter In Type Required Default
tag_id path integer yes

Responses: 204, 401, 403, 404, 422, 429.

Versions

Method Path Summary Scope Roles Success
GET /api/v1/releases List releases (deprecated) read admin, member, guest 200
POST /api/v1/releases Tag a release (deprecated) write admin, member 201
GET /api/v1/releases/{release_id} Get a release (deprecated) read admin, member, guest 200
PATCH /api/v1/releases/{release_id} Update a release (deprecated) write admin, member 200
DELETE /api/v1/releases/{release_id} Delete a release (deprecated, admin) admin admin 204
POST /api/v1/releases/{release_id}/promote Promote a release (deprecated) write admin, member 201
GET /api/v1/versions List versions read admin, member, guest 200
POST /api/v1/versions Create a version write admin, member 201
GET /api/v1/versions/{version_id} Get a version read admin, member, guest 200
PATCH /api/v1/versions/{version_id} Update a version write admin, member 200
DELETE /api/v1/versions/{version_id} Delete a version (admin) admin admin 204
POST /api/v1/versions/{version_id}/promote Promote a version to another environment write admin, member 201

GET /api/v1/releases

List releases (deprecated).

Deprecated since 2026-09-27, removed on 2027-03-31.

Use GET /versions.

Parameter In Type Required Default
environment query string no

Responses: 200 list of ReleaseOut, 401, 403, 422, 429.

POST /api/v1/releases

Tag a release (deprecated).

Deprecated since 2026-09-27, removed on 2027-03-31.

Use POST /versions.

Body (application/json): ReleaseIn.

Responses: 201 ReleaseOut, 400 Error, 401, 403, 409 Error, 422, 429.

GET /api/v1/releases/{release_id}

Get a release (deprecated).

Deprecated since 2026-09-27, removed on 2027-03-31.

Use GET /versions/{id}.

Parameter In Type Required Default
release_id path string (uuid) yes

Responses: 200 ReleaseOut, 401, 403, 404, 422, 429.

PATCH /api/v1/releases/{release_id}

Update a release (deprecated).

Deprecated since 2026-09-27, removed on 2027-03-31.

Use PATCH /versions/{id}.

Parameter In Type Required Default
release_id path string (uuid) yes

Body (application/json): ReleasePatch.

Responses: 200 ReleaseOut, 400 Error, 401, 403, 404, 409 Error, 422, 429.

DELETE /api/v1/releases/{release_id}

Delete a release (deprecated, admin).

Deprecated since 2026-09-27, removed on 2027-03-31.

Use DELETE /versions/{id}.

Parameter In Type Required Default
release_id path string (uuid) yes

Responses: 204, 401, 403, 404, 422, 429.

POST /api/v1/releases/{release_id}/promote

Promote a release (deprecated).

Deprecated since 2026-09-27, removed on 2027-03-31.

Use POST /versions/{id}/promote.

Parameter In Type Required Default
release_id path string (uuid) yes

Body (application/json): PromoteIn.

Responses: 201 ReleaseOut, 400 Error, 401, 403, 404, 409 Error, 422, 429.

GET /api/v1/versions

List versions.

Newest first. environment (slug, name or id) keeps one environment, tag the versions carrying a tag, q a part of the label, git ref or sha.

Parameter In Type Required Default
environment query string no
tag query string no
q query string no
limit query integer no 50
offset query integer no 0

Responses: 200 Page_VersionOut_, 401, 403, 422, 429.

POST /api/v1/versions

Create a version.

A version of your code in one environment, from deployed_at (default now). Runs of that environment are attributed to it until the next version. 409 already_exists when the label exists in the environment. CI can instead send version when it triggers a run.

Body (application/json): VersionIn.

Responses: 201 VersionOut, 400 Error, 401, 403, 409 Error, 422, 429.

GET /api/v1/versions/{version_id}

Get a version.

Parameter In Type Required Default
version_id path string (uuid) yes

Responses: 200 VersionOut, 401, 403, 404, 422, 429.

PATCH /api/v1/versions/{version_id}

Update a version.

Parameter In Type Required Default
version_id path string (uuid) yes

Body (application/json): VersionPatch.

Responses: 200 VersionOut, 400 Error, 401, 403, 404, 409 Error, 422, 429.

DELETE /api/v1/versions/{version_id}

Delete a version (admin).

Its runs are kept; they fall back to the previous version of the environment.

Parameter In Type Required Default
version_id path string (uuid) yes

Responses: 204, 401, 403, 404, 422, 429.

POST /api/v1/versions/{version_id}/promote

Promote a version to another environment.

The same code in the next environment (staging to production), from deployed_at (default now): label, git fields, notes, number and tags travel with it. 409 already_exists when the label is already there.

Parameter In Type Required Default
version_id path string (uuid) yes

Body (application/json): VersionPromoteIn.

Responses: 201 VersionOut, 400 Error, 401, 403, 404, 409 Error, 422, 429.

Environments

Method Path Summary Scope Roles Success
GET /api/v1/environments List environments read admin, member, guest 200
POST /api/v1/environments Create an environment write admin, member 201
GET /api/v1/environments/compare Compare environments (Growth and up) read admin, member, guest 200
GET /api/v1/environments/stability Stability per environment (Growth and up) read admin, member, guest 200
GET /api/v1/environments/{env_id} Get an environment read admin, member, guest 200
PATCH /api/v1/environments/{env_id} Update an environment write admin, member 200
DELETE /api/v1/environments/{env_id} Delete an environment (admin) admin admin 204
PUT /api/v1/environments/{env_id}/numbers Set the number dialed in an environment write admin, member 200
GET /api/v1/environments/{env_id}/scenarios Scenarios of an environment read admin, member, guest 200
POST /api/v1/environments/{env_id}/scenarios Bind, pause or unbind many scenarios write admin, member 200
GET /api/v1/matrix What runs where, and what passes read admin, member, guest 200
GET /api/v1/scenarios/{scenario_id}/environments Environments of a scenario read admin, member, guest 200
PUT /api/v1/scenarios/{scenario_id}/environments/{env_id} Activate or schedule a scenario in an environment write admin, member 200
DELETE /api/v1/scenarios/{scenario_id}/environments/{env_id} Remove a scenario from an environment write admin, member 204

GET /api/v1/environments

List environments.

In promotion order. Every organization has one default environment (is_default), used by runs and versions that name none.

Responses: 200 list of EnvironmentOut, 401, 403, 422, 429.

POST /api/v1/environments

Create an environment.

Body (application/json): EnvironmentIn.

Responses: 201 EnvironmentOut, 400 Error, 401, 403, 409 Error, 422, 429.

GET /api/v1/environments/compare

Compare environments (Growth and up).

Side a vs side b (for example a=staging&b=production): overall KPIs, per scenario, and the test cases whose last verdict differs. Narrow a side to one version with a_version / b_version (version id), and to one scenario with scenario_id. Without a version, a side covers the last days days. a_release / b_release are the deprecated names of a_version / b_version.

Parameter In Type Required Default
a query string yes
b query string yes
a_version query string (uuid) no
b_version query string (uuid) no
scenario_id query string (uuid) no
days query integer no 30
a_release query string (uuid) no
b_release query string (uuid) no

Responses: 200 CompareOut, 400 Error, 401, 403 Error, 422, 429.

GET /api/v1/environments/stability

Stability per environment (Growth and up).

Over the last days (7, 30 or 90): tests, pass rate, average score, p95 latency, flaky test cases and last run of each environment.

Parameter In Type Required Default
days query integer no 30

Responses: 200 list of StabilityOut, 401, 403 Error, 422, 429.

GET /api/v1/environments/{env_id}

Get an environment.

By id or slug.

Parameter In Type Required Default
env_id path string yes

Responses: 200 EnvironmentOut, 401, 403, 404, 422, 429.

PATCH /api/v1/environments/{env_id}

Update an environment.

Parameter In Type Required Default
env_id path string yes

Body (application/json): EnvironmentPatch.

Responses: 200 EnvironmentOut, 400 Error, 401, 403, 404, 409 Error, 422, 429.

DELETE /api/v1/environments/{env_id}

Delete an environment (admin).

Refused (409 environment_in_use) while runs or versions belong to it, and for the default environment (409 default_required).

Parameter In Type Required Default
env_id path string yes

Responses: 204, 401, 403, 404, 409 Error, 422, 429.

PUT /api/v1/environments/{env_id}/numbers

Set the number dialed in an environment.

Runs of this environment dial number_id instead of target_id. Both are numbers of your organization, each verified on its own.

Parameter In Type Required Default
env_id path string yes

Body (application/json): NumberIn.

Responses: 200 EnvironmentOut, 400 Error, 401, 403, 404, 422, 429.

GET /api/v1/environments/{env_id}/scenarios

Scenarios of an environment.

The scenarios activated in (or bound to) this environment.

Parameter In Type Required Default
env_id path string yes

Responses: 200 list of BindingOut, 401, 403, 404, 422, 429.

POST /api/v1/environments/{env_id}/scenarios

Bind, pause or unbind many scenarios.

One call for a multi-select or a drag and drop: enable, disable or remove the listed scenarios in this environment, optionally with the same schedule, timezone, call_window for all. An id of another organization answers 404 and nothing changes.

Parameter In Type Required Default
env_id path string yes

Body (application/json): BulkBindingIn.

Responses: 200 BulkBindingOut, 400 Error, 401, 403, 404, 422, 429.

GET /api/v1/matrix

What runs where, and what passes.

For every scenario x environment: activated or not, schedule, last completed run (verdict, score, version), trend of the last trend_runs runs; for the last versions versions of each environment: the scenarios that pass, warn, fail or were not tested. Filters: environment and tag (repeat them), scenario_id (repeat), q (part of a scenario name), bound_only.

Parameter In Type Required Default
environment query list of string no
tag query list of string no
scenario_id query list of string (uuid) no
q query string no ""
bound_only query boolean no false
trend_runs query integer no 5
versions query integer no 5

Responses: 200 MatrixOut, 401, 403, 422, 429.

GET /api/v1/scenarios/{scenario_id}/environments

Environments of a scenario.

Where the scenario runs, with the schedule, the call window and the number dialed in each environment.

Parameter In Type Required Default
scenario_id path string (uuid) yes

Responses: 200 list of BindingOut, 401, 403, 404, 422, 429.

PUT /api/v1/scenarios/{scenario_id}/environments/{env_id}

Activate or schedule a scenario in an environment.

Creates the binding when needed. Fields left out keep their value. Each environment has its own schedule and window.

Parameter In Type Required Default
scenario_id path string (uuid) yes
env_id path string yes

Body (application/json): BindingIn.

Responses: 200 BindingOut, 400 Error, 401, 403, 404, 422, 429.

DELETE /api/v1/scenarios/{scenario_id}/environments/{env_id}

Remove a scenario from an environment.

Deletes the binding and its schedule. Past runs are kept.

Parameter In Type Required Default
scenario_id path string (uuid) yes
env_id path string yes

Responses: 204, 401, 403, 404, 422, 429.

Webhooks

Method Path Summary Scope Roles Success
GET /api/v1/webhooks List webhook endpoints read admin, member 200
POST /api/v1/webhooks Create a webhook endpoint admin admin 201
GET /api/v1/webhooks/{webhook_id} Get a webhook endpoint read admin, member 200
PATCH /api/v1/webhooks/{webhook_id} Update a webhook endpoint admin admin 200
DELETE /api/v1/webhooks/{webhook_id} Delete a webhook endpoint admin admin 204
GET /api/v1/webhooks/{webhook_id}/deliveries Delivery log of an endpoint read admin, member 200
POST /api/v1/webhooks/{webhook_id}/rotate-secret Rotate the signing secret admin admin 200
POST /api/v1/webhooks/{webhook_id}/test Send a test event now admin admin 200

GET /api/v1/webhooks

List webhook endpoints.

Payloads are JSON {id, type, created, data} signed with HMAC-SHA256:

CallQA-Signature: t=<unix ts>,v1=<hex hmac_sha256(secret, "<ts>.<raw body>")>

During a secret rotation the header carries one v1 per valid secret: accept the delivery if any v1 matches. Verify in constant time and reject timestamps older than 5 minutes. Failed deliveries are retried after 1 min, 5 min, 30 min, 2 h and 6 h.

Events: call.completed, call.failed, call.blocked, call.regressed, call.recovered, suite_run.started, suite_run.completed, suite_run.failed, score.dropped, version.created, version.promoted, release.created, release.promoted, wallet.low_balance

Responses: 200 list of WebhookOut, 401, 403, 422, 429.

POST /api/v1/webhooks

Create a webhook endpoint.

Payloads are JSON {id, type, created, data} signed with HMAC-SHA256:

CallQA-Signature: t=<unix ts>,v1=<hex hmac_sha256(secret, "<ts>.<raw body>")>

During a secret rotation the header carries one v1 per valid secret: accept the delivery if any v1 matches. Verify in constant time and reject timestamps older than 5 minutes. Failed deliveries are retried after 1 min, 5 min, 30 min, 2 h and 6 h.

Events: call.completed, call.failed, call.blocked, call.regressed, call.recovered, suite_run.started, suite_run.completed, suite_run.failed, score.dropped, version.created, version.promoted, release.created, release.promoted, wallet.low_balance

Body (application/json): WebhookIn.

Responses: 201 WebhookCreated, 400 Error, 401, 403, 422, 429.

GET /api/v1/webhooks/{webhook_id}

Get a webhook endpoint.

Parameter In Type Required Default
webhook_id path string (uuid) yes

Responses: 200 WebhookOut, 401, 403, 404, 422, 429.

PATCH /api/v1/webhooks/{webhook_id}

Update a webhook endpoint.

Change the URL, the events, the description, or pause it (enabled: false). The signing secret does not change: use rotate-secret.

Parameter In Type Required Default
webhook_id path string (uuid) yes

Body (application/json): WebhookPatch.

Responses: 200 WebhookOut, 400 Error, 401, 403, 404, 422, 429.

DELETE /api/v1/webhooks/{webhook_id}

Delete a webhook endpoint.

Parameter In Type Required Default
webhook_id path string (uuid) yes

Responses: 204, 401, 403, 404, 422, 429.

GET /api/v1/webhooks/{webhook_id}/deliveries

Delivery log of an endpoint.

Parameter In Type Required Default
webhook_id path string (uuid) yes
limit query integer no 50
offset query integer no 0

Responses: 200 Page_DeliveryOut_, 401, 403, 404, 422, 429.

POST /api/v1/webhooks/{webhook_id}/rotate-secret

Rotate the signing secret.

Returns the new secret (shown once). During grace_period_hours (default 24, at most 168) every delivery is signed with both secrets (two v1 in CallQA-Signature), so you can deploy the new one without losing events.

Parameter In Type Required Default
webhook_id path string (uuid) yes

Body (application/json): SecretRotateIn.

Responses: 200 WebhookCreated, 400 Error, 401, 403, 404, 422, 429.

POST /api/v1/webhooks/{webhook_id}/test

Send a test event now.

Parameter In Type Required Default
webhook_id path string (uuid) yes

Responses: 200 DeliveryOut, 401, 403, 404, 422, 429.

Alerts

Method Path Summary Scope Roles Success
GET /api/v1/alert-channels List alert channels admin admin 200
POST /api/v1/alert-channels Create an alert channel admin admin 201
GET /api/v1/alert-channels/{channel_id} Get an alert channel admin admin 200
PATCH /api/v1/alert-channels/{channel_id} Update an alert channel admin admin 200
DELETE /api/v1/alert-channels/{channel_id} Delete an alert channel admin admin 204
POST /api/v1/alert-channels/{channel_id}/test Send a test alert admin admin 200

GET /api/v1/alert-channels

List alert channels.

Alert channels warn people in Slack (incoming webhook) or by email when a run run completes and one of the channel's rules matches: the run failed (on_failure), its score is under on_score_below, or a call's p95 latency is above on_latency_p95_above_ms. One message per channel and run at most, sent within seconds by the dispatcher and retried after 1, 5 and 30 minutes.

The Slack webhook URL is a secret: it must be an https://hooks.slack.com/ URL, and it is never returned in full. For machine integrations, use webhooks.

Responses: 200 list of AlertChannelOut, 401, 403, 422, 429.

POST /api/v1/alert-channels

Create an alert channel.

Alert channels warn people in Slack (incoming webhook) or by email when a run run completes and one of the channel's rules matches: the run failed (on_failure), its score is under on_score_below, or a call's p95 latency is above on_latency_p95_above_ms. One message per channel and run at most, sent within seconds by the dispatcher and retried after 1, 5 and 30 minutes.

The Slack webhook URL is a secret: it must be an https://hooks.slack.com/ URL, and it is never returned in full. For machine integrations, use webhooks.

Body (application/json): AlertChannelIn.

Responses: 201 AlertChannelOut, 400 Error, 401, 403, 422, 429.

GET /api/v1/alert-channels/{channel_id}

Get an alert channel.

Parameter In Type Required Default
channel_id path integer yes

Responses: 200 AlertChannelOut, 401, 403, 404, 422, 429.

PATCH /api/v1/alert-channels/{channel_id}

Update an alert channel.

Only the fields you send change. null on a threshold removes that rule. The kind cannot change.

Parameter In Type Required Default
channel_id path integer yes

Body (application/json): AlertChannelPatch.

Responses: 200 AlertChannelOut, 400 Error, 401, 403, 404, 422, 429.

DELETE /api/v1/alert-channels/{channel_id}

Delete an alert channel.

Parameter In Type Required Default
channel_id path integer yes

Responses: 204, 401, 403, 404, 422, 429.

POST /api/v1/alert-channels/{channel_id}/test

Send a test alert.

Sends a test message now and tells whether it went through. Limited to 5 per minute per channel.

Parameter In Type Required Default
channel_id path integer yes

Responses: 200 AlertTestOut, 401, 403, 404, 422, 429.

API keys & logs

Method Path Summary Scope Roles Success
GET /api/v1/api-keys List API keys admin admin 200
POST /api/v1/api-keys Create an API key admin admin 201
GET /api/v1/api-keys/{key_id} Get an API key admin admin 200
PATCH /api/v1/api-keys/{key_id} Rename or restrict an API key admin admin 200
DELETE /api/v1/api-keys/{key_id} Revoke an API key admin admin 204
POST /api/v1/api-keys/{key_id}/rotate Rotate an API key admin admin 201
GET /api/v1/api-logs API request journal read admin, member 200

GET /api/v1/api-keys

List API keys.

Responses: 200 list of ApiKeyOut, 401, 403, 422, 429.

POST /api/v1/api-keys

Create an API key.

The full key is returned once; only a hash is stored. allowed_cidrs restricts where it can be used from.

Body (application/json): ApiKeyIn.

Responses: 201 ApiKeyCreated, 400 Error, 401, 403, 422, 429.

GET /api/v1/api-keys/{key_id}

Get an API key.

Parameter In Type Required Default
key_id path integer yes

Responses: 200 ApiKeyOut, 401, 403, 404, 422, 429.

PATCH /api/v1/api-keys/{key_id}

Rename or restrict an API key.

Scopes cannot change: create a new key instead.

Parameter In Type Required Default
key_id path integer yes

Body (application/json): ApiKeyPatch.

Responses: 200 ApiKeyOut, 400 Error, 401, 403, 404, 422, 429.

DELETE /api/v1/api-keys/{key_id}

Revoke an API key.

Parameter In Type Required Default
key_id path integer yes

Responses: 204, 401, 403, 404, 422, 429.

POST /api/v1/api-keys/{key_id}/rotate

Rotate an API key.

Creates a replacement with the same name, scopes, networks and expiry, returned once. The old key keeps working for grace_period_hours (default 24, at most 168, 0 revokes it now), then expires: deploy the new one in between.

Parameter In Type Required Default
key_id path integer yes

Body (application/json): KeyRotateIn.

Responses: 201 ApiKeyCreated, 400 Error, 401, 403, 404, 422, 429.

GET /api/v1/api-logs

API request journal.

Every authenticated API request of your organization (method, path, status, duration, key, IP). Bodies are never stored.

Parameter In Type Required Default
since query string (date-time) no
status_code query integer no
key_prefix query string no
limit query integer no 100
offset query integer no 0

Responses: 200 Page_ApiLogOut_, 401, 403, 422, 429.

Exports for LLMs

Method Path Summary Scope Roles Success
GET /api/v1/calls/{call_id}/llm-export Export a call for an LLM read admin, member, guest 200
GET /api/v1/runs/{run_id}/llm-export Export a run for an LLM read admin, member, guest 200
GET /api/v1/scenarios/{scenario_id}/llm-export Export a scenario for an LLM read admin, member, guest 200

GET /api/v1/calls/{call_id}/llm-export

Export a call for an LLM.

A report made to be pasted into an LLM (a chat assistant or a coding agent) that will fix your voice agent: the scenario, the results, every failed check with the judge's quote, a readable transcript (AGENT = your agent, CALLER = our test caller), metrics, the judge's recommendations and a primer asking for a fix.

format=markdown (default) answers text/markdown; format=json answers the same document as compact JSON (format: "callqa.llm_report", version, level).

Parameter In Type Required Default
call_id path string (uuid) yes
format query string no "markdown"

Responses: 200 object, 401, 403, 404, 422, 429.

GET /api/v1/runs/{run_id}/llm-export

Export a run for an LLM.

A report made to be pasted into an LLM (a chat assistant or a coding agent) that will fix your voice agent: the scenario, the results, every failed check with the judge's quote, a readable transcript (AGENT = your agent, CALLER = our test caller), metrics, the judge's recommendations and a primer asking for a fix.

format=markdown (default) answers text/markdown; format=json answers the same document as compact JSON (format: "callqa.llm_report", version, level). The failed calls (up to 5) are detailed.

Parameter In Type Required Default
run_id path string (uuid) yes
format query string no "markdown"

Responses: 200 object, 401, 403, 404, 422, 429.

GET /api/v1/scenarios/{scenario_id}/llm-export

Export a scenario for an LLM.

A report made to be pasted into an LLM (a chat assistant or a coding agent) that will fix your voice agent: the scenario, the results, every failed check with the judge's quote, a readable transcript (AGENT = your agent, CALLER = our test caller), metrics, the judge's recommendations and a primer asking for a fix.

format=markdown (default) answers text/markdown; format=json answers the same document as compact JSON (format: "callqa.llm_report", version, level). Covers the scenario YAML, its last 5 runs, the checks that fail most and the last 3 failed calls.

Parameter In Type Required Default
scenario_id path string (uuid) yes
format query string no "markdown"

Responses: 200 object, 401, 403, 404, 422, 429.

Billing

Method Path Summary Scope Roles Success
GET /api/v1/billing/forecast Forecast of the month read admin, member, guest 200
POST /api/v1/billing/portal Open the billing portal admin admin 200
GET /api/v1/billing/referral Your referral link read admin, member, guest 200
GET /api/v1/billing/settings Overage, spend cap and alerts admin admin 200
PATCH /api/v1/billing/settings Change overage, the spend cap or the low balance threshold (admin) admin admin 200
GET /api/v1/billing/subscription Current subscription admin admin 200
POST /api/v1/billing/subscription/checkout Subscribe to a plan admin admin 200
GET /api/v1/plans Public plans read admin, member, guest 200
GET /api/v1/usage Quota usage this month read admin, member, guest 200
GET /api/v1/wallet Wallet balance admin admin 200
POST /api/v1/wallet/checkout Top up the wallet admin admin 200
GET /api/v1/wallet/transactions Wallet ledger admin admin 200

GET /api/v1/billing/forecast

Forecast of the month.

What the enabled schedules will consume until the quota resets (1st of next month, UTC), per schedule and environment: runs left x test cases x destination weight x average 10 minute slices observed over 30 days (maximum duration when a scenario never ran). Compared with the quota, bonus calls, overage settings and cap. Manual, API and CI runs are not forecast.

Responses: 200 ForecastOut, 401, 403, 422, 429.

POST /api/v1/billing/portal

Open the billing portal.

Returns a Stripe billing portal URL: change or cancel the plan, update the payment method, download invoices.

Body (application/json): PortalIn.

Responses: 200 CheckoutOut, 400 Error, 401, 403, 422, 429, 503 Error.

GET /api/v1/billing/referral

Your referral link.

Both organizations get the bonus calls when the referred one starts its first paid subscription. Referring yourself (a member of your organization, the same Stripe customer) or an existing customer gives nothing.

Responses: 200 ReferralOut, 401, 403, 422, 429.

GET /api/v1/billing/settings

Overage, spend cap and alerts.

Paid overage on/off, its monthly cap (wallet spend), what happens at the limit, and the wallet.low_balance threshold.

Responses: 200 BillingSettingsOut, 401, 403, 422, 429.

PATCH /api/v1/billing/settings

Change overage, the spend cap or the low balance threshold (admin).

Only the fields you send change; null removes the cap or turns wallet.low_balance off. When the cap is reached, new calls are not placed (blocked_insufficient_credit, block reason spend_cap_reached) until the next month or a higher cap. allow_paid_overage: false stops calls once the quota and bonus calls are used (overage_disabled); on_limit: pause_schedules also pauses the schedules until the 1st of next month.

Body (application/json): BillingSettingsPatch.

Responses: 200 BillingSettingsOut, 401, 403, 422, 429.

GET /api/v1/billing/subscription

Current subscription.

Responses: 200 SubscriptionOut, 401, 403, 422, 429.

POST /api/v1/billing/subscription/checkout

Subscribe to a plan.

Returns a Stripe Checkout URL for a first subscription. If your organization already has an active Stripe subscription the answer is 409 (subscription_exists): change plan, interval or payment method in the billing portal (POST /billing/portal).

Body (application/json): SubscriptionCheckoutIn.

Responses: 200 CheckoutOut, 400 Error, 401, 403, 409 Error, 422, 429, 503 Error.

GET /api/v1/plans

Public plans.

Plans you can subscribe to with POST /billing/subscription/checkout.

Responses: 200 list of PlanPublicOut, 401, 403, 422, 429.

GET /api/v1/usage

Quota usage this month.

Quota periods are calendar months (UTC). held = units reserved by calls in progress.

Responses: 200 UsageOut, 401, 403, 422, 429.

GET /api/v1/wallet

Wallet balance.

Calls beyond the monthly quota are paid from a prepaid wallet, at your plan's overage price times the destination weight. Credit is reserved before a call is dialed and the unused part is released after. When the wallet cannot cover a call, the call is not placed and ends with status blocked_insufficient_credit: top up with POST /wallet/checkout. Top-ups are between $20 and $5,000 (amount_cents 2000 to 500000).

Responses: 200 WalletOut, 401, 403, 422, 429.

POST /api/v1/wallet/checkout

Top up the wallet.

Calls beyond the monthly quota are paid from a prepaid wallet, at your plan's overage price times the destination weight. Credit is reserved before a call is dialed and the unused part is released after. When the wallet cannot cover a call, the call is not placed and ends with status blocked_insufficient_credit: top up with POST /wallet/checkout. Top-ups are between $20 and $5,000 (amount_cents 2000 to 500000).

Returns a Stripe Checkout URL. The wallet is credited when Stripe confirms the payment (webhook), not before.

Body (application/json): CheckoutIn.

Responses: 200 CheckoutOut, 400 Error, 401, 403, 422, 429, 503 Error.

GET /api/v1/wallet/transactions

Wallet ledger.

Append-only ledger: every balance change has a line.

Parameter In Type Required Default
limit query integer no 50
offset query integer no 0

Responses: 200 Page_WalletTxOut_, 401, 403, 422, 429.

Support

Method Path Summary Scope Roles Success
GET /api/v1/support/tickets List support tickets read admin, member, guest 200
POST /api/v1/support/tickets Open a support ticket read admin, member 201
GET /api/v1/support/tickets/{ticket_id} Get a support ticket read admin, member, guest 200
POST /api/v1/support/tickets/{ticket_id}/close Close a support ticket read admin, member 200
POST /api/v1/support/tickets/{ticket_id}/messages Reply to a support ticket read admin, member 201

GET /api/v1/support/tickets

List support tickets.

Ask the CallQA team: a bug, a question, feedback, a billing matter or an abuse report. A person of the team answers, usually within a few hours on business days; the answer is emailed to the author (and to the organization's admins when an API key opened the ticket) and readable here. Statuses: open (the team has to answer), waiting_customer (the team waits for you), answered, closed. A reply reopens a ticket.

Never paste a secret (API key, password, token): anything that looks like one is replaced by [redacted secret] before it is stored.

Parameter In Type Required Default
status query string no
limit query integer no 20
offset query integer no 0

Responses: 200 Page_TicketOut_, 401, 403, 422, 429.

POST /api/v1/support/tickets

Open a support ticket.

Ask the CallQA team: a bug, a question, feedback, a billing matter or an abuse report. A person of the team answers, usually within a few hours on business days; the answer is emailed to the author (and to the organization's admins when an API key opened the ticket) and readable here. Statuses: open (the team has to answer), waiting_customer (the team waits for you), answered, closed. A reply reopens a ticket.

Never paste a secret (API key, password, token): anything that looks like one is replaced by [redacted secret] before it is stored.

At most 20 new tickets a day per organization (429 rate_limited beyond).

Body (application/json): TicketIn.

Responses: 201 TicketDetail, 400 Error, 401, 403, 422 Error, 429.

GET /api/v1/support/tickets/{ticket_id}

Get a support ticket.

The ticket and every message, oldest first: the answers of the CallQA team have author staff.

Parameter In Type Required Default
ticket_id path string (uuid) yes

Responses: 200 TicketDetail, 401, 403, 404, 422, 429.

POST /api/v1/support/tickets/{ticket_id}/close

Close a support ticket.

When the matter is solved. A later reply reopens it.

Parameter In Type Required Default
ticket_id path string (uuid) yes

Responses: 200 TicketDetail, 401, 403, 404, 422, 429.

POST /api/v1/support/tickets/{ticket_id}/messages

Reply to a support ticket.

Adds a message and reopens the ticket (status open): the team is alerted. Never paste a secret.

Parameter In Type Required Default
ticket_id path string (uuid) yes

Body (application/json): MessageIn.

Responses: 201 TicketDetail, 400 Error, 401, 403, 404, 422, 429.

Schemas

Request and response objects, as named above. Full definitions in api/openapi.json under components.schemas.

ActiveCode

Field Type Required Description
attempt_id string (uuid) yes
method string yes
status pending | calling yes
expires_at string (date-time) yes
call_id string (uuid) yes
call_status string yes
dtmf_gate string no Keys pressed after the answer.

AlertChannelIn

Field Type Required Description
name string yes
kind slack | email yes
webhook_url string no Slack only: incoming webhook URL (https://hooks.slack.com/services/...).
to list of string no Email only: up to 10 recipients.
on_failure boolean no Alert when a run fails.
on_score_below integer no Alert when the run score is under this.
on_latency_p95_above_ms integer no Alert when a call's p95 latency is above this.
enabled boolean no

AlertChannelOut

Field Type Required Description
id integer yes
name string yes
kind slack | email yes
config object yes Slack: {webhook_url} masked (it is a secret). Email: {to}.
on_failure boolean yes
on_score_below integer yes
on_latency_p95_above_ms integer yes
enabled boolean yes
created_at string (date-time) yes

AlertChannelPatch

Field Type Required Description
name string no
webhook_url string no
to list of string no
on_failure boolean no
on_score_below integer no null removes the rule.
on_latency_p95_above_ms integer no null removes the rule.
enabled boolean no

AlertTestOut

Field Type Required Description
sent boolean yes
error string yes

ApiKeyCreated

Field Type Required Description
id integer yes
name string yes
prefix string yes
scopes list of string yes
allowed_cidrs list of string yes
created_at string (date-time) yes
expires_at string (date-time) yes
last_used_at string (date-time) yes
last_used_ip string yes
revoked_at string (date-time) yes
rotated_from_id integer yes
key string yes The API key. Shown only once: store it in your secrets.

ApiKeyIn

Field Type Required Description
name string yes
scopes list of string no
expires_in_days integer no
allowed_cidrs list of string no Only accept requests from these networks (IPv4 or IPv6, CIDR or single address, at most 20). Empty: any address.

ApiKeyOut

Field Type Required Description
id integer yes
name string yes
prefix string yes
scopes list of string yes
allowed_cidrs list of string yes
created_at string (date-time) yes
expires_at string (date-time) yes
last_used_at string (date-time) yes
last_used_ip string yes
revoked_at string (date-time) yes
rotated_from_id integer yes

ApiKeyPatch

Field Type Required Description
name string no
allowed_cidrs list of string no Replaces the list. [] removes the restriction.

ApiLogOut

Field Type Required Description
method string yes
path string yes
status_code integer yes
duration_ms integer yes
api_key_prefix string yes
ip string yes
request_id string yes
created_at string (date-time) yes

AttestIn

Field Type Required Description
accept boolean yes Must be true: you accept the attestation text.
signer_name string yes Full name and title of the person attesting.

AuditOut

Field Type Required Description
id integer yes
action string yes
actor_email string yes The person, for actions done in the dashboard.
actor_api_key_prefix string yes The API key, for API actions.
target_type string yes
target_id string yes
data object yes
ip string yes
created_at string (date-time) yes

BillingSettingsOut

Field Type Required Description
monthly_spend_cap_cents integer yes Most the wallet may spend per calendar month (UTC). null: no cap.
spent_this_month_cents integer yes Wallet spend this month, reservations of calls in progress included.
low_balance_threshold_cents integer yes wallet.low_balance is sent when the balance falls under this. null: never.
allow_paid_overage boolean yes Calls beyond the monthly quota and bonus calls are paid from the wallet. false: they are not placed (block reason overage_disabled).
on_limit block_calls | pause_schedules yes When the limit is reached (overage off, cap reached, no overage on the plan): block each call, or also pause the schedules until the 1st of next month (UTC).
schedules_paused boolean yes The schedules are paused by this rule now.

BillingSettingsPatch

Field Type Required Description
monthly_spend_cap_cents integer no
low_balance_threshold_cents integer no
allow_paid_overage boolean no Admins only (billing.overage).
on_limit string no

BindingIn

Field Type Required Description
enabled boolean no Run the scenario in this environment. A new binding is enabled unless false.
schedule string no Cron expression or every day at 09:00. "" removes the schedule; absent keeps it.
timezone string no IANA zone of the schedule. Absent keeps it (or the one written in schedule).
call_window string no HH:MM-HH:MM Area/City; scheduled calls wait for it. Without a zone, the schedule's zone. "" removes it.
schedule_enabled boolean no Pause (false) or resume the schedule without losing it.

BindingOut

Field Type Required Description
scenario_id string (uuid) yes
scenario_name string yes
environment string yes Environment slug.
enabled boolean yes
schedule string yes
cron string yes The schedule as a cron expression, in timezone.
timezone string yes
call_window string yes
schedule_enabled boolean yes
next_run_at string (date-time) yes
last_run_at string (date-time) yes
number_phone string yes The number dialed in this environment.

BulkBindingIn

Field Type Required Description
enabled boolean no Run the scenario in this environment. A new binding is enabled unless false.
schedule string no Cron expression or every day at 09:00. "" removes the schedule; absent keeps it.
timezone string no IANA zone of the schedule. Absent keeps it (or the one written in schedule).
call_window string no HH:MM-HH:MM Area/City; scheduled calls wait for it. Without a zone, the schedule's zone. "" removes it.
schedule_enabled boolean no Pause (false) or resume the schedule without losing it.
scenario_ids list of string (uuid) yes
action enable | disable | remove yes enable: activate (created if needed); disable: keep the binding and its schedule but do not run; remove: delete the binding.

BulkBindingOut

Field Type Required Description
action string yes
environment string yes
scenarios integer yes
bindings list of BindingOut yes

CallDetailOut

Field Type Required Description
id string (uuid) yes
run_id string (uuid) yes
variant_id string (uuid) yes
title string yes
target_phone string yes
status string yes
block_reason string yes
error string yes
verdict string yes
score integer yes
duration_ms integer yes
latency_p50_ms integer yes
latency_p95_ms integer yes
latency_max_ms integer yes
cost_cents integer yes
quota_units integer yes
replay_of string (uuid) yes
created_at string (date-time) yes
dispatched_at string (date-time) yes
completed_at string (date-time) yes
content_purged_at string (date-time) no When the call's content (trace, transcript, detailed evaluation) was deleted at the end of the plan's retention period. Score, verdict, latencies and cost remain.
run object yes The ResolvedRun that was dialed.
result object yes CallResult: verdicts, metrics, score.
transcript list of TurnOut yes
recording object yes
usage object yes
cost_breakdown object no Our measured cost of the call in USD (telephony, STT, TTS, LLM, judge). Not what you are charged: see cost_cents.

CallOut

Field Type Required Description
id string (uuid) yes
run_id string (uuid) yes
variant_id string (uuid) yes
title string yes
target_phone string yes
status string yes
block_reason string yes
error string yes
verdict string yes
score integer yes
duration_ms integer yes
latency_p50_ms integer yes
latency_p95_ms integer yes
latency_max_ms integer yes
cost_cents integer yes
quota_units integer yes
replay_of string (uuid) yes
created_at string (date-time) yes
dispatched_at string (date-time) yes
completed_at string (date-time) yes
content_purged_at string (date-time) no When the call's content (trace, transcript, detailed evaluation) was deleted at the end of the plan's retention period. Score, verdict, latencies and cost remain.

CheckoutIn

Field Type Required Description
amount_cents integer yes Top-up amount in USD cents (2000 to 500000).
success_url string no
cancel_url string no

CheckoutOut

Field Type Required Description
url string yes Redirect the user to this Stripe Checkout page.

CiOut

Field Type Required Description
threshold integer yes
passed boolean yes null while the run is not finished.

CompareOut

Field Type Required Description
a string yes
b string yes
kpis_a KpisOut yes
kpis_b KpisOut yes
scenarios list of ScenarioCompareOut yes
verdict_changes list of VerdictDiffOut yes

ComplianceOut

Field Type Required Description
status pending | allow | needs_review | block yes Compliance review of the current content: only allow can be run.
label string yes
categories list of string yes
reasons list of ComplianceReasonOut yes
reviewed_at string yes
decided_by_staff boolean yes
message string yes What it means and what to do next.
enforced boolean yes

ComplianceReasonOut

Field Type Required Description
category string yes
quote string yes Words of the scenario that caused the decision.
explanation string yes

DeliveryOut

Field Type Required Description
id string (uuid) yes
event_type string yes
status string yes
attempts integer yes
last_status_code integer yes
last_error string yes
created_at string (date-time) yes
delivered_at string (date-time) yes

DeriveIn

Field Type Required Description
count integer no

EnvironmentForecastOut

Field Type Required Description
environment string yes
units integer yes
runs_left integer yes
calls integer yes

EnvironmentIn

Field Type Required Description
name string yes
order integer no Promotion order, smallest first (development 10, staging 20, production 30). Default: after the last one.
color string no
is_default boolean no Runs that name no environment go here.

EnvironmentNumberOut

Field Type Required Description
target_id string (uuid) yes
target_phone string yes
number_id string (uuid) yes
number_phone string yes

EnvironmentOut

Field Type Required Description
id string (uuid) yes
name string yes
slug string yes
order integer yes
color string yes
is_default boolean yes
numbers list of EnvironmentNumberOut yes
created_at string (date-time) yes

EnvironmentPatch

Field Type Required Description
name string no
order integer no
color string no
is_default boolean no Only true is accepted: make another environment the default to change it.

Error

Field Type Required Description
detail string yes Human readable explanation. Can change: do not parse it.
code string yes Stable machine-readable code, e.g. missing_scope, feature_not_in_plan, not_found. See the errors guide.
feature string no feature_not_in_plan: the plan feature that is missing, e.g. release_compare.
required_plan string no Plan errors: the cheapest plan that includes the feature or lifts the limit.
upgrade_url string no Plan errors: page where an admin upgrades in one click (Stripe, prorated), then comes back.

EventOut

Field Type Required Description
seq integer yes
t_ms integer yes
at string (date-time) yes
type string yes
data object yes

ExportOut

Field Type Required Description
id string (uuid) yes
status queued | running | done | error | expired yes
created_at string (date-time) yes
finished_at string (date-time) yes
expires_at string (date-time) yes The archive is deleted at this date.
size_bytes integer yes
sha256 string yes SHA-256 of the zip archive.
counts object yes Number of objects of each kind.
error string yes
download_url string yes Signed link valid 24 hours, no API key needed. Set when done.

ForecastOut

Field Type Required Description
period_start string yes
period_end string yes First day of the next period (exclusive).
included integer yes
bonus_remaining integer yes
used_units integer yes Used or reserved this month (quota, bonus, wallet).
scheduled_units integer yes What the enabled schedules will use until the end of the month.
projected_units integer yes
will_exceed boolean yes The schedules will use more than the quota and bonus calls left.
overage_units integer yes
estimated_overage_cents integer yes Wallet cost of overage_units at your plan's price, uncapped. null: the plan has no paid overage.
month_overage_cents integer yes Spent so far + estimated.
blocked_units integer yes Units expected to be blocked: overage off, no overage on the plan, or the monthly cap.
overage_allowed boolean yes
spend_cap_cents integer yes
spent_cents integer yes
overage_price_cents integer yes
level ok | warn | danger yes
schedules_paused boolean yes
schedules list of ScheduleForecastOut yes
environments list of EnvironmentForecastOut yes

FromBundleIn

Field Type Required Description
call_id string (uuid) no Build the bundle from this call...
bundle object no ...or pass a ReplayBundle.
title string no

GenerateIn

Field Type Required Description
description string yes What the voice service does, in plain words.
phone string yes
language string no
count integer no

GenerationJobOut

Field Type Required Description
id string (uuid) yes
kind propose | derive | edge_case yes
status queued | running | done | error yes
scenario_id string (uuid) yes
call_id string (uuid) yes
result object yes When done. propose: {scenarios: [...], rejected}; derive: {variants: [...], rejected}; edge_case: {variant: {...}}.
error string yes
usage object yes
created_at string (date-time) yes
finished_at string (date-time) yes
url string yes Poll this URL until status is done or error.

InvitationIn

Field Type Required Description
email string yes
role admin | member | guest no

KeyRotateIn

Field Type Required Description
grace_period_hours integer no How long the old key keeps working. 0 revokes it now.

KpisOut

Field Type Required Description
tests integer yes
passed integer yes
failed integer yes
warned integer yes
pass_rate number yes
avg_score number yes
avg_latency_ms integer yes
p95_latency_ms integer yes
interruptions_per_call number yes
cost_cents integer yes

LastAttempt

Field Type Required Description
attempt_id string (uuid) yes
method string yes
status succeeded | failed | error | expired | calling yes
outcome string yes matched, not_heard, not_answered, wrong_code, call_not_placed...
heard_digits string yes Digits we understood (the code is masked with * when it matched).
sip_status integer no When the line never answered: the SIP final answer of the destination (480 unavailable, 408 no answer, 486 busy, 603 declined, 404 unknown number...).
sip_reason string no The carrier's short reason for sip_status.
failure_hint string no What to fix before the next attempt, in plain words (null when the attempt did not fail). Show it to the user as is.
call_id string (uuid) yes
finished_at string (date-time) yes
dtmf_gate string no Keys pressed after the answer.

MatrixCell

Field Type Required Description
environment string yes
bound boolean yes
enabled boolean yes
schedule string yes
timezone string yes
call_window string yes
schedule_enabled boolean yes
next_run_at string (date-time) yes
running boolean yes A run is queued or in progress.
last_run MatrixRun yes Last completed run.
trend MatrixTrend yes

MatrixEntry

Field Type Required Description
scenario_id string (uuid) yes
name string yes
run_id string (uuid) yes
score integer yes

MatrixEnvironment

Field Type Required Description
id string (uuid) yes
slug string yes
name string yes
color string yes
order integer yes
is_default boolean yes

MatrixOut

Field Type Required Description
environments list of MatrixEnvironment yes
scenarios list of MatrixScenario yes
versions list of MatrixVersionRow yes The last versions of each environment, newest first per environment.
totals object yes Per environment slug, over the enabled scenarios: last verdicts.

MatrixRun

Field Type Required Description
id string (uuid) yes
number integer yes
created_at string (date-time) yes
verdict pass | warn | fail yes
score integer yes
total integer yes
passed integer yes
version MatrixVersion yes

MatrixScenario

Field Type Required Description
id string (uuid) yes
name string yes
target_phone string yes
tags list of string yes
cells list of MatrixCell yes One per environment, in environments order.

MatrixTotals

Field Type Required Description
passed integer yes
warned integer yes
failed integer yes
untested integer yes
running integer yes

MatrixTrend

Field Type Required Description
scores list of integer yes Scores of the last completed runs, oldest first.
direction string yes

MatrixVersion

Field Type Required Description
id string (uuid) yes
label string yes
git_sha string yes
deployed_at string (date-time) yes

MatrixVersionRow

Field Type Required Description
id string (uuid) yes
label string yes
environment string yes
deployed_at string (date-time) yes
git_ref string yes
git_sha string yes
source_url string yes
tags list of string yes
passed list of MatrixEntry yes
warned list of MatrixEntry yes
failed list of MatrixEntry yes
untested list of MatrixEntry yes Enabled in the environment, no run on this version.

MeOut

Field Type Required Description
org_id string (uuid) yes
org_name string yes
org_slug string yes
api_key_prefix string yes
scopes list of string yes
plan PlanOut yes
subscription_status string yes
allowed_countries list of string yes

MemberOut

Field Type Required Description
id integer yes
email string yes
role admin | member | guest yes
joined_at string (date-time) yes

MessageIn

Field Type Required Description
body string yes Markdown.

MessageOut

Field Type Required Description
id integer yes
author customer_human | customer_agent | staff yes staff is the CallQA team.
author_name string yes
body string yes
created_at string (date-time) yes
read_by_customer boolean yes For a message of the team: a person of the organization opened it in the app.

NumberIn

Field Type Required Description
target_id string (uuid) yes The number the scenarios point to.
number_id string (uuid) yes The number to dial instead in this environment (registered and verified); null or equal to target_id removes the mapping.

Page_ApiLogOut_

Field Type Required Description
count integer yes Total number of matching items.
limit integer yes
offset integer yes
items list of ApiLogOut yes

Page_AuditOut_

Field Type Required Description
count integer yes Total number of matching items.
limit integer yes
offset integer yes
items list of AuditOut yes

Page_CallOut_

Field Type Required Description
count integer yes Total number of matching items.
limit integer yes
offset integer yes
items list of CallOut yes

Page_DeliveryOut_

Field Type Required Description
count integer yes Total number of matching items.
limit integer yes
offset integer yes
items list of DeliveryOut yes

Page_EventOut_

Field Type Required Description
count integer yes Total number of matching items.
limit integer yes
offset integer yes
items list of EventOut yes

Page_ExportOut_

Field Type Required Description
count integer yes Total number of matching items.
limit integer yes
offset integer yes
items list of ExportOut yes

Page_RunOut_

Field Type Required Description
count integer yes Total number of matching items.
limit integer yes
offset integer yes
items list of RunOut yes

Page_TicketOut_

Field Type Required Description
count integer yes Total number of matching items.
limit integer yes
offset integer yes
items list of TicketOut yes

Page_VersionOut_

Field Type Required Description
count integer yes Total number of matching items.
limit integer yes
offset integer yes
items list of VersionOut yes

Page_WalletTxOut_

Field Type Required Description
count integer yes Total number of matching items.
limit integer yes
offset integer yes
items list of WalletTxOut yes

PlanOut

Field Type Required Description
code string yes
name string yes
included_calls_per_month integer yes
overage_price_cents integer yes Wallet price of one extra call unit.
max_seats integer yes
max_countries integer yes
max_concurrent_calls integer yes
features object yes

PlanPublicOut

Field Type Required Description
code string yes
name string yes
included_calls_per_month integer yes
overage_price_cents integer yes Wallet price of one extra call unit.
max_seats integer yes
max_countries integer yes
max_concurrent_calls integer yes
features object yes
monthly_price_cents integer yes
annual_monthly_price_cents integer yes Per month, billed yearly.

PortalIn

Field Type Required Description
return_url string no

PromoteIn

Field Type Required Description
environment string yes Environment to promote to, by slug, name or id.
released_at string (date-time) no Default: now.

ReferralOut

Field Type Required Description
code string yes
url string yes Share it: a new organization created through it gets, like yours, bonus_calls_each bonus calls at its first paid subscription.
bonus_calls_each integer yes
pending integer yes Referred organizations that have not paid yet.
granted integer yes
calls_earned integer yes

ReleaseIn

Field Type Required Description
version string yes
notes string no
released_at string (date-time) no
target_id string (uuid) no
environment string no Environment, by slug, name or id (staging). Default: the organization's default environment.

ReleaseOut

Field Type Required Description
id string (uuid) yes
version string yes
notes string yes
released_at string (date-time) yes
target_id string (uuid) yes
environment string yes Slug of the release's environment.

ReleasePatch

Field Type Required Description
version string no
notes string no
released_at string (date-time) no
target_id string (uuid) no Send null to detach the release from its target.

ReplayIn

Field Type Required Description
call_id string (uuid) no Replay this call of your organization...
bundle object no ...or an exported ReplayBundle.
same_runtime boolean no Reuse the bundle's models, voices and settings (exact reproduction).

RoleIn

Field Type Required Description
role admin | member | guest yes

RunOut

Field Type Required Description
id string (uuid) yes
number integer yes
scenario_id string (uuid) yes
scenario_name string yes
trigger string yes
status queued | running | completed | canceled yes
verdict string yes
score integer yes
total integer yes
passed integer yes
failed integer yes
warned integer yes
errored integer yes
blocked integer yes
cost_cents integer yes
version string yes Label of the run's version: the one the trigger named, else the one live in its environment when it ran.
version_id string (uuid) yes
release string yes Deprecated: same value as version.
tags list of string no
environment string yes Slug of the run's environment.
created_at string (date-time) yes
started_at string (date-time) yes
finished_at string (date-time) yes
ci CiOut no
url string yes

Sandbox

Field Type Required Description
calls_left_24h integer yes
max_concurrent_calls integer yes
max_duration_s integer yes
schedules_and_ci boolean yes

ScenarioCompareOut

Field Type Required Description
scenario_id string (uuid) yes
name string yes
a SideScore yes
b SideScore yes
score_delta number yes

ScenarioInvalid

Field Type Required Description
detail string no
code string no
errors list of ValidationIssue yes

ScenarioOut

Field Type Required Description
id string (uuid) yes
name string yes
target_id string (uuid) yes
target_phone string yes
source string yes
tags list of string yes
variants integer yes
archived boolean yes
schedule string yes
updated_at string (date-time) yes
document object yes Full scenario document, variants included.
variant_list list of VariantOut yes
compliance ComplianceOut no Compliance review (acceptable use policy) of this content.

ScenarioSummary

Field Type Required Description
id string (uuid) yes
name string yes
target_id string (uuid) yes
target_phone string yes
source string yes
tags list of string yes
variants integer yes
archived boolean yes
schedule string yes
updated_at string (date-time) yes

ScheduleForecastOut

Field Type Required Description
scenario_id string (uuid) yes
scenario_name string yes
environment string yes Environment of this schedule (a scenario has one schedule per environment): decides the number dialed.
schedule string yes Cron expression, local time of timezone.
timezone string yes
call_window string yes
runs_left integer yes Runs before the 1st of next month, 00:00 UTC.
test_cases integer yes Enabled variants (1 without variants).
weight integer yes Units per 10 minute slice for the destination; 0 when the destination is blocked.
avg_duration_s number yes Average duration of the scenario's calls of the last 30 days.
observed boolean yes false: never ran recently, counted at its maximum duration.
slices_per_call number yes
units integer yes

SecretRotateIn

Field Type Required Description
grace_period_hours integer no How long the previous secret keeps signing deliveries (second v1). 0 stops it at once.

SideScore

Field Type Required Description
tests integer yes
pass_rate number yes
score number yes
latency_ms integer yes

StabilityOut

Field Type Required Description
environment string yes
tests integer yes
pass_rate number yes
avg_score number yes
p95_latency_ms integer yes
flaky integer yes Test cases that both passed and failed in the period.
last_run_id string (uuid) yes
last_run_verdict string yes

SubscriptionCheckoutIn

Field Type Required Description
plan string yes A public plan code (GET /plans).
interval monthly | annual no
success_url string no
cancel_url string no

SubscriptionOut

Field Type Required Description
plan PlanOut yes
status string yes
interval string yes
current_period_start string (date-time) yes
current_period_end string (date-time) yes
cancel_at_period_end boolean yes
managed_by_stripe boolean yes false for plans invoiced manually.
payment_retry_until string (date-time) no status past_due: the renewal payment failed and Stripe retries it. The plan keeps working until this date; update the card in the billing portal before it or the subscription is canceled.

TagBulkIn

Field Type Required Description
add list of string no Tags to add, created if new.
remove list of string no Tags to remove.
objects TagObjects yes

TagBulkOut

Field Type Required Description
added list of string yes
removed list of string yes
objects object yes Objects touched, per kind.

TagIn

Field Type Required Description
name string yes
color string no

TagObjects

Field Type Required Description
scenarios list of string (uuid) no
runs list of string (uuid) no
versions list of string (uuid) no
numbers list of string (uuid) no Phone numbers (targets).

TagOut

Field Type Required Description
id integer yes
name string yes
color string yes

TagPatch

Field Type Required Description
name string no
color string no

TagSuggestion

Field Type Required Description
id integer yes
name string yes
color string yes
uses integer yes Objects carrying the tag (scenarios, runs, versions, numbers).

TargetIn

Field Type Required Description
name string yes
phone string yes E.164 number, e.g. +14155550100.
kind ai_agent | ivr | iva | human | unknown no
platform string no
language string no
description string no

TargetOut

Field Type Required Description
id string (uuid) yes
name string yes
phone string yes
country string yes
line_type string yes
kind string yes
platform string yes
language string yes
description string yes
verification_status unverified | pending | verified | expired | locked | suspended yes
verification_method string yes
verified_at string (date-time) yes
verification_expires_at string (date-time) yes
attested boolean yes The legal attestation is signed (sandbox calls).
created_at string (date-time) yes

TargetPatch

Field Type Required Description
name string no
kind string no
platform string no
language string no
description string no

TicketDetail

Field Type Required Description
id string (uuid) yes
ref string yes Short reference, e.g. CQA-42.
kind bug | question | feedback | billing | abuse_report yes
status open | waiting_customer | answered | closed yes
channel mcp | api | app | email yes
subject string yes
created_by string yes
call_id string (uuid) yes
run_id string (uuid) yes
scenario_id string (uuid) yes
context object yes
created_at string (date-time) yes
updated_at string (date-time) yes
last_staff_at string (date-time) yes Last answer of the team.
unread integer yes Answers of the team nobody of the organization has opened in the app yet.
message_count integer yes
url string yes The ticket in the app.
messages list of MessageOut yes
secrets_redacted integer no On create and reply: how many things that looked like secrets were removed.

TicketIn

Field Type Required Description
subject string yes
body string yes Markdown. What you tried, what happened (error code and detail), what you expected.
kind bug | question | feedback | billing | abuse_report no
call_id string (uuid) no A call this is about.
run_id string (uuid) no A run this is about.
scenario_id string (uuid) no A scenario this is about.
context object no Free JSON (8 KB at most): client name and version, OS, the request that failed...

TicketOut

Field Type Required Description
id string (uuid) yes
ref string yes Short reference, e.g. CQA-42.
kind bug | question | feedback | billing | abuse_report yes
status open | waiting_customer | answered | closed yes
channel mcp | api | app | email yes
subject string yes
created_by string yes
call_id string (uuid) yes
run_id string (uuid) yes
scenario_id string (uuid) yes
context object yes
created_at string (date-time) yes
updated_at string (date-time) yes
last_staff_at string (date-time) yes Last answer of the team.
unread integer yes Answers of the team nobody of the organization has opened in the app yet.
message_count integer yes
url string yes The ticket in the app.

TriggerIn

Field Type Required Description
variants list of string (uuid) no Subset of variant ids; default all enabled variants.
version string no Version of your code under test (v2.3.0, a short sha), in the run's environment; created if new. Without it the run belongs to the version live in the environment.
git_ref string no Branch or tag of the version, stored when the version is created.
git_sha string no
source_url string no Commit or pipeline page.
release string no Deprecated: the former name of version, read when version is absent.
tags list of string no Free keywords added to the run.
ci boolean no Mark the run as triggered by CI.
environment string no Environment, by slug, name or id (staging). Default: the organization's default environment. The version, when given, is looked up (or created) in this environment.

TurnOut

Field Type Required Description
t_ms integer yes
speaker string yes
text string yes
start_ms integer yes
end_ms integer yes

UsageOut

Field Type Required Description
period_start string yes
included integer yes
used integer yes
held integer yes
remaining integer yes
bonus_remaining integer yes
overage_price_cents integer yes
wallet_available_cents integer yes
plan string yes

ValidationIssue

Field Type Required Description
loc string yes Dotted path of the offending field.
msg string yes

VariantIn

Field Type Required Description
title string yes
source auto | manual no
persona object no
goal string no
instructions string no
test_data object no
assertions list of object no
enabled boolean no

VariantOut

Field Type Required Description
id string (uuid) yes
index integer yes
title string yes
source auto | manual yes
enabled boolean yes
spec object yes callqa_spec Variant document.

VariantPatch

Field Type Required Description
enabled boolean no
spec object no Replace the variant document.

VerdictDiffOut

Field Type Required Description
scenario string yes
index integer yes
title string yes
a string yes
b string yes
a_call_id string (uuid) yes
b_call_id string (uuid) yes
regressed boolean yes True when the test case passes on side A only.

VerificationCallIn

Field Type Required Description
dtmf_gate string no Replace the keys to press of the active code (see start). {code} only works when it was already given to start or retry.

VerificationCallOut

Field Type Required Description
call_id string (uuid) yes
verification VerificationOut yes

VerificationCodeOut

Field Type Required Description
code string yes Shown ONCE, never stored in clear: copy it now.
phrase string yes Add this sentence to the greeting or the agent prompt.
code_expires_at string (date-time) yes
method string yes
verification VerificationOut yes

VerificationOut

Field Type Required Description
status unverified | pending | verified | expired | locked | suspended yes
method string yes
verified_at string (date-time) yes
expires_at string (date-time) yes Re-verification needed after this date.
locked_until string (date-time) yes
suspended_reason string yes
attested boolean yes
attested_at string (date-time) yes
attestation_version string no Version of the signed text.
attestation_current_version string yes Version to sign today.
attestation_outdated boolean no Signed under a text that is no longer accepted: sign again, calls to this number are blocked until then.
attempts_left integer yes Failed attempts allowed before a 24 h lock. A call that is not answered counts too.
attempts_max integer no Failed attempts allowed per number in 24 h.
attempts_reset_at string (date-time) no When the oldest failed attempt stops counting (24 h after it ended) and one attempt comes back; null when none counts.
dtmf_gate string no Keys remembered for the next verification call (IVR menu).
active_code ActiveCode yes
last_attempt LastAttempt yes
sandbox Sandbox yes Limits while the number is not verified (attestation signed).
inbound_number string yes Number to call for inbound_dtmf.

VerificationStartIn

Field Type Required Description
method spoken_code | inbound_dtmf no spoken_code (default): put the code in the line's greeting or agent prompt, then POST .../verification/call. inbound_dtmf (when enabled): call our verification number from the line and type the code.
attestation AttestIn no Sign the attestation in the same request (required once).
dtmf_gate string no Keys the verification call presses after the line answers, before listening for the code, when the code is behind an IVR menu: 0-9, *, #, w for a 0.5 s pause (as in Twilio's sendDigits), at most 40 characters. Example: 1w2. {code} types the code itself (reserved to CallQA test lines). Omitted: the keys remembered on the number; empty string: no keys.
renew boolean no Verify a verified number again (renews the 180 days). The number stays verified during the attempt and if it fails.

VerifyIn

Field Type Required Description
method string no Legacy endpoint: signs the attestation only.
accept boolean yes Must be true: you accept the attestation text.
signer_name string yes Full name of the person attesting.

VersionIn

Field Type Required Description
label string yes
environment string no Environment, by slug, name or id (staging). Default: the organization's default environment.
deployed_at string (date-time) no When it went live. Default: now.
git_ref string no
git_sha string no
source_url string no
notes string no
target_id string (uuid) no Only for this phone number (default: every number).
tags list of string no

VersionOut

Field Type Required Description
id string (uuid) yes
label string yes
environment string yes Slug of the version's environment.
deployed_at string (date-time) yes
git_ref string yes
git_sha string yes
source_url string yes
notes string yes
target_id string (uuid) yes
tags list of string yes
created_at string (date-time) yes

VersionPatch

Field Type Required Description
label string no
deployed_at string (date-time) no
git_ref string no
git_sha string no
source_url string no
notes string no
target_id string (uuid) no Send null to apply the version to every number.
tags list of string no Replaces the tags.

VersionPromoteIn

Field Type Required Description
environment string yes Environment to promote to, by slug, name or id.
deployed_at string (date-time) no Default: now.

WalletOut

Field Type Required Description
balance_cents integer yes
held_cents integer yes
available_cents integer yes
currency string yes

WalletTxOut

Field Type Required Description
id integer yes
kind string yes
amount_cents integer yes
held_delta_cents integer yes
balance_after_cents integer yes
call_id string (uuid) yes
description string yes
created_at string (date-time) yes

WebhookCreated

Field Type Required Description
id string (uuid) yes
url string yes
events list of string yes
description string yes
enabled boolean yes
created_at string (date-time) yes
filters WebhookFilters yes
previous_secret_expires_at string (date-time) no While set and in the future, deliveries carry a second v1 signature made with the previous secret.
secret string yes Signing secret. Shown only once.

WebhookFilters

Field Type Required Description
scenario_ids list of string (uuid) no Only events of these scenarios.
tags list of string no Only events of scenarios or runs carrying one of these tags.
environments list of string no Only events of these environments (slug, name or id).

WebhookIn

Field Type Required Description
url string yes
events list of string yes Event types, or ['*'] for all.
description string no
enabled boolean no
filters WebhookFilters no Optional scope. Each non-empty list must match (any value of a list, all lists). Call, run and version events are filtered; organization events (wallet.low_balance) always go through. Empty or absent: every event.

WebhookOut

Field Type Required Description
id string (uuid) yes
url string yes
events list of string yes
description string yes
enabled boolean yes
created_at string (date-time) yes
filters WebhookFilters yes
previous_secret_expires_at string (date-time) no While set and in the future, deliveries carry a second v1 signature made with the previous secret.

WebhookPatch

Field Type Required Description
url string no
events list of string no
description string no
enabled boolean no
filters WebhookFilters no Optional scope. Each non-empty list must match (any value of a list, all lists). Call, run and version events are filtered; organization events (wallet.low_balance) always go through. Empty or absent: every event. Send {} or null to remove every filter.