{
  "openapi": "3.1.1",
  "info": {
    "title": "Agentive Voice API",
    "version": "1.0.0",
    "summary": "Place calls, have an AI voice agent call a customer, trigger saved campaigns, read call records, and send and check voice verification codes.",
    "description": "The public REST API for the Agentive Voice platform.\n\n## Base URL\n\n`https://voice.agentive.co.in/api/public/v1`\n\nOnce your credentials are accepted, any path under the base that is not a\nroute below answers 404 with `error_code` `not_found` and the sentence\n\"Unknown endpoint.\" Authentication runs first, so an unknown path sent\nwithout a usable key answers 401 rather than 404.\n\n## Authentication\n\nEvery request needs a publishable key and a secret. Send them as the\n`x-api-key` and `x-api-secret` headers. HTTP Basic\n(`Authorization: Basic base64(key:secret)`) is accepted as an alias and is\nread only when `x-api-key` is absent. On a POST the secret may also be\nsent as a `secret` field in the JSON body, which is the last resort:\nprecedence is header, then Basic password, then body.\n\nThe publishable key is `pk_live_` plus 48 hex characters. The secret is\n`sk_live_` plus 64 hex characters, is stored only as a hash, is compared\nin constant time, and is shown once at generation or rotation. It can\nnever be revealed again.\n\nAn unknown key and a wrong secret on a known key return the identical\n401 body, so a key cannot be enumerated. Every authenticated request is\nscoped to its own account.\n\nTreat both keys as secrets, the publishable key included: keep them on\nyour server and send them only to this base URL, over HTTPS. For a\ndownload, prefer HTTP Basic, which HTTP clients never forward to another\nhost. The API has no address allow list, and a key is account wide.\nA request body larger than 64 KB is refused with 413 before it is read.\n\n## Entitlements\n\nThe account needs the master API entitlement before any route answers.\nWithout it every request is 403 with `error_code` `api_disabled`. Each\nroute then checks the product it belongs to, read fresh on every request,\nso an operator switching a product on takes effect on the next call. A\nmissing product is 403 with `error_code` `feature_disabled`.\n\n`GET /account` returns `products` (`ai_voice_agent`, `voice_broadcast`,\n`verification_calls`), which is what the key can use. An account with the\nAI voice agent and no voice broadcast reads only its AI calls: any other id\non the call read routes answers 404, never 403. Any other account,\nincluding one with neither product, reads all its calls. The transcript\nand analysis routes answer for AI calls only on every account.\n\n## Envelopes\n\nTwo JSON shapes. Single resource routes answer\n`{ status, code, unique_id, reference_id, details, ...extra }`, and their\nerrors answer the same keys plus `error_code` (a stable machine token) and\n`error` (the same sentence as `details`). Collection routes answer\n`{ status, code, data }`, with a `pagination` block on `GET /calls` only.\n\n## Rate limits\n\nFour independent limits, all counted in fixed 60 second windows.\n\n1. Before authentication, keyed on the attributed client address: 240\n   requests per minute. Only requests refused with 401 or 403 are\n   counted, so a working integration never meets it.\n2. Before authentication, keyed on the network peer: 2400 requests per\n   minute, so a caller forging client attribution headers is still bounded.\n3. After authentication, per account: 120 requests per minute in each of\n   two buckets, one for GET and one for every other method, so a polling\n   loop cannot starve call placement.\n4. Failed authentications per publishable key and client address: 20 per\n   minute. A request with the correct secret is always admitted, so a\n   stranger guessing at your key cannot lock your integration out.\n\n`POST /otp/verify` additionally has its own budget of 60 verification\nchecks per minute per account.\n\nEvery response carries `RateLimit-Limit`, `RateLimit-Remaining` and\n`RateLimit-Reset`, plus the `X-RateLimit-*` aliases with the same values.\nThey describe the bucket for that request's method. Every 429 and every\n409 carries `error_code`, `retry_after_sec` and a `Retry-After` header.\n`Retry-After` is the time until the window resets, so at most 60 seconds\nfor a request budget and at most 600 seconds for the per number cap.\n\nCalls placed through the API to one number are capped at 3 in 10 minutes\nand 10 in an IST day per account, across every call placing route, with\n429 `number_flood`.\n\n## Lines\n\nThe account is sold a number of concurrent lines. Each call placing\nrequest is admitted against them: 1 line for an audio blast, a\nverification call or a campaign trigger, and 2 for a press 1 call because\nit can bridge a second leg. Admitted or refused,\nthose responses carry `X-Agentive-Lines`, `X-Agentive-Lines-In-Use` and\n`X-Agentive-Lines-Mode`. The mode is `off` (not evaluated), `shadow`\n(decided and logged, always admitted) or `on` (a request past the line\ncount is refused with 429 `concurrency_limit`). Read the mode header\nrather than reading a 200 as proof of headroom.\n\n## Calling window\n\nPromotional calls (audio blast, press 1, AI voice agent calls and\ncampaign triggers) are refused outside 09:00 to 21:00 IST with 409\n`calling_hours`, unless the account has anytime calling enabled. If the\naccount's calling-hours setting cannot be read, the call is refused with\n503 `service_unavailable` and `retry_after_sec` 30, never placed.\nVerification calls are exempt and run 24 hours.\n\n## Idempotency\n\n`POST /calls`, `POST /ai/calls`, `POST /campaigns/{id}/trigger`,\n`POST /otp/send` and `POST /otp/verify` each accept an optional\n`Idempotency-Key` header (1 to 128 printable ASCII characters; a longer\nkey is refused with 400 `validation_error`). Within 24 hours the same\naccount plus key replays the original status and body with\n`idempotency-replayed: true` and `Idempotent-Replayed: true` set. The same\nkey with a different request body is 422 `idempotency_conflict`. A request\nstill running under that key is 409 `idempotency_in_progress`.\n\nStored outcomes are 2xx plus 400, 401, 404 and 422. A stored 400 or 404\nkeeps replaying for the 24 hours even after the request or the dashboard\nsetting behind it is fixed, so send the fixed request with a new key.\nTransient refusals\n(402, 403, 409, 429) and a 5xx raised before the dial engine ran are\nreleased, so the same key can be retried at once. A 5xx raised after the\nengine was invoked is stored and rewritten with `error_code`\n`call_state_unknown`, because a blind retry there could dial twice.\n\n## Money and time\n\nAmounts are Indian rupees. Timestamps are published twice beside the raw\nstored value: `created_at_ist` is ISO 8601 at +05:30 and `created_at_utc`\nis ISO 8601 with a Z.\n\n## Webhook verification\n\nVerify `X-Agentive-Signature-V2` only: `t=<unix seconds>,v2=<hex>`,\nwhere the hex is HMAC-SHA256 with the signing secret over `t`, a dot and\nthe raw body. Reject a `t` more than five minutes from your clock, compare\nin constant time after checking byte lengths, and refuse a delivery\nwithout the header. The older `X-Agentive-Signature` signs the body\nalone and cannot prove freshness; never accept it on its own.\n\nEvery webhook address (account endpoints, campaign and call menu hooks,\nan AI agent's own webhook) must be `https://` on port 443 or 8443; the\nrule is checked on every delivery, so an address that breaks it gets nothing.\nAccount webhooks describe calls on the account, not only calls placed\nthrough the API (`via_api` marks those). An account with the AI voice\nagent and no voice broadcast receives events for its AI calls only; any\nother account, including one with neither product, receives all its calls.\n\n## Reporting a vulnerability\n\nWrite to hello@agentive.co.in. The contact is also published at\nhttps://agentive.co.in/.well-known/security.txt.",
    "contact": {
      "name": "Agentive",
      "url": "https://agentive.co.in/contact"
    }
  },
  "servers": [
    {
      "url": "https://voice.agentive.co.in/api/public/v1",
      "description": "Production"
    }
  ],
  "security": [
    {
      "ApiKey": [],
      "ApiSecret": []
    }
  ],
  "tags": [
    {
      "name": "Calls",
      "description": "Place calls and read what happened on them."
    },
    {
      "name": "AI voice agent",
      "description": "Have an AI voice agent call a customer, and manage which agent answers a number."
    },
    {
      "name": "Campaigns",
      "description": "Fire a saved campaign for one number."
    },
    {
      "name": "Verification",
      "description": "Voice verification codes: send one, then check what the recipient read back."
    },
    {
      "name": "Account",
      "description": "Wallet balance and live line usage."
    }
  ],
  "paths": {
    "/calls": {
      "post": {
        "operationId": "placeCall",
        "tags": [
          "Calls"
        ],
        "summary": "Place one call immediately",
        "description": "Places one call without a saved campaign. Three kinds, chosen with\n`type`: an audio blast, a press 1 call, or a voice verification code.\n\nThe guards run in a fixed order, and nothing is written until every one\nhas passed. Three run before the paths split: the `type` gate, then the\nproduct gate for an audio blast or a press 1 call, then phone\nvalidation.\n\nThe verification path then runs its own product gate, the balance floor,\nsupplied code validation, the number lock, spacing, the daily cap, the\n10 minute cap, line admission, and only then the engine. No verification\nrow exists and no validity clock starts before that.\n\nThe broadcast path then runs the calling window, `audio_id`,\n`caller_id`, the balance floor, digit map normalisation, line admission,\nthen the engine.\n\nA press 1 call reserves 2 lines. Everything else reserves 1.\n\nThe verification response is byte for byte the response of\n`POST /otp/send`. The two share one guard helper from the balance floor\nonward, so those checks and their error bodies cannot drift. The steps\nbefore it differ: this route validates the number before its product\ngate, and `POST /otp/send` checks the product gate first.",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PlaceCallRequest"
              },
              "examples": {
                "audioBlast": {
                  "summary": "Audio blast",
                  "value": {
                    "type": "audio_blast",
                    "number": "9876543210",
                    "audio_id": 4821,
                    "reference_id": "order-7781"
                  }
                },
                "pressOne": {
                  "summary": "Press 1 call with a digit map",
                  "value": {
                    "type": "press1",
                    "number": "+919876543210",
                    "audio_id": 4821,
                    "dtmf_map": {
                      "1": "connect",
                      "2": "decline"
                    }
                  }
                },
                "verification": {
                  "summary": "Voice verification code",
                  "value": {
                    "type": "otp",
                    "number": "9876543210",
                    "length": 6,
                    "ttl": 300
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Queued. A broadcast answers the run envelope; a verification answers the verification envelope.",
            "headers": {
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "idempotency-replayed": {
                "$ref": "#/components/headers/IdempotencyReplayed"
              },
              "Idempotent-Replayed": {
                "$ref": "#/components/headers/IdempotencyReplayed"
              },
              "X-Agentive-Lines": {
                "$ref": "#/components/headers/AgentiveLines"
              },
              "X-Agentive-Lines-In-Use": {
                "$ref": "#/components/headers/AgentiveLinesInUse"
              },
              "X-Agentive-Lines-Mode": {
                "$ref": "#/components/headers/AgentiveLinesMode"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/RunQueuedResponse"
                    },
                    {
                      "$ref": "#/components/schemas/VerificationQueuedResponse"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "`validation_error` when `type` is missing or not one of the three, when\n`audio_id` is missing or is not a clip on this account, when a digit map\nnames the `whatsapp` action, or when the dial engine rejects the\nparameters. `invalid_number` when the number is not a valid Indian\nmobile. `invalid_code` when a supplied code is not 4 to 8 digits.\n`invalid_caller_id` when `caller_id` is not on the account or is not\nenabled for voice broadcast, and when no number on the account is\nbroadcast enabled at all.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/InsufficientBalance"
          },
          "403": {
            "description": "`feature_disabled` when the account lacks the product for the requested type. `trial_restricted` when a trial account calls a number that is not its verified number. Also `account_inactive` and `api_disabled`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "409": {
            "description": "`calling_hours` for an audio blast or press 1 call outside 09:00 to 21:00 IST. `idempotency_in_progress` when a request with this key is still running.",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "422": {
            "$ref": "#/components/responses/IdempotencyConflict"
          },
          "429": {
            "description": "`number_locked` after too many wrong verification codes for this number.\n`number_flood` for the verification spacing, the 10 per day cap, the 3\nper 10 minutes cap, or the dial engine's own per number refusal on any\ntype. `concurrency_limit` when every purchased line is in use.\n`rate_limited` for a request rate bucket.",
            "headers": {
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfter"
              },
              "X-Agentive-Lines": {
                "$ref": "#/components/headers/AgentiveLines"
              },
              "X-Agentive-Lines-In-Use": {
                "$ref": "#/components/headers/AgentiveLinesInUse"
              },
              "X-Agentive-Lines-Mode": {
                "$ref": "#/components/headers/AgentiveLinesMode"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "502": {
            "description": "`call_state_unknown` when the dial engine was invoked and threw, so we cannot say whether a call went out. Check with GET /calls/{unique_id} before retrying. `upstream_error` for a non specific refusal, including a trial block or a missing caller ID reported on the verification path.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "503": {
            "description": "`service_unavailable` with `retry_after_sec` 30 (and `Retry-After: 30`) when the account's calling-hours setting could not be read, so the calling window could not be checked. Nothing was placed. Retry with the same key.",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      },
      "get": {
        "operationId": "listCalls",
        "tags": [
          "Calls"
        ],
        "summary": "List calls",
        "description": "Lists the account's calls, newest first, in the public call shape.\nStrictly scoped to the key's account. Only the public call fields are\nreturned. An account with the AI voice agent and no voice broadcast\nlists only its AI calls; any other account, including one with neither\nproduct, lists all its calls.\n\nAn unknown value for `direction` or `status` is refused with 400 rather\nthan dropped: silently ignoring a typo used to return an unfiltered page\nthat looked like an answer. An empty value still means no filter.\n\nThe `status` filter expands to the stored states behind that word, so\n`in_progress` also matches answered rows and `ringing` also matches early\nrows. `reference_id` is filled in from the run's contact for rows that\nbelong to a campaign.",
        "parameters": [
          {
            "name": "page",
            "in": "query",
            "required": false,
            "description": "1 based page number.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            }
          },
          {
            "name": "per_page",
            "in": "query",
            "required": false,
            "description": "Rows per page.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 50
            }
          },
          {
            "name": "direction",
            "in": "query",
            "required": false,
            "description": "Filter by call direction. Any other non empty value is 400.",
            "schema": {
              "type": "string",
              "enum": [
                "inbound",
                "outbound"
              ]
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "description": "Filter by public status. Any other non empty value is 400.",
            "schema": {
              "type": "string",
              "enum": [
                "queued",
                "ringing",
                "in_progress",
                "completed",
                "failed",
                "missed"
              ]
            }
          },
          {
            "name": "from",
            "in": "query",
            "required": false,
            "description": "ISO date or timestamp. Matches created_at greater than or equal to it.",
            "schema": {
              "type": "string"
            },
            "example": "2026-09-01"
          },
          {
            "name": "to",
            "in": "query",
            "required": false,
            "description": "ISO date or timestamp. Matches created_at strictly less than it.",
            "schema": {
              "type": "string"
            },
            "example": "2026-09-07"
          },
          {
            "name": "number",
            "in": "query",
            "required": false,
            "description": "Substring match against either the from number or the to number, after spaces, plus signs, hyphens and brackets are stripped.",
            "schema": {
              "type": "string"
            },
            "example": "9876543210"
          }
        ],
        "responses": {
          "200": {
            "description": "A page of calls.",
            "headers": {
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CallListEnvelope"
                }
              }
            }
          },
          "400": {
            "description": "`validation_error` when `direction` is present but not inbound or outbound, or `status` is present but not one of the six public statuses.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "`account_inactive` when the account is not active. `api_disabled` when the account does not have the master API entitlement. This route calls no product gate, so `feature_disabled` never appears on it.",
            "headers": {
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "description": "`service_unavailable` when the listing could not be read.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/campaigns/{id}/trigger": {
      "post": {
        "operationId": "triggerCampaign",
        "tags": [
          "Campaigns"
        ],
        "summary": "Trigger a saved campaign for one number",
        "description": "Fires an existing API campaign for one number. The campaign's saved\nconfiguration is cloned into a one shot run: the audio clips, the\ngoodbye clip, the digit map, the key wait and maximum call length, the\ncaller ID or the rotation pool, the press 1 action and its connect\ntarget, machine answer handling, the no answer timeout and the retry\ncount.\n\nNone of it can be overridden from the request. `number`,\n`reference_id`, and on an AI voice agent campaign `variables` and\n`metadata`, are read. A broadcast campaign refuses both with 400:\n`variables` with \"This campaign type does not take variables.\" and\n`metadata` with \"This campaign type does not take metadata.\"\n\nAn AI voice agent campaign runs with its saved agent, calling number or\npool, press 1 action and connect target, allowed team members, forward\nnumbers, pop up and no answer timeouts, machine answer handling,\nWhatsApp template and webhook. It passes two gates in order: the\naccount's total lines, then the narrower AI lane, where runs still\nringing count against the AI call limit. The run is stamped as placed\nthrough the API with its `reference_id`. A press 1 campaign reserves 2\nlines, an audio or AI campaign 1.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The numeric campaign id. The campaign must belong to your account and be an API campaign.",
            "schema": {
              "type": "integer",
              "minimum": 1
            }
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TriggerCampaignRequest"
              },
              "examples": {
                "basic": {
                  "summary": "Trigger for one number",
                  "value": {
                    "number": "9876543210",
                    "reference_id": "lead-118"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Queued. The response carries `run_<id>` as `unique_id`, the id every webhook event for the call carries. An AI voice agent campaign also answers `campaign_id`, `agent_id`, `call_status` and `metadata`.",
            "headers": {
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "idempotency-replayed": {
                "$ref": "#/components/headers/IdempotencyReplayed"
              },
              "Idempotent-Replayed": {
                "$ref": "#/components/headers/IdempotencyReplayed"
              },
              "X-Agentive-Lines": {
                "$ref": "#/components/headers/AgentiveLines"
              },
              "X-Agentive-Lines-In-Use": {
                "$ref": "#/components/headers/AgentiveLinesInUse"
              },
              "X-Agentive-Lines-Mode": {
                "$ref": "#/components/headers/AgentiveLinesMode"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RunQueuedResponse"
                }
              }
            }
          },
          "400": {
            "description": "`invalid_number` when the number is not a valid Indian mobile.\n`validation_error` for an AI campaign whose agent is gone, not active\nor only takes incoming calls, a campaign that is paused or stopped, a\nverification code campaign (which must be sent through the\nverification endpoints), any other campaign type that cannot be\ntriggered for a single number, a broadcast campaign with no audio\nconfigured, or a rejection from the dial engine.\n`whatsapp_template_required` when the campaign has no saved template.\n`invalid_caller_id` when no number on the account is enabled for voice\nbroadcast, or when an AI voice agent campaign has no calling number\nand no pool. `validation_error` when `variables` or `metadata` break\na rule (the sentence names the key) or are sent to a broadcast\ncampaign.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/InsufficientBalance"
          },
          "403": {
            "description": "`feature_disabled` when the account lacks the product for this campaign type, including an AI lane refusal that is not a concurrency limit. `whatsapp_locked` when the campaign sends a message after the call and the account does not have that access. `trial_restricted` on a trial calling an unverified number. `account_inactive` when the account's calling is on hold. Also `api_disabled`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "description": "`not_found` when there is no such campaign for this account, or it is not an API campaign.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "409": {
            "description": "`calling_hours` outside 09:00 to 21:00 IST. `whatsapp_not_connected` when the account's messaging is not connected. `idempotency_in_progress` when a request with this key is still running. `ai_concurrency_limit` when every AI call channel is busy on an AI voice agent campaign, `Retry-After` 5.",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "422": {
            "$ref": "#/components/responses/IdempotencyConflict"
          },
          "429": {
            "description": "`concurrency_limit` when every purchased line is in use. `number_flood` for the per number caps (3 in 10 minutes, 10 in an IST day), with `Retry-After` and `retry_after_sec` up to 600 seconds, or the seconds until midnight IST for the daily cap. `rate_limited` for a request rate bucket.",
            "headers": {
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfter"
              },
              "X-Agentive-Lines": {
                "$ref": "#/components/headers/AgentiveLines"
              },
              "X-Agentive-Lines-In-Use": {
                "$ref": "#/components/headers/AgentiveLinesInUse"
              },
              "X-Agentive-Lines-Mode": {
                "$ref": "#/components/headers/AgentiveLinesMode"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "502": {
            "description": "`call_state_unknown` when the engine was invoked and threw, or reported a failed trigger. `upstream_error` for any other engine refusal. A balance or account hold found when the run starts is reported as 402 or 403 instead, and the draft run is removed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "503": {
            "description": "`service_unavailable` with `retry_after_sec` 30 (and `Retry-After: 30`) when the account's calling-hours setting could not be read, so the calling window could not be checked. Nothing was placed. Retry with the same key.",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/ai/calls": {
      "post": {
        "operationId": "placeAiCall",
        "tags": [
          "AI voice agent"
        ],
        "summary": "Have an AI voice agent call one number",
        "description": "Queues one outbound call from an AI voice agent. The agent must be\nactive and able to place calls. `variables` fill the `{{key}}`\nplaceholders in the agent's greeting and prompt for this call only, and\n`metadata` is stored with the call and echoed on `GET /calls/{unique_id}`\nand every webhook event, never shown to the agent.\n\nGuards, in order: the product gate, `agent_id`, `to_number`,\n`variables` and `metadata`, line admission, the AI lane (calls still\nringing count), `from_number`, the calling window (refused if it cannot\nbe checked), the trial rule, the 10 minute per number cap, the balance\nfloor (one minute of the agent's per minute price, and at least ₹5),\nthe calling pool's daily limit when a pool number is picked, then the\nper number reservation (10 minute and daily caps). A `variables` or\n`metadata` rule broken is a 400 naming the key, with `field`; nothing\nis silently dropped.\n\nThe response's `unique_id` is the dial id, and it stays the call's\n`unique_id` on every read and every webhook event, `call.analysed`\nincluded, and `leg_id` carries the same dial id. After an answered\ncall ends the call record has its own `call_id`.",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AiCallRequest"
              },
              "examples": {
                "basic": {
                  "summary": "A renewal reminder in Hinglish",
                  "value": {
                    "agent_id": 318,
                    "to_number": "9876543210",
                    "variables": {
                      "naam": "Neha",
                      "amount": "₹2,499",
                      "plan": "Gold"
                    },
                    "metadata": {
                      "crm_lead_id": "L-20931"
                    },
                    "reference_id": "lead-5567"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Queued.",
            "headers": {
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "idempotency-replayed": {
                "$ref": "#/components/headers/IdempotencyReplayed"
              },
              "Idempotent-Replayed": {
                "$ref": "#/components/headers/IdempotencyReplayed"
              },
              "X-Agentive-Lines": {
                "$ref": "#/components/headers/AgentiveLines"
              },
              "X-Agentive-Lines-In-Use": {
                "$ref": "#/components/headers/AgentiveLinesInUse"
              },
              "X-Agentive-Lines-Mode": {
                "$ref": "#/components/headers/AgentiveLinesMode"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AiCallQueuedResponse"
                }
              }
            }
          },
          "400": {
            "description": "`validation_error` for a missing or invalid `agent_id`, an invalid\n`to_number`, an agent that is not active or only takes inbound calls, a\nbroken `variables` or `metadata` rule, an `Idempotency-Key` longer than\n128 characters, or an account with no number to call from.\n`invalid_caller_id` when `from_number` is not the account's or is not\nenabled for AI voice agents.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/InsufficientBalance"
          },
          "403": {
            "description": "`feature_disabled` when the AI voice agent, or outgoing AI calls, is not active on the account. `trial_restricted` on a trial calling an unverified number. Also `account_inactive` and `api_disabled`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "description": "`not_found`, \"Agent not found.\", when the agent is not on this account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "409": {
            "description": "`calling_hours` outside 09:00 to 21:00 IST. `ai_concurrency_limit` when every AI call channel is busy, `Retry-After` 5. `idempotency_in_progress` when a request with this key is still running.",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "422": {
            "$ref": "#/components/responses/IdempotencyConflict"
          },
          "429": {
            "description": "`concurrency_limit` when every line is in use. `number_flood` for the per number caps (3 in 10 minutes, 10 in an IST day), with `Retry-After` and `retry_after_sec` up to 600 seconds, or the seconds until midnight IST for the daily cap. `daily_cap` when the calling pool's numbers reached their daily limit, `Retry-After` until midnight IST. `rate_limited` for a request rate bucket.",
            "headers": {
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfter"
              },
              "X-Agentive-Lines": {
                "$ref": "#/components/headers/AgentiveLines"
              },
              "X-Agentive-Lines-In-Use": {
                "$ref": "#/components/headers/AgentiveLinesInUse"
              },
              "X-Agentive-Lines-Mode": {
                "$ref": "#/components/headers/AgentiveLinesMode"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "502": {
            "description": "`call_state_unknown` when the engine was invoked and we cannot say whether the call went out. Check with GET /calls/{unique_id} before placing it again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "503": {
            "description": "`service_unavailable`. Calling is briefly unavailable, or the account's calling-hours setting could not be read (then `retry_after_sec` is 30 and `Retry-After: 30`). Nothing was placed. Retry with the same key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/otp/send": {
      "post": {
        "operationId": "sendVerificationCode",
        "tags": [
          "Verification"
        ],
        "summary": "Send a voice verification code",
        "description": "Places a voice verification call that reads a code aloud to the\nrecipient. Verification calls are exempt from the 09:00 to 21:00 IST\nwindow and run 24 hours.\n\nThe code itself is never returned, never logged and never sent on a\nwebhook. The spoken clip is swept and deleted once the verification\nexpires or reaches a terminal state. `otp.sent` fires once the call is\nqueued.\n\nFrom the balance floor onward this route and `POST /calls` with `type`\n`otp` run one shared guard helper, so those checks, their order and\ntheir error bodies are the same on both, and the success body is byte\nfor byte identical. The steps before it differ: this route checks the\nproduct gate and then validates the number, while `POST /calls`\nvalidates the number first.",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SendVerificationRequest"
              },
              "examples": {
                "generated": {
                  "summary": "Platform generated code",
                  "value": {
                    "number": "9876543210",
                    "length": 6,
                    "ttl": 300
                  }
                },
                "supplied": {
                  "summary": "Your own code and read out",
                  "value": {
                    "number": "9876543210",
                    "code": "584213",
                    "message": "Your verification code is {code}. It is valid for {validity_time}.",
                    "reference_id": "signup-4471"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The verification call is queued.",
            "headers": {
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "idempotency-replayed": {
                "$ref": "#/components/headers/IdempotencyReplayed"
              },
              "Idempotent-Replayed": {
                "$ref": "#/components/headers/IdempotencyReplayed"
              },
              "X-Agentive-Lines": {
                "$ref": "#/components/headers/AgentiveLines"
              },
              "X-Agentive-Lines-In-Use": {
                "$ref": "#/components/headers/AgentiveLinesInUse"
              },
              "X-Agentive-Lines-Mode": {
                "$ref": "#/components/headers/AgentiveLinesMode"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VerificationQueuedResponse"
                }
              }
            }
          },
          "400": {
            "description": "`invalid_number` when the number is not a valid Indian mobile, including the engine's own rejection. `invalid_code` when a supplied code is not 4 to 8 digits.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/InsufficientBalance"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "$ref": "#/components/responses/IdempotencyInProgress"
          },
          "422": {
            "$ref": "#/components/responses/IdempotencyConflict"
          },
          "429": {
            "description": "`number_locked` after too many wrong codes for this number. `number_flood` for the 30 second spacing, the 10 per day cap, the 3 per 10 minutes cap, or the engine's own backstop. `concurrency_limit` when every purchased line is in use. `rate_limited` for a request rate bucket.",
            "headers": {
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfter"
              },
              "X-Agentive-Lines": {
                "$ref": "#/components/headers/AgentiveLines"
              },
              "X-Agentive-Lines-In-Use": {
                "$ref": "#/components/headers/AgentiveLinesInUse"
              },
              "X-Agentive-Lines-Mode": {
                "$ref": "#/components/headers/AgentiveLinesMode"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "502": {
            "description": "`call_state_unknown` when the engine was invoked and threw. `upstream_error` for any other engine refusal, including a trial block or no usable caller ID on this path.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/otp/verify": {
      "post": {
        "operationId": "verifyCode",
        "tags": [
          "Verification"
        ],
        "summary": "Check a code the recipient read back",
        "description": "Checks a code against a verification request, or against the number.\nSend `request_id` or `number`, and always `code`.\n\nEvery failure body carries `verified: false` beside `attempts_left` and\n`verification_status`. The number lock is evaluated before the engine,\nso a locked number burns no attempt. The number is taken from the body\nwhen supplied, otherwise from the verification row.\n\nA correct code fires `otp.verified`. A wrong code fires `otp.failed`\nwith `attempts_left` and `max_attempts`, and the attempt that exhausts\nthe allowance additionally fires `otp.max_attempts` once. An attempt\nagainst an already locked verification fires nothing, so an integrator\nalerting on `otp.max_attempts` gets exactly one signal per verification.\nThe code is never present in any webhook payload.\n\nThis route has its own budget of 60 checks per minute per account,\nseparate from the request bucket.",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/VerifyCodeRequest"
              },
              "examples": {
                "byRequestId": {
                  "summary": "By request id",
                  "value": {
                    "request_id": "otp_9f2c41a8",
                    "code": "584213"
                  }
                },
                "byNumber": {
                  "summary": "By number",
                  "value": {
                    "number": "9876543210",
                    "code": "584213"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The code matched.",
            "headers": {
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "idempotency-replayed": {
                "$ref": "#/components/headers/IdempotencyReplayed"
              },
              "Idempotent-Replayed": {
                "$ref": "#/components/headers/IdempotencyReplayed"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VerifiedResponse"
                }
              }
            }
          },
          "400": {
            "description": "`validation_error` when there is no code, or neither `request_id` nor\n`number`. `max_attempts` for an attempt against a verification that was\nalready locked (`verification_status` `locked`). `code_expired` when the\nvalidity window has passed (`expired`). `code_used` when the code was\nalready verified (`verified`). `verification_not_found` when no\nverification matches for this account (`failed`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VerificationErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "`incorrect_code` for a wrong code while attempts remain, with\n`attempts_left` and `verification_status` `pending`. `max_attempts` for\nthe wrong code that spent the last allowance, with `attempts_left` 0 and\n`verification_status` `locked`.\n\nAlso `missing_credentials` and `invalid_credentials` from\nauthentication, which carry no verification fields.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VerificationErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "`account_inactive` when the account is not active. `api_disabled` when the account does not have the master API entitlement. This route calls no product gate, so `feature_disabled` never appears on it.",
            "headers": {
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "409": {
            "$ref": "#/components/responses/IdempotencyInProgress"
          },
          "422": {
            "$ref": "#/components/responses/IdempotencyConflict"
          },
          "429": {
            "description": "`number_locked` after too many wrong codes for this number, carrying `verified: false` and `verification_status` `locked`. `rate_limited` from the dedicated budget of 60 verification checks per minute, which additionally carries `verification_status` `pending`. `rate_limited` from a plain request rate bucket carries the standard error envelope only, with no verification fields.",
            "headers": {
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VerificationErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/calls/{unique_id}": {
      "get": {
        "operationId": "getCallStatus",
        "tags": [
          "Calls"
        ],
        "summary": "Read one call, run or verification request",
        "description": "Reads the state of whatever the id names. Resolution order: a\n`run_<campaign_id>` pattern first, then a verification request id, then\na call uuid, then a dial leg uuid, then the in flight map for an AI call\nwhose record has not landed yet.\n\nAn AI call's record can be keyed on an internal session id rather than\non the dial id, so either id resolves here. For an AI call placed\nthrough the API, `unique_id` (top level and inside `call`) is always\nthe id the API returned: the dial id, or `run_<id>` for a trigger.\n`call_id` is the internal record id and `leg_id` the dial leg. The\n`call` object adds `agent_id`, `via_api`, `metadata`, `charge_inr` and\nthe two availability flags; `via_api` and `metadata` are repeated at\nthe top level. An account with the AI voice agent and no voice\nbroadcast sees only its AI calls here; any other account, including one\nwith neither product, sees all its calls.\n\nA call from `POST /ai/calls` that was never answered (missed, busy or\nfailed) keeps answering its final status from our records after it is\nover, with `duration_sec` 0 and `charge_inr` 0. It does not turn into 404.\n\nOn a run, `call_status` keeps the older five word vocabulary (queued,\nringing, answered, completed, failed) while the nested `call` object\nuses the closed public status set. A run is reported completed only when\nthe call genuinely answered, and an unanswered terminal run reports\nfailed with `duration_sec` 0. `end_reason` always passes through the\npublished vocabulary, so a raw hangup cause can never reach you.\n`reference_id` is resolved from the run's contact when the call record\ndoes not carry one.",
        "parameters": [
          {
            "$ref": "#/components/parameters/UniqueIdPath"
          }
        ],
        "responses": {
          "200": {
            "description": "The state of the run, the verification request, or the call.",
            "headers": {
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/RunStatusResponse"
                    },
                    {
                      "$ref": "#/components/schemas/VerificationStatusResponse"
                    },
                    {
                      "$ref": "#/components/schemas/CallStatusResponse"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "`account_inactive` when the account is not active. `api_disabled` when the account does not have the master API entitlement. This route calls no product gate, so `feature_disabled` never appears on it.",
            "headers": {
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "description": "`not_found`. The run does not exist for this account (details \"Call not found.\"), or nothing at all matches the id (details \"Not found.\").",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/calls/{unique_id}/recording": {
      "get": {
        "operationId": "getRecording",
        "tags": [
          "Calls"
        ],
        "summary": "Fetch the audio for one call",
        "description": "The only route here that does not answer with the JSON envelope on\nsuccess. The API streams the audio itself, with HTTP Range support: 200\nfor the whole file, 206 for a range, 416 for a range outside the file.\nIt never redirects, so no client ever carries credentials to another\nhost. Prefer HTTP Basic for this route in any case.\n\nRecordings are OGG Opus for most calls and WAV where no compressed copy\nexists. The Content-Type follows the stored file.\n\nThe path id accepts three forms. `run_<campaign_id>` resolves to the\nlatest attempt's call for that campaign. Any other value must match\n`^[a-zA-Z0-9_-]{8,128}$` and is looked up first as the call uuid, then as\nthe dial leg uuid, newest first, always scoped to your account.\n\nThere is no query parameter of any kind. `reference_id` is null in the\nerror bodies on this route because a GET carries no body to read it from.\n\nThis route needs call recording on the account. An account with the AI\nvoice agent and no voice broadcast reads only its AI calls' recordings;\nany other account, including one with neither product, reads them all.\nA call with no recording yet answers 404; `recording.available` says\nwhen it is ready.",
        "parameters": [
          {
            "name": "unique_id",
            "in": "path",
            "required": true,
            "description": "A call uuid, a dial leg uuid, or run_<campaign_id>.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Range",
            "in": "header",
            "required": false,
            "description": "Standard byte range. A valid range answers 206; a range outside the file answers 416.",
            "schema": {
              "type": "string"
            },
            "example": "bytes=0-65535"
          }
        ],
        "responses": {
          "200": {
            "description": "The audio bytes.",
            "headers": {
              "Content-Type": {
                "description": "audio/ogg or audio/wav, chosen by the stored file extension.",
                "schema": {
                  "type": "string"
                }
              },
              "Accept-Ranges": {
                "description": "bytes",
                "schema": {
                  "type": "string"
                }
              },
              "Content-Length": {
                "description": "Size of the body in bytes.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "audio/ogg": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "audio/wav": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "206": {
            "description": "A byte range of the audio, when the request carried a valid Range.",
            "headers": {
              "Content-Range": {
                "description": "bytes <start>-<end>/<size>",
                "schema": {
                  "type": "string"
                }
              },
              "Accept-Ranges": {
                "description": "bytes",
                "schema": {
                  "type": "string"
                }
              },
              "Content-Length": {
                "description": "Size of this range in bytes.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "audio/ogg": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "audio/wav": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "`feature_disabled` when call recording is not enabled for the account. Also `account_inactive` and `api_disabled`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "404": {
            "description": "`not_found`, with the sentence \"No recording is available for this call.\"\nIt covers every miss: the id does not resolve to a call this key can\nread, the id fails the character whitelist, or no audio exists for\nthe call.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "416": {
            "description": "`validation_error`, \"The requested byte range cannot be served. Send one range inside the file, or no Range header.\" A malformed range, several ranges, or a range starting past the end of the file.",
            "headers": {
              "Content-Range": {
                "description": "bytes */<size>, when the size is known.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "502": {
            "description": "`upstream_error` when the audio could not be fetched right now. Retry.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/calls/{unique_id}/transcript": {
      "get": {
        "operationId": "getTranscript",
        "tags": [
          "Calls"
        ],
        "summary": "Read the conversation for one AI call",
        "description": "The conversation turn by turn. AI calls only, since the transcript\nledger is an AI product artefact.\n\nOnly speaker turns are published. The assistant role is renamed to\n`agent` and the user role to `caller`. System context and tool markers\nnever leave the platform. `turns` is an empty array when the stored\nhistory holds no publishable turns.\n\nThe ids in the response are taken from the resolved record, not from\nwhat you typed, so a caller who polled with either id can tell which\ncall answered. This route uses the bare read envelope, so there is no\n`reference_id` or `details` on success.",
        "parameters": [
          {
            "$ref": "#/components/parameters/ResolvableIdPath"
          }
        ],
        "responses": {
          "200": {
            "description": "The conversation.",
            "headers": {
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TranscriptResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "`not_found`, either \"Call not found.\" when the id does not resolve to a call on this account, or \"No transcript is available for this call.\" when it resolved but has no transcript.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/calls/{unique_id}/analysis": {
      "get": {
        "operationId": "getAnalysis",
        "tags": [
          "Calls"
        ],
        "summary": "Read the post call analysis for one AI call",
        "description": "Summary, sentiment, intent, outcome, structured extractions and tags,\ngenerated shortly after the call ends.\n\nThe column allow list is strict. No stack or model names, no latency, and\nno cost or margin column is ever published here. `extractions` and `tags`\nare stored as JSON text and returned parsed; a value that fails to parse\nfalls back to an empty object and an empty array rather than erroring.\n\nSame id resolution as the transcript route.",
        "parameters": [
          {
            "$ref": "#/components/parameters/ResolvableIdPath"
          }
        ],
        "responses": {
          "200": {
            "description": "The post call read.",
            "headers": {
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AnalysisResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "`not_found`, either \"Call not found.\" or \"No analysis is available for this call yet. It is generated shortly after the call ends.\"",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/account": {
      "get": {
        "operationId": "getAccount",
        "tags": [
          "Account"
        ],
        "summary": "Read the wallet balance and live line usage",
        "description": "This route does not use the single resource envelope: there is no\n`unique_id`, `reference_id` or `details` key.\n\n`lines_in_use` is counted exactly the way the call placing routes admit\nagainst it, so it is directly comparable with the\n`X-Agentive-Lines-In-Use` header. `lines_mode` tells you whether\nexceeding `lines` is actually refused, only logged, or not evaluated.\n\nA failure to read the balance or the line count is swallowed and reported\nas 0 or null rather than raising an error.",
        "responses": {
          "200": {
            "description": "The account's balance and lines.",
            "headers": {
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AccountResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "`feature_disabled` when the account's wallet surface is switched off. Also `account_inactive` and `api_disabled`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/campaigns": {
      "get": {
        "operationId": "listCampaigns",
        "tags": [
          "Campaigns"
        ],
        "summary": "List the saved campaigns this API can trigger",
        "description": "No pagination and no envelope id fields. The whole list comes back,\nnewest first.\n\nOnly API campaigns are listed, never the one call runs a trigger\ncreates, and every campaign carries one of the four public type words.\nAn AI voice agent campaign also carries its `agent_id`.\n\nThere is no product gate on this route, so a campaign whose product is\nswitched off is still listed and will be refused at trigger time. A\nverification campaign appears here as your account's verification\nconfiguration, but is delivered through the verification endpoints, not\nthrough the trigger route.",
        "responses": {
          "200": {
            "description": "The account's API campaigns.",
            "headers": {
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CampaignListResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "`account_inactive` when the account is not active. `api_disabled` when the account does not have the master API entitlement. This route calls no product gate, so `feature_disabled` never appears on it.",
            "headers": {
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "description": "`service_unavailable` when the listing could not be read.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/agents": {
      "get": {
        "operationId": "listAgents",
        "tags": [
          "AI voice agent"
        ],
        "summary": "List the account's AI voice agents",
        "description": "Every AI voice agent on the account, oldest first. Read only: agents\nare created and edited in the dashboard. `variables` lists the\n`{{key}}` placeholders the agent's greeting and prompt use, with\n`default_set` telling you whether the agent has a default for each.",
        "responses": {
          "200": {
            "description": "The agents.",
            "headers": {
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AgentListResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "description": "`service_unavailable` when the list could not be read.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/agents/{id}": {
      "get": {
        "operationId": "getAgent",
        "tags": [
          "AI voice agent"
        ],
        "summary": "Read one AI voice agent",
        "description": "One agent, in the same shape as the list.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The agent id.",
            "schema": {
              "type": "integer",
              "minimum": 1
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The agent.",
            "headers": {
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AgentResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "`not_found`, \"Agent not found.\"",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/numbers": {
      "get": {
        "operationId": "listNumbers",
        "tags": [
          "AI voice agent"
        ],
        "summary": "List the account's own numbers and their inbound agent",
        "description": "The account's own numbers, with the AI agent that answers each one.\nShared calling pool numbers are not listed. `bindable` is false when a\nnumber rings a team member, runs a call menu, is a shared line or is not\nenabled for AI, so an agent cannot be put on it through the API.",
        "responses": {
          "200": {
            "description": "The numbers.",
            "headers": {
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NumberListResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "description": "`service_unavailable` when the list could not be read.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/numbers/{number}": {
      "patch": {
        "operationId": "setNumberAgent",
        "tags": [
          "AI voice agent"
        ],
        "summary": "Put an AI agent on an inbound number, or take it off",
        "description": "Sets which AI voice agent answers calls to one of the account's own\nnumbers, or clears it with `null`. The agent must be active and able to\ntake calls. A number on a call menu, assigned to a team member, shared,\nor not enabled for AI voice agents is refused with a sentence saying\nwhich. Every change is recorded in the account's security log. The key\nis account wide, so whoever holds it can move numbers between agents.",
        "parameters": [
          {
            "name": "number",
            "in": "path",
            "required": true,
            "description": "One of the account's numbers, matched on its last ten digits.",
            "schema": {
              "type": "string"
            },
            "example": "01100000045"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/NumberBindingRequest"
              },
              "examples": {
                "bind": {
                  "summary": "Put agent 322 on the number",
                  "value": {
                    "inbound_agent_id": 322
                  }
                },
                "clear": {
                  "summary": "Take the agent off",
                  "value": {
                    "inbound_agent_id": null
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The number's new binding.",
            "headers": {
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NumberBindingResponse"
                }
              }
            }
          },
          "400": {
            "description": "`validation_error` for a missing or invalid `inbound_agent_id`, an agent that is not active or only places calls, or a number that is on a call menu, assigned to a team member, shared, or not enabled for AI voice agents.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "`not_found` when the number or the agent is not on this account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "description": "`service_unavailable` when the change could not be saved. Retry.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        }
      }
    }
  },
  "webhooks": {
    "call.initiated": {
      "post": {
        "operationId": "webhookCallInitiated",
        "summary": "A call has been queued with the dial engine",
        "description": "Sent when a call is accepted for dialling. `data.object` is a call, with status `queued`.",
        "parameters": [
          {
            "$ref": "#/components/parameters/WebhookSignature"
          },
          {
            "$ref": "#/components/parameters/WebhookSignatureV2"
          },
          {
            "$ref": "#/components/parameters/WebhookTimestamp"
          },
          {
            "$ref": "#/components/parameters/WebhookEvent"
          },
          {
            "$ref": "#/components/parameters/WebhookEventId"
          },
          {
            "$ref": "#/components/parameters/WebhookDeliveryId"
          },
          {
            "$ref": "#/components/parameters/WebhookAttempt"
          }
        ],
        "requestBody": {
          "$ref": "#/components/requestBodies/CallEvent"
        },
        "responses": {
          "2XX": {
            "$ref": "#/components/responses/WebhookAck"
          }
        }
      }
    },
    "call.ringing": {
      "post": {
        "operationId": "webhookCallRinging",
        "summary": "The recipient's phone is ringing",
        "description": "`data.object` is a call, with status `ringing`.",
        "parameters": [
          {
            "$ref": "#/components/parameters/WebhookSignature"
          },
          {
            "$ref": "#/components/parameters/WebhookSignatureV2"
          },
          {
            "$ref": "#/components/parameters/WebhookTimestamp"
          },
          {
            "$ref": "#/components/parameters/WebhookEvent"
          },
          {
            "$ref": "#/components/parameters/WebhookEventId"
          },
          {
            "$ref": "#/components/parameters/WebhookDeliveryId"
          },
          {
            "$ref": "#/components/parameters/WebhookAttempt"
          }
        ],
        "requestBody": {
          "$ref": "#/components/requestBodies/CallEvent"
        },
        "responses": {
          "2XX": {
            "$ref": "#/components/responses/WebhookAck"
          }
        }
      }
    },
    "call.answered": {
      "post": {
        "operationId": "webhookCallAnswered",
        "summary": "The recipient answered",
        "description": "`data.object` is a call, with status `in_progress`.",
        "parameters": [
          {
            "$ref": "#/components/parameters/WebhookSignature"
          },
          {
            "$ref": "#/components/parameters/WebhookSignatureV2"
          },
          {
            "$ref": "#/components/parameters/WebhookTimestamp"
          },
          {
            "$ref": "#/components/parameters/WebhookEvent"
          },
          {
            "$ref": "#/components/parameters/WebhookEventId"
          },
          {
            "$ref": "#/components/parameters/WebhookDeliveryId"
          },
          {
            "$ref": "#/components/parameters/WebhookAttempt"
          }
        ],
        "requestBody": {
          "$ref": "#/components/requestBodies/CallEvent"
        },
        "responses": {
          "2XX": {
            "$ref": "#/components/responses/WebhookAck"
          }
        }
      }
    },
    "call.completed": {
      "post": {
        "operationId": "webhookCallCompleted",
        "summary": "The call connected and has ended",
        "description": "`data.object` is a call, with status `completed` and `end_reason` `completed` or `voicemail`. A voicemail pickup is an answered call, so it arrives here rather than as a failure.",
        "parameters": [
          {
            "$ref": "#/components/parameters/WebhookSignature"
          },
          {
            "$ref": "#/components/parameters/WebhookSignatureV2"
          },
          {
            "$ref": "#/components/parameters/WebhookTimestamp"
          },
          {
            "$ref": "#/components/parameters/WebhookEvent"
          },
          {
            "$ref": "#/components/parameters/WebhookEventId"
          },
          {
            "$ref": "#/components/parameters/WebhookDeliveryId"
          },
          {
            "$ref": "#/components/parameters/WebhookAttempt"
          }
        ],
        "requestBody": {
          "$ref": "#/components/requestBodies/CallEvent"
        },
        "responses": {
          "2XX": {
            "$ref": "#/components/responses/WebhookAck"
          }
        }
      }
    },
    "call.no_answer": {
      "post": {
        "operationId": "webhookCallNoAnswer",
        "summary": "The call rang out",
        "description": "`data.object` is a call, with status `missed` and `end_reason` `no_answer`.",
        "parameters": [
          {
            "$ref": "#/components/parameters/WebhookSignature"
          },
          {
            "$ref": "#/components/parameters/WebhookSignatureV2"
          },
          {
            "$ref": "#/components/parameters/WebhookTimestamp"
          },
          {
            "$ref": "#/components/parameters/WebhookEvent"
          },
          {
            "$ref": "#/components/parameters/WebhookEventId"
          },
          {
            "$ref": "#/components/parameters/WebhookDeliveryId"
          },
          {
            "$ref": "#/components/parameters/WebhookAttempt"
          }
        ],
        "requestBody": {
          "$ref": "#/components/requestBodies/CallEvent"
        },
        "responses": {
          "2XX": {
            "$ref": "#/components/responses/WebhookAck"
          }
        }
      }
    },
    "call.busy": {
      "post": {
        "operationId": "webhookCallBusy",
        "summary": "The line was busy or the call was declined",
        "description": "`data.object` is a call, with status `missed` and `end_reason` `busy` or `rejected`.",
        "parameters": [
          {
            "$ref": "#/components/parameters/WebhookSignature"
          },
          {
            "$ref": "#/components/parameters/WebhookSignatureV2"
          },
          {
            "$ref": "#/components/parameters/WebhookTimestamp"
          },
          {
            "$ref": "#/components/parameters/WebhookEvent"
          },
          {
            "$ref": "#/components/parameters/WebhookEventId"
          },
          {
            "$ref": "#/components/parameters/WebhookDeliveryId"
          },
          {
            "$ref": "#/components/parameters/WebhookAttempt"
          }
        ],
        "requestBody": {
          "$ref": "#/components/requestBodies/CallEvent"
        },
        "responses": {
          "2XX": {
            "$ref": "#/components/responses/WebhookAck"
          }
        }
      }
    },
    "call.failed": {
      "post": {
        "operationId": "webhookCallFailed",
        "summary": "The call did not connect",
        "description": "`data.object` is a call, with status `failed` and `end_reason` `canceled` or `failed`. A raw hangup cause never appears; every value passes through the published vocabulary first.",
        "parameters": [
          {
            "$ref": "#/components/parameters/WebhookSignature"
          },
          {
            "$ref": "#/components/parameters/WebhookSignatureV2"
          },
          {
            "$ref": "#/components/parameters/WebhookTimestamp"
          },
          {
            "$ref": "#/components/parameters/WebhookEvent"
          },
          {
            "$ref": "#/components/parameters/WebhookEventId"
          },
          {
            "$ref": "#/components/parameters/WebhookDeliveryId"
          },
          {
            "$ref": "#/components/parameters/WebhookAttempt"
          }
        ],
        "requestBody": {
          "$ref": "#/components/requestBodies/CallEvent"
        },
        "responses": {
          "2XX": {
            "$ref": "#/components/responses/WebhookAck"
          }
        }
      }
    },
    "call.analysed": {
      "post": {
        "operationId": "webhookCallAnalysed",
        "summary": "The post call analysis of an AI call is ready",
        "description": "Accounts with the AI voice agent only; offered in the event list only\nto them. Sent once per answered AI call on the account (incoming calls\nan agent answers included; `via_api` marks the ones placed through the\nAPI), after the post call analysis:\nthe summary, sentiment, intent, outcome, interest, tags, the fields the\nagent collected, the transcript as `{ role, text }` turns with role\n`agent` or `customer`, whether the recording is ready, the talk time and\nthe charge in rupees, beside the call's ids, `reference_id` and\n`metadata`. Never carries a cost to the platform or any model name.",
        "parameters": [
          {
            "$ref": "#/components/parameters/WebhookSignature"
          },
          {
            "$ref": "#/components/parameters/WebhookSignatureV2"
          },
          {
            "$ref": "#/components/parameters/WebhookTimestamp"
          },
          {
            "$ref": "#/components/parameters/WebhookEvent"
          },
          {
            "$ref": "#/components/parameters/WebhookEventId"
          },
          {
            "$ref": "#/components/parameters/WebhookDeliveryId"
          },
          {
            "$ref": "#/components/parameters/WebhookAttempt"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookCallAnalysedEvent"
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "$ref": "#/components/responses/WebhookAck"
          }
        }
      }
    },
    "recording.available": {
      "post": {
        "operationId": "webhookRecordingAvailable",
        "summary": "The recording for a call is ready to fetch",
        "description": "`data.object` is a recording. `recording_url` points at the authenticated recording endpoint, never at a storage path, so fetching it still needs your API credentials.",
        "parameters": [
          {
            "$ref": "#/components/parameters/WebhookSignature"
          },
          {
            "$ref": "#/components/parameters/WebhookSignatureV2"
          },
          {
            "$ref": "#/components/parameters/WebhookTimestamp"
          },
          {
            "$ref": "#/components/parameters/WebhookEvent"
          },
          {
            "$ref": "#/components/parameters/WebhookEventId"
          },
          {
            "$ref": "#/components/parameters/WebhookDeliveryId"
          },
          {
            "$ref": "#/components/parameters/WebhookAttempt"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookRecordingEvent"
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "$ref": "#/components/responses/WebhookAck"
          }
        }
      }
    },
    "otp.sent": {
      "post": {
        "operationId": "webhookOtpSent",
        "summary": "A verification call has been queued",
        "description": "`data.object` is a verification, carrying the expiry and the attempt budget. The code is never included.",
        "parameters": [
          {
            "$ref": "#/components/parameters/WebhookSignature"
          },
          {
            "$ref": "#/components/parameters/WebhookSignatureV2"
          },
          {
            "$ref": "#/components/parameters/WebhookTimestamp"
          },
          {
            "$ref": "#/components/parameters/WebhookEvent"
          },
          {
            "$ref": "#/components/parameters/WebhookEventId"
          },
          {
            "$ref": "#/components/parameters/WebhookDeliveryId"
          },
          {
            "$ref": "#/components/parameters/WebhookAttempt"
          }
        ],
        "requestBody": {
          "$ref": "#/components/requestBodies/VerificationEvent"
        },
        "responses": {
          "2XX": {
            "$ref": "#/components/responses/WebhookAck"
          }
        }
      }
    },
    "otp.verified": {
      "post": {
        "operationId": "webhookOtpVerified",
        "summary": "A code was confirmed correct",
        "description": "`data.object` is a verification with `verified` true and `verification_status` `verified`.",
        "parameters": [
          {
            "$ref": "#/components/parameters/WebhookSignature"
          },
          {
            "$ref": "#/components/parameters/WebhookSignatureV2"
          },
          {
            "$ref": "#/components/parameters/WebhookTimestamp"
          },
          {
            "$ref": "#/components/parameters/WebhookEvent"
          },
          {
            "$ref": "#/components/parameters/WebhookEventId"
          },
          {
            "$ref": "#/components/parameters/WebhookDeliveryId"
          },
          {
            "$ref": "#/components/parameters/WebhookAttempt"
          }
        ],
        "requestBody": {
          "$ref": "#/components/requestBodies/VerificationEvent"
        },
        "responses": {
          "2XX": {
            "$ref": "#/components/responses/WebhookAck"
          }
        }
      }
    },
    "otp.failed": {
      "post": {
        "operationId": "webhookOtpFailed",
        "summary": "A wrong code was submitted",
        "description": "`data.object` is a verification carrying `attempts_left` and `max_attempts`. Fires on every wrong code while the verification is open.",
        "parameters": [
          {
            "$ref": "#/components/parameters/WebhookSignature"
          },
          {
            "$ref": "#/components/parameters/WebhookSignatureV2"
          },
          {
            "$ref": "#/components/parameters/WebhookTimestamp"
          },
          {
            "$ref": "#/components/parameters/WebhookEvent"
          },
          {
            "$ref": "#/components/parameters/WebhookEventId"
          },
          {
            "$ref": "#/components/parameters/WebhookDeliveryId"
          },
          {
            "$ref": "#/components/parameters/WebhookAttempt"
          }
        ],
        "requestBody": {
          "$ref": "#/components/requestBodies/VerificationEvent"
        },
        "responses": {
          "2XX": {
            "$ref": "#/components/responses/WebhookAck"
          }
        }
      }
    },
    "otp.max_attempts": {
      "post": {
        "operationId": "webhookOtpMaxAttempts",
        "summary": "A verification just locked",
        "description": "Fires once per verification, on the attempt that spends the last\nallowance, alongside `otp.failed`. A later attempt against an already\nlocked verification fires nothing, so alerting on this event gives\nexactly one signal per verification.",
        "parameters": [
          {
            "$ref": "#/components/parameters/WebhookSignature"
          },
          {
            "$ref": "#/components/parameters/WebhookSignatureV2"
          },
          {
            "$ref": "#/components/parameters/WebhookTimestamp"
          },
          {
            "$ref": "#/components/parameters/WebhookEvent"
          },
          {
            "$ref": "#/components/parameters/WebhookEventId"
          },
          {
            "$ref": "#/components/parameters/WebhookDeliveryId"
          },
          {
            "$ref": "#/components/parameters/WebhookAttempt"
          }
        ],
        "requestBody": {
          "$ref": "#/components/requestBodies/VerificationEvent"
        },
        "responses": {
          "2XX": {
            "$ref": "#/components/responses/WebhookAck"
          }
        }
      }
    },
    "campaign.attempt_end": {
      "post": {
        "operationId": "webhookCampaignAttemptEnd",
        "summary": "An attempt on a campaign contact is final",
        "description": "A campaign webhook, set per campaign in the dashboard rather than on an\nendpoint subscription. One event per contact, when the attempt on that\ncontact is final; never on a retry. A run started through\n`POST /campaigns/{id}/trigger` does not fire it: every API trigger runs\nas its own child of the campaign, and those children do not inherit the\ncampaign's webhook URL. The legacy `event`\nfield keeps the value `attempt_end`. Signed with the account signing\nsecret, delivered on the campaign lane rule described on\n`X-Agentive-Signature`.",
        "parameters": [
          {
            "$ref": "#/components/parameters/CampaignWebhookSignature"
          },
          {
            "$ref": "#/components/parameters/CampaignWebhookSignatureV2"
          },
          {
            "$ref": "#/components/parameters/CampaignWebhookTimestamp"
          },
          {
            "$ref": "#/components/parameters/WebhookEvent"
          },
          {
            "$ref": "#/components/parameters/CampaignWebhookEventId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookCampaignAttemptEndEvent"
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "$ref": "#/components/responses/CampaignWebhookAck"
          }
        }
      }
    },
    "campaign.disposition_set": {
      "post": {
        "operationId": "webhookCampaignDispositionSet",
        "summary": "A team member set a disposition on a campaign call",
        "description": "A campaign webhook, set per campaign in the dashboard. Carries the same\nenvelope, `campaign` and `contact` objects as `campaign.attempt_end`\n(`contact` is `{ \"id\": null }` when the attempt has no contact row), a\n`call` object and `set_at`. The legacy `event` field keeps the value\n`disposition_set`. Signed with the account signing secret, delivered on\nthe campaign lane rule described on `X-Agentive-Signature`.",
        "parameters": [
          {
            "$ref": "#/components/parameters/CampaignWebhookSignature"
          },
          {
            "$ref": "#/components/parameters/CampaignWebhookSignatureV2"
          },
          {
            "$ref": "#/components/parameters/CampaignWebhookTimestamp"
          },
          {
            "$ref": "#/components/parameters/WebhookEvent"
          },
          {
            "$ref": "#/components/parameters/CampaignWebhookEventId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookCampaignDispositionSetEvent"
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "$ref": "#/components/responses/CampaignWebhookAck"
          }
        }
      }
    },
    "ivr.keypress": {
      "post": {
        "operationId": "webhookIvrKeypress",
        "summary": "A caller pressed a key on a call menu",
        "description": "The call menu key press webhook, set per call menu under Call menu >\nAdvanced > Key press webhook. One event per key a caller\npresses, sent off the call path so it never delays the call. `type` and\n`event` are both `ivr.keypress`; there is no `org_id`. Signed with the\nsame account signing secret as campaign webhooks and delivered on the\nsame rule, described on `X-Agentive-Signature`. The Test button on the\ncall menu page sends the same body with `test` true, an `id` prefixed\n`evt_test_`, an all-zero `call_uuid`, the caller `+919876543210`,\n`to_number` the account's first number in the stored national form, 0\nand the ten digits (empty when the account holds none yet; a live event\ncarries the dialled number as the carrier presented it) and key `1`\nlabelled `Sales`.",
        "parameters": [
          {
            "$ref": "#/components/parameters/CampaignWebhookSignature"
          },
          {
            "$ref": "#/components/parameters/CampaignWebhookSignatureV2"
          },
          {
            "$ref": "#/components/parameters/CampaignWebhookTimestamp"
          },
          {
            "$ref": "#/components/parameters/WebhookEvent"
          },
          {
            "$ref": "#/components/parameters/CampaignWebhookEventId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookIvrKeypressEvent"
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "$ref": "#/components/responses/CampaignWebhookAck"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ApiKey": {
        "type": "apiKey",
        "in": "header",
        "name": "x-api-key",
        "description": "The publishable key, `pk_live_` plus 48 hex characters. Required together with x-api-secret on every request."
      },
      "ApiSecret": {
        "type": "apiKey",
        "in": "header",
        "name": "x-api-secret",
        "description": "The secret, `sk_live_` plus 64 hex characters. Required together with x-api-key on every request. Shown once at generation or rotation and never revealed again."
      }
    },
    "parameters": {
      "IdempotencyKey": {
        "name": "Idempotency-Key",
        "in": "header",
        "required": false,
        "description": "Optional. 1 to 128 printable ASCII characters; a longer key is refused\nwith 400 `validation_error`. Within 24 hours the\nsame account plus key replays the original status and body. The same key\nwith a different request body is 422. See the Idempotency section of the\nintroduction for exactly which outcomes are stored.",
        "schema": {
          "type": "string",
          "maxLength": 128
        }
      },
      "UniqueIdPath": {
        "name": "unique_id",
        "in": "path",
        "required": true,
        "description": "`run_<campaign_id>` from a trigger response, a verification request id, a\ncall uuid, or a dial leg uuid. The 8 to 128 character whitelist of\nletters, digits, underscore and hyphen is applied when the id is looked\nup as a call uuid or a dial leg uuid. A `run_<campaign_id>` handle and a\nverification request id are matched before that check and are not\nsubject to it.",
        "schema": {
          "type": "string",
          "maxLength": 128
        }
      },
      "ResolvableIdPath": {
        "name": "unique_id",
        "in": "path",
        "required": true,
        "description": "`run_<campaign_id>` (resolved to the latest attempt of that campaign), a call uuid, or a dial leg uuid.",
        "schema": {
          "type": "string"
        }
      },
      "WebhookSignature": {
        "name": "X-Agentive-Signature",
        "in": "header",
        "required": true,
        "description": "The older signature, kept for receivers built before version 2:\n`sha256=<hex HMAC-SHA256(endpoint secret, raw body)>`. Version 1 signs the\nbody alone, so it is identical on every attempt and cannot authenticate\na timestamp. Do not accept it on its own; verify\n`X-Agentive-Signature-V2`.\n\nDelivery is a fast path of tries with short backoff, then a durable tail\nthat keeps trying for about 24 hours. Nine attempts in all. Any status\noutside 2xx, and any network error, is a retry. Redirects are not\nfollowed, so a 3xx cannot bounce the signed body elsewhere.",
        "schema": {
          "type": "string"
        },
        "example": "sha256=6f1b2c8d4a0e..."
      },
      "WebhookSignatureV2": {
        "name": "X-Agentive-Signature-V2",
        "in": "header",
        "required": true,
        "description": "`t=<unix seconds>,v2=<hex HMAC-SHA256(endpoint secret, \"t.body\")>`,\nrecomputed per attempt with the same `t` as `X-Agentive-Timestamp`. The\nsignature to verify, and the only one: reject a `t` more than five\nminutes from your clock, compare `v2` in constant time after checking\nbyte lengths, and refuse a delivery without it. The timestamp is inside\nthe signed material, so the window stops a captured delivery being sent\nagain later.",
        "schema": {
          "type": "string"
        },
        "example": "t=1789234567,v2=9c41ab2f7e05..."
      },
      "WebhookTimestamp": {
        "name": "X-Agentive-Timestamp",
        "in": "header",
        "required": true,
        "description": "Unix seconds at the moment this attempt was sent.",
        "schema": {
          "type": "string"
        },
        "example": "1789234567"
      },
      "WebhookEvent": {
        "name": "X-Agentive-Event",
        "in": "header",
        "required": true,
        "description": "The event name, for cheap routing before parsing the body.",
        "schema": {
          "type": "string"
        },
        "example": "call.completed"
      },
      "WebhookEventId": {
        "name": "X-Agentive-Event-Id",
        "in": "header",
        "required": true,
        "description": "The event id, shared across every endpoint that receives this event. Dedupe on this rather than acting on all nine attempts.",
        "schema": {
          "type": "string"
        },
        "example": "evt_3f9a1c74d0b64e0f"
      },
      "WebhookDeliveryId": {
        "name": "X-Agentive-Delivery-Id",
        "in": "header",
        "required": true,
        "description": "This event to this endpoint. Stable across all attempts.",
        "schema": {
          "type": "string"
        }
      },
      "WebhookAttempt": {
        "name": "X-Agentive-Attempt",
        "in": "header",
        "required": true,
        "description": "The 1 based attempt number, continuing into the durable tail.",
        "schema": {
          "type": "string"
        },
        "example": "1"
      },
      "CampaignWebhookSignature": {
        "name": "X-Agentive-Signature",
        "in": "header",
        "required": true,
        "description": "The older signature: `sha256=<hex HMAC-SHA256(account signing secret, raw\nbody)>`. Version 1 signs the body alone, so it cannot prove freshness; do\nnot accept it on its own, verify version 2. The secret is\nthe one account level webhook signing secret, shown on the campaign page\nand on the call menu page, and shared by campaign webhooks and the call\nmenu key press webhook. Absent only when the account has no signing\nsecret, which happens only on a database fault; `X-Agentive-Signature-V2`\nis then absent too, and nothing is signed in their place.\n\nDelivery on these lanes is two attempts, 6 seconds each, 2 seconds\napart, retried only on a 5xx, a timeout or an unreachable address. A\n4xx is final. Only a 2xx is a delivery. At most one redirect is\nfollowed, and its target must pass the same public address check as\nthe saved URL; a second redirect, a `Location` we cannot deliver to, or\na 3xx with no usable `Location` ends the delivery. There is no delivery\nlog: the campaign page and the call menu page show the last delivery\nresult.",
        "schema": {
          "type": "string"
        },
        "example": "sha256=4f1d2c8d4a0e..."
      },
      "CampaignWebhookSignatureV2": {
        "name": "X-Agentive-Signature-V2",
        "in": "header",
        "required": true,
        "description": "`t=<unix seconds>,v2=<hex HMAC-SHA256(account signing secret, \"t.body\")>`,\nwith the same `t` as `X-Agentive-Timestamp`. This is the signature to\nenforce a replay window against, because the timestamp is inside the\nsigned material: reject a `t` more than five minutes from your own\nclock, then compare `v2` with a timing-safe comparison\nafter checking byte lengths. Verify version 2 only, and refuse a delivery\nwithout it: every delivery carries it. It is absent only when the\naccount has no signing secret, which happens only on a database fault,\nand `X-Agentive-Signature` is then absent too.",
        "schema": {
          "type": "string"
        },
        "example": "t=1780589528,v2=9c41ab2f7e05..."
      },
      "CampaignWebhookTimestamp": {
        "name": "X-Agentive-Timestamp",
        "in": "header",
        "required": true,
        "description": "Unix seconds at the moment the event was sent, the same value as `created` in the body and as `t` in `X-Agentive-Signature-V2`. Stamped once per event, so the retry carries the same value as the first attempt.",
        "schema": {
          "type": "string"
        },
        "example": "1780589528"
      },
      "CampaignWebhookEventId": {
        "name": "X-Agentive-Event-Id",
        "in": "header",
        "required": true,
        "description": "The same value as `id` in the body, `evt_` and 20 hex characters (`evt_test_` on the Test button's sample). De-duplicate on it: an event can arrive twice.",
        "schema": {
          "type": "string"
        },
        "example": "evt_9f3c1a7b40d25e86c1b4"
      }
    },
    "headers": {
      "RateLimitLimit": {
        "description": "Requests allowed in the current 60 second window for this request's bucket.",
        "schema": {
          "type": "integer"
        }
      },
      "RateLimitRemaining": {
        "description": "Requests left in the current window for this request's bucket.",
        "schema": {
          "type": "integer"
        }
      },
      "RateLimitReset": {
        "description": "Seconds until the current window resets.",
        "schema": {
          "type": "integer"
        }
      },
      "RetryAfter": {
        "description": "Seconds to wait before retrying. Carries the same value as `retry_after_sec` in the body.",
        "schema": {
          "type": "integer"
        }
      },
      "IdempotencyReplayed": {
        "description": "`true` when this response is a stored replay of an earlier request with the same Idempotency-Key. Sent under both `idempotency-replayed` and `Idempotent-Replayed`.",
        "schema": {
          "type": "string",
          "enum": [
            "true"
          ]
        }
      },
      "AgentiveLines": {
        "description": "The account's purchased concurrent lines.",
        "schema": {
          "type": "integer"
        }
      },
      "AgentiveLinesInUse": {
        "description": "Live channels plus current reservations at the moment of admission.",
        "schema": {
          "type": "integer"
        }
      },
      "AgentiveLinesMode": {
        "description": "`off` (lines not evaluated), `shadow` (decided and logged, always admitted) or `on` (a request past the line count is refused).",
        "schema": {
          "type": "string",
          "enum": [
            "off",
            "shadow",
            "on"
          ]
        }
      }
    },
    "requestBodies": {
      "CallEvent": {
        "required": true,
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/WebhookCallEvent"
            }
          }
        }
      },
      "VerificationEvent": {
        "required": true,
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/WebhookVerificationEvent"
            }
          }
        }
      }
    },
    "responses": {
      "Unauthorized": {
        "description": "`missing_credentials` when no key was sent. `invalid_credentials` for an unknown key or a wrong secret on a known key; both return the identical body so a key cannot be enumerated.",
        "headers": {
          "RateLimit-Limit": {
            "$ref": "#/components/headers/RateLimitLimit"
          },
          "RateLimit-Remaining": {
            "$ref": "#/components/headers/RateLimitRemaining"
          },
          "RateLimit-Reset": {
            "$ref": "#/components/headers/RateLimitReset"
          },
          "X-RateLimit-Limit": {
            "$ref": "#/components/headers/RateLimitLimit"
          },
          "X-RateLimit-Remaining": {
            "$ref": "#/components/headers/RateLimitRemaining"
          },
          "X-RateLimit-Reset": {
            "$ref": "#/components/headers/RateLimitReset"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            }
          }
        }
      },
      "Forbidden": {
        "description": "`account_inactive` when the account is not active. `api_disabled` when the account does not have the master API entitlement. `feature_disabled` when it lacks the product this route belongs to.",
        "headers": {
          "RateLimit-Limit": {
            "$ref": "#/components/headers/RateLimitLimit"
          },
          "RateLimit-Remaining": {
            "$ref": "#/components/headers/RateLimitRemaining"
          },
          "RateLimit-Reset": {
            "$ref": "#/components/headers/RateLimitReset"
          },
          "X-RateLimit-Limit": {
            "$ref": "#/components/headers/RateLimitLimit"
          },
          "X-RateLimit-Remaining": {
            "$ref": "#/components/headers/RateLimitRemaining"
          },
          "X-RateLimit-Reset": {
            "$ref": "#/components/headers/RateLimitReset"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            }
          }
        }
      },
      "InsufficientBalance": {
        "description": "`insufficient_balance`. The balance is below the lane floor: the account's\nbulk call rate for a broadcast or verification call, defaulting to 1\nrupee; one minute of the agent's per minute price for an AI voice agent\ncall. The body adds `balance_inr` and `minimum_balance_inr`.",
        "headers": {
          "RateLimit-Limit": {
            "$ref": "#/components/headers/RateLimitLimit"
          },
          "RateLimit-Remaining": {
            "$ref": "#/components/headers/RateLimitRemaining"
          },
          "RateLimit-Reset": {
            "$ref": "#/components/headers/RateLimitReset"
          },
          "X-RateLimit-Limit": {
            "$ref": "#/components/headers/RateLimitLimit"
          },
          "X-RateLimit-Remaining": {
            "$ref": "#/components/headers/RateLimitRemaining"
          },
          "X-RateLimit-Reset": {
            "$ref": "#/components/headers/RateLimitReset"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            }
          }
        }
      },
      "TooManyRequests": {
        "description": "`rate_limited`. A request rate bucket is exhausted.",
        "headers": {
          "RateLimit-Limit": {
            "$ref": "#/components/headers/RateLimitLimit"
          },
          "RateLimit-Remaining": {
            "$ref": "#/components/headers/RateLimitRemaining"
          },
          "RateLimit-Reset": {
            "$ref": "#/components/headers/RateLimitReset"
          },
          "X-RateLimit-Limit": {
            "$ref": "#/components/headers/RateLimitLimit"
          },
          "X-RateLimit-Remaining": {
            "$ref": "#/components/headers/RateLimitRemaining"
          },
          "X-RateLimit-Reset": {
            "$ref": "#/components/headers/RateLimitReset"
          },
          "Retry-After": {
            "$ref": "#/components/headers/RetryAfter"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            }
          }
        }
      },
      "IdempotencyInProgress": {
        "description": "`idempotency_in_progress`. A request with this Idempotency-Key is still being processed. `retry_after_sec` 2.",
        "headers": {
          "Retry-After": {
            "$ref": "#/components/headers/RetryAfter"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            }
          }
        }
      },
      "IdempotencyConflict": {
        "description": "`idempotency_conflict`. This Idempotency-Key was already used with a different request body. Use a new key for a new request.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            }
          }
        }
      },
      "WebhookAck": {
        "description": "Any 2xx acknowledges the delivery. Anything else, and any network error, is retried on the schedule described on the signature header."
      },
      "CampaignWebhookAck": {
        "description": "Any 2xx acknowledges the delivery. A 5xx, a timeout or an unreachable address is retried once, 2 seconds later. A 4xx is final and is not retried."
      }
    },
    "schemas": {
      "ErrorCode": {
        "type": "string",
        "description": "The frozen machine token vocabulary. Branch on this instead of parsing `details`.",
        "enum": [
          "missing_credentials",
          "invalid_credentials",
          "account_inactive",
          "api_disabled",
          "feature_disabled",
          "trial_restricted",
          "validation_error",
          "invalid_number",
          "invalid_caller_id",
          "invalid_code",
          "calling_hours",
          "idempotency_in_progress",
          "idempotency_conflict",
          "rate_limited",
          "number_flood",
          "daily_cap",
          "concurrency_limit",
          "ai_concurrency_limit",
          "number_locked",
          "insufficient_balance",
          "not_found",
          "conflict",
          "upstream_error",
          "call_state_unknown",
          "service_unavailable",
          "incorrect_code",
          "max_attempts",
          "code_expired",
          "code_used",
          "verification_not_found",
          "whatsapp_locked",
          "whatsapp_not_connected",
          "whatsapp_template_required"
        ]
      },
      "PublicCallStatus": {
        "type": [
          "string",
          "null"
        ],
        "description": "The closed public status set. An internal state label never appears here.",
        "enum": [
          "queued",
          "ringing",
          "in_progress",
          "completed",
          "failed",
          "missed",
          null
        ]
      },
      "CallEndReason": {
        "type": [
          "string",
          "null"
        ],
        "description": "The closed public end reason set. A raw hangup cause never appears here.",
        "enum": [
          "completed",
          "no_answer",
          "busy",
          "rejected",
          "canceled",
          "failed",
          "voicemail",
          null
        ]
      },
      "VerificationStatus": {
        "type": "string",
        "description": "The closed verification lifecycle set.",
        "enum": [
          "pending",
          "verified",
          "failed",
          "locked",
          "expired",
          "undeliverable"
        ]
      },
      "IndianMobile": {
        "type": "string",
        "description": "An Indian mobile number. Accepted as 10 digits, 12 digits starting 91, a\n+91 form, or any longer form whose last 10 digits are used. The 10 digit\ncore must start 6 to 9.",
        "examples": [
          "9876543210",
          "919876543210",
          "+919876543210"
        ]
      },
      "ReferenceId": {
        "type": [
          "string",
          "null"
        ],
        "maxLength": 120,
        "description": "Your correlation key. Control characters are stripped and it is trimmed to 120 characters. Echoed on the response, on error bodies and on webhooks."
      },
      "SuccessEnvelope": {
        "type": "object",
        "description": "The single resource envelope. Endpoint specific keys are merged in beside these.",
        "required": [
          "status",
          "code",
          "unique_id",
          "reference_id",
          "details"
        ],
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "success"
            ]
          },
          "code": {
            "type": "integer",
            "enum": [
              200
            ]
          },
          "unique_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "Our id for the resource this call created or read."
          },
          "reference_id": {
            "$ref": "#/components/schemas/ReferenceId"
          },
          "details": {
            "type": "string",
            "description": "A short human sentence."
          }
        }
      },
      "ErrorEnvelope": {
        "type": "object",
        "description": "The single resource error envelope. The keys sit in this order, and endpoint specific extras are merged last, so a call site can override `error_code` or `retry_after_sec`.",
        "required": [
          "status",
          "code",
          "unique_id",
          "reference_id",
          "details",
          "error"
        ],
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "error"
            ]
          },
          "code": {
            "type": "integer",
            "description": "Mirrors the HTTP status."
          },
          "unique_id": {
            "type": "null"
          },
          "reference_id": {
            "$ref": "#/components/schemas/ReferenceId"
          },
          "details": {
            "type": "string",
            "description": "A short human sentence."
          },
          "error_code": {
            "$ref": "#/components/schemas/ErrorCode"
          },
          "error": {
            "type": "string",
            "description": "The same sentence as `details`."
          },
          "retry_after_sec": {
            "type": "integer",
            "description": "Present on every 429 and every 409. Matches the Retry-After header."
          },
          "balance_inr": {
            "type": "number",
            "description": "Present on insufficient_balance."
          },
          "minimum_balance_inr": {
            "type": "number",
            "description": "Present on insufficient_balance."
          },
          "lines": {
            "type": "integer",
            "description": "Present on concurrency_limit."
          },
          "lines_in_use": {
            "type": "integer",
            "description": "Present on concurrency_limit."
          }
        },
        "additionalProperties": true
      },
      "Pagination": {
        "type": "object",
        "required": [
          "page",
          "per_page",
          "total",
          "total_pages"
        ],
        "properties": {
          "page": {
            "type": "integer"
          },
          "per_page": {
            "type": "integer"
          },
          "total": {
            "type": "integer"
          },
          "total_pages": {
            "type": "integer"
          }
        }
      },
      "BareEnvelope": {
        "type": "object",
        "description": "The collection and read envelope. No `unique_id`, `reference_id` or `details`.",
        "required": [
          "status",
          "code"
        ],
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "success"
            ]
          },
          "code": {
            "type": "integer",
            "enum": [
              200
            ]
          }
        }
      },
      "PublicCall": {
        "type": "object",
        "description": "The public call object. Exactly these fields, never an internal column.\n`unique_id` is `run_<campaign_id>` when the record belongs to a campaign,\notherwise the record uuid, except that a call placed with `POST /ai/calls`\nalways answers the id the API returned (its dial id), on every read and\nevery event. `call_id` is always the record uuid. `leg_id` is the\ndial leg uuid when the record is keyed on something else, otherwise null.",
        "required": [
          "unique_id",
          "call_id",
          "leg_id",
          "direction",
          "from_number",
          "to_number",
          "status",
          "end_reason",
          "duration_sec",
          "campaign_id",
          "reference_id",
          "created_at",
          "created_at_ist",
          "created_at_utc"
        ],
        "properties": {
          "unique_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "call_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "leg_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "direction": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "inbound",
              "outbound",
              null
            ]
          },
          "from_number": {
            "type": [
              "string",
              "null"
            ]
          },
          "to_number": {
            "type": [
              "string",
              "null"
            ]
          },
          "status": {
            "$ref": "#/components/schemas/PublicCallStatus"
          },
          "end_reason": {
            "$ref": "#/components/schemas/CallEndReason"
          },
          "duration_sec": {
            "type": [
              "integer",
              "null"
            ]
          },
          "campaign_id": {
            "type": [
              "integer",
              "null"
            ]
          },
          "reference_id": {
            "$ref": "#/components/schemas/ReferenceId"
          },
          "created_at": {
            "type": [
              "string",
              "null"
            ],
            "description": "The raw stored timestamp."
          },
          "created_at_ist": {
            "type": [
              "string",
              "null"
            ],
            "description": "ISO 8601 at +05:30."
          },
          "created_at_utc": {
            "type": [
              "string",
              "null"
            ],
            "description": "ISO 8601 with a Z."
          }
        }
      },
      "PlaceCallRequest": {
        "type": "object",
        "required": [
          "type",
          "number"
        ],
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "audio_blast",
              "press1",
              "otp"
            ],
            "description": "Anything else is 400 validation_error."
          },
          "number": {
            "$ref": "#/components/schemas/IndianMobile"
          },
          "reference_id": {
            "$ref": "#/components/schemas/ReferenceId"
          },
          "audio_id": {
            "type": "integer",
            "description": "Required for audio_blast and press1. A clip id from your own library. A clip belonging to another account resolves to nothing and returns 400."
          },
          "caller_id": {
            "type": "string",
            "description": "Optional, audio_blast and press1 only. A number on your account that is\nenabled for voice broadcast, matched on the last 10 digits. Omit it and\nthe platform picks one. Quarantined numbers and rotation pool numbers\nare never accepted."
          },
          "dtmf_map": {
            "type": "object",
            "description": "Optional, press1 only. Keys are single digits 0 to 9. `connect_agent` is\naccepted as a legacy alias for `connect`. `whatsapp` is rejected with 400\nbecause an inline call has no saved template. The default when omitted is\n`{\"1\":\"connect\"}`, and a map whose every entry is invalid also falls back\nto that.",
            "propertyNames": {
              "pattern": "^[0-9]$"
            },
            "additionalProperties": {
              "type": "string",
              "enum": [
                "connect",
                "interest",
                "decline",
                "connect_agent"
              ]
            },
            "examples": [
              {
                "1": "connect",
                "2": "decline"
              }
            ]
          },
          "code": {
            "type": "string",
            "pattern": "^\\d{4,8}$",
            "description": "Optional, otp only. Your own 4 to 8 digit code. Omitted means one is generated. A trivially guessable code such as 1111 or 1234 is accepted, and the success body then carries warnings: [\"weak_code\"]."
          },
          "length": {
            "type": "integer",
            "description": "Optional, otp only. Generated code length, clamped to 4 to 8, default 4.",
            "default": 4
          },
          "ttl": {
            "type": "integer",
            "description": "Optional, otp only. Validity in seconds, clamped to 60 to 1800. Defaults to the validity on your account's verification campaign, otherwise 600. `ttl_seconds` is accepted as the same field."
          },
          "ttl_seconds": {
            "type": "integer",
            "description": "Alias for `ttl`."
          },
          "max_attempts": {
            "type": "integer",
            "description": "Optional, otp only. Verify attempts allowed, clamped to 1 to 10, default 3.",
            "default": 3
          },
          "message": {
            "type": "string",
            "description": "Optional, otp only. The spoken read out. Supports the {code} and\n{validity_time} tokens. Defaults to your account's verification campaign\ntemplate, otherwise the platform template. A custom template with no\n{code} has the code appended, so it is always spoken.\n`message_template` is accepted as the same field."
          },
          "message_template": {
            "type": "string",
            "description": "Alias for `message`."
          },
          "secret": {
            "type": "string",
            "description": "The API secret. Read only when neither the x-api-secret header nor HTTP Basic supplied it."
          }
        }
      },
      "TriggerCampaignRequest": {
        "type": "object",
        "required": [
          "number"
        ],
        "properties": {
          "number": {
            "$ref": "#/components/schemas/IndianMobile"
          },
          "reference_id": {
            "$ref": "#/components/schemas/ReferenceId"
          },
          "variables": {
            "allOf": [
              {
                "$ref": "#/components/schemas/CallVariables"
              }
            ],
            "description": "AI voice agent campaigns only. A broadcast campaign refuses it with 400."
          },
          "metadata": {
            "allOf": [
              {
                "$ref": "#/components/schemas/CallMetadata"
              }
            ],
            "description": "AI voice agent campaigns only. Echoed on the call and every webhook event."
          },
          "secret": {
            "type": "string",
            "description": "The API secret when not sent as a header."
          }
        }
      },
      "SendVerificationRequest": {
        "type": "object",
        "required": [
          "number"
        ],
        "properties": {
          "number": {
            "$ref": "#/components/schemas/IndianMobile"
          },
          "code": {
            "type": "string",
            "pattern": "^\\d{4,8}$",
            "description": "Your own code. Omitted means one is generated."
          },
          "length": {
            "type": "integer",
            "description": "Generated code length, clamped to 4 to 8, default 4.",
            "default": 4
          },
          "ttl": {
            "type": "integer",
            "description": "Validity in seconds, clamped to 60 to 1800, defaulting to your account's verification campaign validity and otherwise 600."
          },
          "ttl_seconds": {
            "type": "integer",
            "description": "Alias for `ttl`."
          },
          "max_attempts": {
            "type": "integer",
            "description": "Verify attempts allowed, clamped to 1 to 10, default 3.",
            "default": 3
          },
          "message": {
            "type": "string",
            "description": "The spoken read out, supporting {code} and {validity_time}."
          },
          "message_template": {
            "type": "string",
            "description": "Alias for `message`."
          },
          "reference_id": {
            "$ref": "#/components/schemas/ReferenceId"
          },
          "secret": {
            "type": "string",
            "description": "The API secret when not sent as a header."
          }
        }
      },
      "VerifyCodeRequest": {
        "type": "object",
        "required": [
          "code"
        ],
        "description": "Either `request_id` or `number` must be present.",
        "anyOf": [
          {
            "required": [
              "request_id"
            ]
          },
          {
            "required": [
              "otp_id"
            ]
          },
          {
            "required": [
              "number"
            ]
          },
          {
            "required": [
              "phone"
            ]
          }
        ],
        "properties": {
          "code": {
            "type": "string",
            "description": "The code the recipient gave you."
          },
          "request_id": {
            "type": "string",
            "description": "The id returned by the send. `otp_id` is accepted as the same field."
          },
          "otp_id": {
            "type": "string",
            "description": "Alias for `request_id`."
          },
          "number": {
            "$ref": "#/components/schemas/IndianMobile"
          },
          "phone": {
            "type": "string",
            "description": "Alias for `number`."
          },
          "secret": {
            "type": "string",
            "description": "The API secret when not sent as a header."
          }
        }
      },
      "RunQueuedResponse": {
        "allOf": [
          {
            "$ref": "#/components/schemas/SuccessEnvelope"
          },
          {
            "type": "object",
            "properties": {
              "unique_id": {
                "type": "string",
                "description": "`run_<campaign_id>`.",
                "examples": [
                  "run_4821"
                ]
              },
              "details": {
                "type": "string",
                "enum": [
                  "Call queued."
                ]
              },
              "campaign_id": {
                "type": "integer",
                "description": "AI voice agent campaigns only: the API campaign you triggered."
              },
              "agent_id": {
                "type": [
                  "integer",
                  "null"
                ],
                "description": "AI voice agent campaigns only: the agent the campaign calls with."
              },
              "call_status": {
                "type": "string",
                "enum": [
                  "queued"
                ],
                "description": "AI voice agent campaigns only."
              },
              "metadata": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/CallMetadata"
                  },
                  {
                    "type": "null"
                  }
                ],
                "description": "AI voice agent campaigns only: what you sent. A number comes back as text."
              }
            }
          }
        ]
      },
      "VerificationQueuedResponse": {
        "allOf": [
          {
            "$ref": "#/components/schemas/SuccessEnvelope"
          },
          {
            "type": "object",
            "required": [
              "request_id",
              "expires_in_sec",
              "verification_status",
              "campaign_id"
            ],
            "properties": {
              "unique_id": {
                "type": "string",
                "description": "The verification request id."
              },
              "details": {
                "type": "string",
                "enum": [
                  "Verification call queued."
                ]
              },
              "request_id": {
                "type": "string",
                "description": "The same id as unique_id."
              },
              "expires_in_sec": {
                "type": "integer"
              },
              "verification_status": {
                "type": "string",
                "enum": [
                  "pending"
                ]
              },
              "campaign_id": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "warnings": {
                "type": "array",
                "description": "Present only when the supplied code was trivially guessable.",
                "items": {
                  "type": "string",
                  "enum": [
                    "weak_code"
                  ]
                }
              }
            }
          }
        ]
      },
      "VerifiedResponse": {
        "allOf": [
          {
            "$ref": "#/components/schemas/SuccessEnvelope"
          },
          {
            "type": "object",
            "required": [
              "request_id",
              "verified",
              "verification_status"
            ],
            "properties": {
              "details": {
                "type": "string",
                "enum": [
                  "Code verified."
                ]
              },
              "reference_id": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The reference_id stored with the verification, otherwise the one sent on this request."
              },
              "request_id": {
                "type": "string"
              },
              "verified": {
                "type": "boolean",
                "enum": [
                  true
                ]
              },
              "verification_status": {
                "type": "string",
                "enum": [
                  "verified"
                ]
              }
            }
          }
        ]
      },
      "VerificationErrorEnvelope": {
        "allOf": [
          {
            "$ref": "#/components/schemas/ErrorEnvelope"
          },
          {
            "type": "object",
            "properties": {
              "verified": {
                "type": "boolean",
                "enum": [
                  false
                ]
              },
              "attempts_left": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "verification_status": {
                "$ref": "#/components/schemas/VerificationStatus"
              }
            }
          }
        ]
      },
      "RunStatusResponse": {
        "allOf": [
          {
            "$ref": "#/components/schemas/SuccessEnvelope"
          },
          {
            "type": "object",
            "required": [
              "call_status",
              "duration_sec",
              "call_id",
              "end_reason"
            ],
            "properties": {
              "unique_id": {
                "type": "string",
                "examples": [
                  "run_4821"
                ]
              },
              "details": {
                "type": "string",
                "description": "The status word."
              },
              "call_status": {
                "type": "string",
                "description": "The older five word run vocabulary.",
                "enum": [
                  "queued",
                  "ringing",
                  "answered",
                  "completed",
                  "failed"
                ]
              },
              "duration_sec": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "call_id": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "end_reason": {
                "$ref": "#/components/schemas/CallEndReason"
              },
              "call": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/PublicCall"
                  }
                ],
                "description": "Present once a call record exists."
              }
            }
          }
        ]
      },
      "VerificationStatusResponse": {
        "allOf": [
          {
            "$ref": "#/components/schemas/SuccessEnvelope"
          },
          {
            "type": "object",
            "required": [
              "call_status",
              "request_id",
              "verified",
              "attempts",
              "max_attempts",
              "attempts_left",
              "locked",
              "verification_status",
              "expires_at_ist",
              "campaign_id"
            ],
            "properties": {
              "details": {
                "type": "string",
                "description": "The stored status."
              },
              "call_status": {
                "type": "string",
                "description": "The stored status."
              },
              "request_id": {
                "type": "string"
              },
              "verified": {
                "type": "boolean"
              },
              "attempts": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "max_attempts": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "attempts_left": {
                "type": "integer"
              },
              "locked": {
                "type": "boolean"
              },
              "verification_status": {
                "$ref": "#/components/schemas/VerificationStatus"
              },
              "expires_at_ist": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "ISO 8601 at +05:30."
              },
              "campaign_id": {
                "type": [
                  "integer",
                  "null"
                ]
              }
            }
          }
        ]
      },
      "CallStatusResponse": {
        "allOf": [
          {
            "$ref": "#/components/schemas/SuccessEnvelope"
          },
          {
            "type": "object",
            "required": [
              "call",
              "call_status",
              "duration_sec"
            ],
            "properties": {
              "details": {
                "type": "string",
                "description": "The public status."
              },
              "call": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/PublicCall"
                  },
                  {
                    "type": "object",
                    "properties": {
                      "agent_id": {
                        "type": [
                          "integer",
                          "null"
                        ],
                        "description": "The AI agent on the call, else null."
                      },
                      "via_api": {
                        "type": "boolean",
                        "description": "True when the call was placed through this API."
                      },
                      "metadata": {
                        "anyOf": [
                          {
                            "$ref": "#/components/schemas/CallMetadata"
                          },
                          {
                            "type": "null"
                          }
                        ],
                        "description": "The metadata you sent. A number comes back as text."
                      },
                      "charge_inr": {
                        "type": [
                          "number",
                          "null"
                        ],
                        "description": "The billed amount in rupees for an AI call once billed, else null."
                      },
                      "transcript_available": {
                        "type": "boolean"
                      },
                      "analysis_available": {
                        "type": "boolean"
                      }
                    }
                  }
                ],
                "description": "For an AI call placed moments ago whose record has not landed yet, the same shape with status queued or in_progress, call_id and leg_id equal to the dial id, and both availability flags false."
              },
              "call_status": {
                "$ref": "#/components/schemas/PublicCallStatus"
              },
              "duration_sec": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "via_api": {
                "type": "boolean",
                "description": "The same value as call.via_api."
              },
              "metadata": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/CallMetadata"
                  },
                  {
                    "type": "null"
                  }
                ],
                "description": "The same value as call.metadata."
              }
            }
          }
        ]
      },
      "CallListEnvelope": {
        "allOf": [
          {
            "$ref": "#/components/schemas/BareEnvelope"
          },
          {
            "type": "object",
            "required": [
              "data",
              "pagination"
            ],
            "properties": {
              "data": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/PublicCall"
                }
              },
              "pagination": {
                "$ref": "#/components/schemas/Pagination"
              }
            }
          }
        ]
      },
      "AccountResponse": {
        "allOf": [
          {
            "$ref": "#/components/schemas/BareEnvelope"
          },
          {
            "type": "object",
            "required": [
              "data"
            ],
            "properties": {
              "data": {
                "type": "object",
                "required": [
                  "balance_inr",
                  "currency",
                  "lines",
                  "lines_in_use",
                  "lines_mode"
                ],
                "properties": {
                  "balance_inr": {
                    "type": "number",
                    "description": "Rounded to 2 decimals."
                  },
                  "currency": {
                    "type": "string",
                    "enum": [
                      "INR"
                    ]
                  },
                  "lines": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "description": "Purchased concurrent lines. An account with no line count stored against it reports the platform default of 10, so this is never 0. It is null only when the read itself failed."
                  },
                  "lines_in_use": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "description": "Live channels plus current reservations, null if unreadable."
                  },
                  "lines_mode": {
                    "type": "string",
                    "enum": [
                      "off",
                      "shadow",
                      "on"
                    ]
                  },
                  "products": {
                    "type": "object",
                    "description": "What this key can use.",
                    "properties": {
                      "ai_voice_agent": {
                        "type": "boolean"
                      },
                      "voice_broadcast": {
                        "type": "boolean"
                      },
                      "verification_calls": {
                        "type": "boolean"
                      }
                    }
                  }
                }
              }
            }
          }
        ]
      },
      "CampaignSummary": {
        "type": "object",
        "required": [
          "id",
          "name",
          "type",
          "status",
          "press1_action"
        ],
        "properties": {
          "id": {
            "type": "integer"
          },
          "name": {
            "type": "string",
            "description": "Empty string when unset."
          },
          "type": {
            "type": "string",
            "enum": [
              "audio_blast",
              "press1",
              "otp",
              "ai_agent"
            ]
          },
          "status": {
            "type": [
              "string",
              "null"
            ]
          },
          "press1_action": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "connect",
              "interest",
              "whatsapp",
              null
            ],
            "description": "Set on a press1 campaign, null for every other type."
          },
          "agent_id": {
            "type": [
              "integer",
              "null"
            ],
            "description": "The AI voice agent on an ai_agent campaign, null for every other type."
          }
        }
      },
      "CampaignListResponse": {
        "allOf": [
          {
            "$ref": "#/components/schemas/BareEnvelope"
          },
          {
            "type": "object",
            "required": [
              "data"
            ],
            "properties": {
              "data": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/CampaignSummary"
                }
              }
            }
          }
        ]
      },
      "TranscriptTurn": {
        "type": "object",
        "required": [
          "role",
          "text"
        ],
        "properties": {
          "role": {
            "type": "string",
            "enum": [
              "caller",
              "agent"
            ]
          },
          "text": {
            "type": "string"
          }
        }
      },
      "TranscriptResponse": {
        "allOf": [
          {
            "$ref": "#/components/schemas/BareEnvelope"
          },
          {
            "type": "object",
            "required": [
              "data"
            ],
            "properties": {
              "data": {
                "type": "object",
                "required": [
                  "unique_id",
                  "call_id",
                  "leg_id",
                  "turns",
                  "created_at",
                  "created_at_ist"
                ],
                "properties": {
                  "unique_id": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "call_id": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "leg_id": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "turns": {
                    "type": "array",
                    "items": {
                      "$ref": "#/components/schemas/TranscriptTurn"
                    }
                  },
                  "created_at": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "created_at_ist": {
                    "type": [
                      "string",
                      "null"
                    ]
                  }
                }
              }
            }
          }
        ]
      },
      "AnalysisResponse": {
        "allOf": [
          {
            "$ref": "#/components/schemas/BareEnvelope"
          },
          {
            "type": "object",
            "required": [
              "data"
            ],
            "properties": {
              "data": {
                "type": "object",
                "required": [
                  "unique_id",
                  "call_id",
                  "leg_id",
                  "summary",
                  "sentiment",
                  "sentiment_score",
                  "intent",
                  "outcome",
                  "extractions",
                  "tags",
                  "created_at",
                  "created_at_ist"
                ],
                "properties": {
                  "unique_id": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "call_id": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "leg_id": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "summary": {
                    "type": "string",
                    "description": "Empty string when unset."
                  },
                  "sentiment": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "sentiment_score": {
                    "type": [
                      "number",
                      "null"
                    ]
                  },
                  "intent": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "outcome": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "extractions": {
                    "type": "object",
                    "description": "Defaults to an empty object when unset or unparseable."
                  },
                  "tags": {
                    "type": "array",
                    "items": {},
                    "description": "Defaults to an empty array when unset or unparseable."
                  },
                  "created_at": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "created_at_ist": {
                    "type": [
                      "string",
                      "null"
                    ]
                  }
                }
              }
            }
          }
        ]
      },
      "WebhookEnvelope": {
        "type": "object",
        "description": "The signed envelope every delivery carries. Receivers must ignore unknown\nkeys: additive fields do not bump `api_version`, which changes only on a\nremoved or renamed field.",
        "required": [
          "id",
          "object",
          "api_version",
          "type",
          "created",
          "occurred_at",
          "timestamp_ist",
          "org_id",
          "reference_id",
          "livemode",
          "data",
          "event",
          "event_id"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "The event id. Dedupe on this.",
            "examples": [
              "evt_3f9a1c74d0b64e0f"
            ]
          },
          "object": {
            "type": "string",
            "enum": [
              "event"
            ]
          },
          "api_version": {
            "type": "string",
            "examples": [
              "2026-06-01"
            ]
          },
          "type": {
            "type": "string",
            "description": "The event name, as `{resource}.{action}`."
          },
          "created": {
            "type": "integer",
            "description": "Unix epoch seconds."
          },
          "occurred_at": {
            "type": "string",
            "description": "UTC ISO 8601."
          },
          "timestamp_ist": {
            "type": "string",
            "description": "The same instant at +05:30."
          },
          "org_id": {
            "type": "integer"
          },
          "reference_id": {
            "$ref": "#/components/schemas/ReferenceId"
          },
          "livemode": {
            "type": "boolean"
          },
          "data": {
            "type": "object"
          },
          "event": {
            "type": "string",
            "description": "Legacy alias of `type`."
          },
          "event_id": {
            "type": "string",
            "description": "Legacy alias of `id`."
          }
        }
      },
      "WebhookCallData": {
        "type": "object",
        "required": [
          "object",
          "unique_id",
          "call_id",
          "leg_id",
          "direction",
          "from_number",
          "to_number",
          "status",
          "duration_sec",
          "end_reason",
          "campaign_id",
          "campaign_type",
          "agent_id",
          "reference_id",
          "response"
        ],
        "properties": {
          "object": {
            "type": "string",
            "enum": [
              "call"
            ]
          },
          "unique_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "The public handle, for example run_4821. For an AI call placed through the API, the id the API returned (the dial id or run_<id>), on every event of the call."
          },
          "call_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "leg_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "The second id for the same call, or null."
          },
          "direction": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "inbound",
              "outbound",
              null
            ]
          },
          "from_number": {
            "type": [
              "string",
              "null"
            ]
          },
          "to_number": {
            "type": [
              "string",
              "null"
            ]
          },
          "status": {
            "type": [
              "string",
              "null"
            ],
            "description": "Derived from the event, never from what the fire site happened to hold.",
            "enum": [
              "queued",
              "ringing",
              "in_progress",
              "completed",
              "missed",
              "failed",
              null
            ]
          },
          "duration_sec": {
            "type": [
              "integer",
              "null"
            ]
          },
          "end_reason": {
            "$ref": "#/components/schemas/CallEndReason"
          },
          "campaign_id": {
            "type": [
              "integer",
              "null"
            ],
            "description": "The run this call belongs to; for an AI voice agent API campaign, the campaign you triggered."
          },
          "campaign_type": {
            "type": [
              "string",
              "null"
            ],
            "description": "audio_blast, press1, otp or ai_agent."
          },
          "agent_id": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Set on an AI call, else null."
          },
          "reference_id": {
            "$ref": "#/components/schemas/ReferenceId"
          },
          "metadata": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/CallMetadata"
              },
              {
                "type": "null"
              }
            ],
            "description": "The metadata sent when the call was placed through the API. A number comes back as text."
          },
          "via_api": {
            "type": "boolean",
            "description": "True when the call was placed through this API."
          },
          "charge_inr": {
            "type": [
              "number",
              "null"
            ],
            "description": "AI calls, terminal events only: what the call cost the wallet in rupees, 0 when it never connected, null when an answered call is not billed yet."
          },
          "response": {
            "type": "object",
            "description": "How the recipient responded to an interactive call. Null fields on early events.",
            "properties": {
              "disposition": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "interested, not_interested, or null."
              },
              "digit": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The first key pressed, if any."
              },
              "via": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "keypad, voice, or null."
              },
              "answers": {
                "description": "Guided flow per question answers, or null."
              },
              "voicemail_left": {
                "type": [
                  "boolean",
                  "null"
                ]
              }
            }
          }
        }
      },
      "WebhookVerificationData": {
        "type": "object",
        "required": [
          "object",
          "request_id",
          "number",
          "status",
          "verification_status",
          "verified",
          "attempts_left",
          "expires_in_sec",
          "max_attempts",
          "campaign_id",
          "reference_id"
        ],
        "properties": {
          "object": {
            "type": "string",
            "enum": [
              "verification"
            ]
          },
          "request_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "number": {
            "type": [
              "string",
              "null"
            ],
            "description": "The recipient, in E.164."
          },
          "status": {
            "type": [
              "string",
              "null"
            ],
            "description": "The legacy status word, unchanged for existing integrations."
          },
          "verification_status": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "pending",
              "verified",
              "failed",
              "locked",
              "expired",
              "undeliverable",
              null
            ]
          },
          "verified": {
            "type": [
              "boolean",
              "null"
            ]
          },
          "attempts_left": {
            "type": [
              "integer",
              "null"
            ]
          },
          "expires_in_sec": {
            "type": [
              "integer",
              "null"
            ]
          },
          "max_attempts": {
            "type": [
              "integer",
              "null"
            ]
          },
          "campaign_id": {
            "type": [
              "integer",
              "null"
            ]
          },
          "reference_id": {
            "$ref": "#/components/schemas/ReferenceId"
          }
        }
      },
      "WebhookRecordingData": {
        "type": "object",
        "required": [
          "object",
          "unique_id",
          "call_id",
          "leg_id",
          "recording_url",
          "format",
          "duration_sec",
          "size_bytes",
          "campaign_id",
          "campaign_type",
          "reference_id"
        ],
        "properties": {
          "object": {
            "type": "string",
            "enum": [
              "recording"
            ]
          },
          "unique_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "call_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "leg_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "recording_url": {
            "type": [
              "string",
              "null"
            ],
            "description": "The authenticated API URL to download the audio, never a storage path."
          },
          "format": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "ogg",
              "wav",
              null
            ]
          },
          "duration_sec": {
            "type": [
              "integer",
              "null"
            ]
          },
          "size_bytes": {
            "type": [
              "integer",
              "null"
            ]
          },
          "campaign_id": {
            "type": [
              "integer",
              "null"
            ],
            "description": "The run this call belongs to; for an AI voice agent API campaign, the campaign you triggered. The same value the call events carry."
          },
          "campaign_type": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "audio_blast",
              "press1",
              "otp",
              "ai_agent",
              null
            ],
            "description": "audio_blast, press1, otp or ai_agent (an AI voice agent took the conversation). The same value the call events carry."
          },
          "reference_id": {
            "$ref": "#/components/schemas/ReferenceId"
          },
          "metadata": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/CallMetadata"
              },
              {
                "type": "null"
              }
            ],
            "description": "The metadata sent when the call was placed through the API, else null. A number comes back as text."
          },
          "via_api": {
            "type": "boolean",
            "description": "True when the call was placed through this API. The same value the call events carry."
          }
        }
      },
      "CallVariables": {
        "type": "object",
        "description": "Values for the `{{key}}` placeholders in the agent's greeting and prompt,\nfor this call only. At most 20 keys. A key is lowercased, then must match\n`^[a-z0-9_]{1,40}$`, and is matched to the agent's placeholders without\nregard to case. A value is a string or a finite number (numbers become\ntext), at most 200 characters after trimming, with no line breaks or other\ncontrol characters (the Unicode line and paragraph separators included)\nand no `{` or `}` braces; all values together at most 2,000 characters.\nA value is inserted into the agent's instructions exactly as written: pass\nfacts your systems control (a name, an amount, a date, a plan name), never\nfree text typed by the person being called.\nReserved keys are refused: company_name, brand_name, agent_name,\ncaller_number, customer_number, current_date, current_time, current_day,\ncurrent_weekday, call_id, org_id, agent_id, caller_phone,\ncaller_phone_last4, industry. A key the agent does not use is ignored. Any\nbroken rule is a 400 `validation_error` whose sentence names the key.",
        "maxProperties": 20,
        "propertyNames": {
          "maxLength": 40
        },
        "additionalProperties": {
          "type": [
            "string",
            "number"
          ]
        },
        "examples": [
          {
            "naam": "Neha",
            "amount": "₹2,499",
            "plan": "Gold"
          }
        ]
      },
      "CallMetadata": {
        "type": "object",
        "description": "Your own data for the call, never shown to the agent. At most 10 keys,\neach matching `^[A-Za-z0-9_]{1,40}$` and kept as sent; a value is a string\nor a number, at most 200 characters, with no control characters. A number\nis stored as text, so `2` comes back as `\"2\"`. Echoed on\n`GET /calls/{unique_id}` and every webhook event for the call.",
        "maxProperties": 10,
        "propertyNames": {
          "pattern": "^[A-Za-z0-9_]{1,40}$"
        },
        "additionalProperties": {
          "type": [
            "string",
            "number"
          ]
        },
        "examples": [
          {
            "crm_lead_id": "L-20931"
          }
        ]
      },
      "AiCallRequest": {
        "type": "object",
        "required": [
          "agent_id",
          "to_number"
        ],
        "properties": {
          "agent_id": {
            "type": "integer",
            "minimum": 1,
            "description": "An active agent that can place calls."
          },
          "to_number": {
            "$ref": "#/components/schemas/IndianMobile"
          },
          "from_number": {
            "type": "string",
            "description": "Optional. One of the account's numbers enabled for AI voice agents. Omitted means one is chosen."
          },
          "variables": {
            "$ref": "#/components/schemas/CallVariables"
          },
          "metadata": {
            "$ref": "#/components/schemas/CallMetadata"
          },
          "reference_id": {
            "$ref": "#/components/schemas/ReferenceId"
          }
        }
      },
      "AiCallQueuedResponse": {
        "allOf": [
          {
            "$ref": "#/components/schemas/SuccessEnvelope"
          },
          {
            "type": "object",
            "required": [
              "call_id",
              "agent_id",
              "to_number",
              "from_number",
              "call_status"
            ],
            "properties": {
              "unique_id": {
                "type": "string",
                "description": "The dial id. Every webhook event for the call carries it."
              },
              "details": {
                "type": "string",
                "enum": [
                  "Call queued."
                ]
              },
              "call_id": {
                "type": "string",
                "description": "The same value as unique_id. Once an answered call ends its record has its own call_id; this id stays the leg_id and unique_id never changes."
              },
              "agent_id": {
                "type": "integer"
              },
              "to_number": {
                "type": "string",
                "description": "E.164."
              },
              "from_number": {
                "type": "string"
              },
              "call_status": {
                "type": "string",
                "enum": [
                  "queued"
                ]
              },
              "metadata": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/CallMetadata"
                  },
                  {
                    "type": "null"
                  }
                ]
              }
            }
          }
        ]
      },
      "AgentVariable": {
        "type": "object",
        "required": [
          "key",
          "default_set"
        ],
        "properties": {
          "key": {
            "type": "string",
            "description": "A placeholder the agent's greeting or prompt uses."
          },
          "default_set": {
            "type": "boolean",
            "description": "True when the agent has a default value for it."
          }
        }
      },
      "Agent": {
        "type": "object",
        "required": [
          "id",
          "name",
          "status",
          "direction",
          "languages",
          "variables",
          "can_call_out",
          "can_take_calls"
        ],
        "properties": {
          "id": {
            "type": "integer"
          },
          "name": {
            "type": "string",
            "description": "Empty string when unset."
          },
          "status": {
            "type": "string"
          },
          "direction": {
            "type": "string",
            "enum": [
              "inbound",
              "outbound",
              "both"
            ]
          },
          "languages": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "variables": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AgentVariable"
            }
          },
          "can_call_out": {
            "type": "boolean",
            "description": "Can be used with POST /ai/calls."
          },
          "can_take_calls": {
            "type": "boolean",
            "description": "Can be put on an inbound number."
          }
        }
      },
      "AgentListResponse": {
        "allOf": [
          {
            "$ref": "#/components/schemas/BareEnvelope"
          },
          {
            "type": "object",
            "required": [
              "data"
            ],
            "properties": {
              "data": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/Agent"
                }
              }
            }
          }
        ]
      },
      "AgentResponse": {
        "allOf": [
          {
            "$ref": "#/components/schemas/BareEnvelope"
          },
          {
            "type": "object",
            "required": [
              "data"
            ],
            "properties": {
              "data": {
                "$ref": "#/components/schemas/Agent"
              }
            }
          }
        ]
      },
      "PhoneNumber": {
        "type": "object",
        "required": [
          "number",
          "inbound_agent_id",
          "bindable"
        ],
        "properties": {
          "number": {
            "type": "string",
            "description": "The number in national form."
          },
          "inbound_agent_id": {
            "type": [
              "integer",
              "null"
            ],
            "description": "The AI agent answering it, or null."
          },
          "bindable": {
            "type": "boolean",
            "description": "False when the number rings a team member, runs a call menu, is a shared line or is not enabled for AI."
          }
        }
      },
      "NumberListResponse": {
        "allOf": [
          {
            "$ref": "#/components/schemas/BareEnvelope"
          },
          {
            "type": "object",
            "required": [
              "data"
            ],
            "properties": {
              "data": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/PhoneNumber"
                }
              }
            }
          }
        ]
      },
      "NumberBindingRequest": {
        "type": "object",
        "required": [
          "inbound_agent_id"
        ],
        "properties": {
          "inbound_agent_id": {
            "type": [
              "integer",
              "null"
            ],
            "description": "An active agent that can take calls, or null to take the agent off."
          }
        }
      },
      "NumberBindingResponse": {
        "allOf": [
          {
            "$ref": "#/components/schemas/BareEnvelope"
          },
          {
            "type": "object",
            "required": [
              "data"
            ],
            "properties": {
              "data": {
                "type": "object",
                "required": [
                  "number",
                  "inbound_agent_id"
                ],
                "properties": {
                  "number": {
                    "type": "string"
                  },
                  "inbound_agent_id": {
                    "type": [
                      "integer",
                      "null"
                    ]
                  }
                }
              }
            }
          }
        ]
      },
      "WebhookCallAnalysedData": {
        "type": "object",
        "description": "Exactly these keys, in this order. The ids are the same as on the call\nevents of the same call: `unique_id` is the id the API returned (the\ndial id, or `run_<id>` for a trigger), `call_id` the internal record id,\n`leg_id` the dial leg.",
        "required": [
          "object",
          "unique_id",
          "call_id",
          "leg_id",
          "direction",
          "from_number",
          "to_number",
          "agent_id",
          "campaign_id",
          "campaign_type",
          "via_api",
          "reference_id",
          "metadata",
          "summary",
          "sentiment",
          "intent",
          "outcome",
          "interest",
          "tags",
          "extractions",
          "transcript",
          "recording_available",
          "duration_sec",
          "charge_inr"
        ],
        "properties": {
          "object": {
            "type": "string",
            "enum": [
              "call_analysis"
            ]
          },
          "unique_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "The id returned when the call was placed: the dial id, or run_<id> for a trigger."
          },
          "call_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "The internal call record id."
          },
          "leg_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "The dial id for a call from POST /ai/calls. Otherwise the phone leg's id when the call record is keyed on something else, else null."
          },
          "direction": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "inbound",
              "outbound",
              null
            ]
          },
          "from_number": {
            "type": [
              "string",
              "null"
            ],
            "description": "The number the customer saw."
          },
          "to_number": {
            "type": [
              "string",
              "null"
            ],
            "description": "The number called."
          },
          "agent_id": {
            "type": [
              "integer",
              "null"
            ]
          },
          "campaign_id": {
            "type": [
              "integer",
              "null"
            ],
            "description": "The API campaign that was triggered, else null."
          },
          "campaign_type": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "ai_agent",
              null
            ]
          },
          "via_api": {
            "type": [
              "boolean",
              "null"
            ],
            "description": "True when the call was placed through this API."
          },
          "reference_id": {
            "$ref": "#/components/schemas/ReferenceId"
          },
          "metadata": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/CallMetadata"
              },
              {
                "type": "null"
              }
            ],
            "description": "What you sent. A number comes back as text."
          },
          "summary": {
            "type": [
              "string",
              "null"
            ]
          },
          "sentiment": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "positive",
              "neutral",
              "negative",
              null
            ]
          },
          "intent": {
            "type": [
              "string",
              "null"
            ]
          },
          "outcome": {
            "type": [
              "string",
              "null"
            ],
            "description": "For example unclear, callback, interested, not_interested, do_not_call or voicemail. Treat a value you do not recognise as unclear."
          },
          "interest": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "interested",
              "not_interested",
              "callback",
              null
            ]
          },
          "tags": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "extractions": {
            "type": [
              "object",
              "null"
            ],
            "description": "The answers the agent collected, keyed by the field names set on the agent."
          },
          "transcript": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "type": "object",
              "required": [
                "role",
                "text"
              ],
              "properties": {
                "role": {
                  "type": "string",
                  "enum": [
                    "agent",
                    "customer"
                  ]
                },
                "text": {
                  "type": "string"
                }
              }
            }
          },
          "recording_available": {
            "type": [
              "boolean",
              "null"
            ]
          },
          "duration_sec": {
            "type": [
              "integer",
              "null"
            ]
          },
          "charge_inr": {
            "type": [
              "number",
              "null"
            ],
            "description": "What the call cost the wallet, in rupees. null when it is not billed yet."
          }
        }
      },
      "WebhookCallAnalysedEvent": {
        "allOf": [
          {
            "$ref": "#/components/schemas/WebhookEnvelope"
          },
          {
            "type": "object",
            "properties": {
              "data": {
                "$ref": "#/components/schemas/WebhookCallAnalysedData"
              }
            }
          }
        ]
      },
      "WebhookCallEvent": {
        "allOf": [
          {
            "$ref": "#/components/schemas/WebhookEnvelope"
          },
          {
            "type": "object",
            "properties": {
              "data": {
                "$ref": "#/components/schemas/WebhookCallData"
              }
            }
          }
        ]
      },
      "WebhookVerificationEvent": {
        "allOf": [
          {
            "$ref": "#/components/schemas/WebhookEnvelope"
          },
          {
            "type": "object",
            "properties": {
              "data": {
                "$ref": "#/components/schemas/WebhookVerificationData"
              }
            }
          }
        ]
      },
      "WebhookRecordingEvent": {
        "allOf": [
          {
            "$ref": "#/components/schemas/WebhookEnvelope"
          },
          {
            "type": "object",
            "properties": {
              "data": {
                "$ref": "#/components/schemas/WebhookRecordingData"
              }
            }
          }
        ]
      },
      "CampaignEventEnvelope": {
        "type": "object",
        "description": "The envelope a campaign webhook carries. It is not the endpoint webhook\nenvelope: there is no `api_version`, `object`, `occurred_at`,\n`reference_id`, `livemode` or `event_id`. Receivers must ignore unknown\nkeys.",
        "required": [
          "id",
          "type",
          "event",
          "created",
          "timestamp_ist",
          "test",
          "org_id"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "`evt_` and 20 hex characters. Stable for this event; de-duplicate on it. The Test button's sample is prefixed `evt_test_` instead.",
            "examples": [
              "evt_9f3c1a7b40d25e86c1b4"
            ]
          },
          "type": {
            "type": "string",
            "description": "The dotted event name. Branch on this."
          },
          "event": {
            "type": "string",
            "description": "The legacy name, kept so existing handlers keep working."
          },
          "created": {
            "type": "integer",
            "description": "Unix epoch seconds, the same value as `X-Agentive-Timestamp`."
          },
          "timestamp_ist": {
            "type": "string",
            "description": "The same instant at +05:30, ISO 8601 with milliseconds.",
            "examples": [
              "2026-06-04T21:42:08.412+05:30"
            ]
          },
          "test": {
            "type": "boolean",
            "description": "`true` only on the sample the Test button sends."
          },
          "org_id": {
            "type": "integer",
            "description": "Your account id."
          }
        }
      },
      "CampaignRef": {
        "type": "object",
        "description": "The campaign an event belongs to.",
        "required": [
          "id",
          "name",
          "mode",
          "dial_type",
          "parent_campaign_id"
        ],
        "properties": {
          "id": {
            "type": "integer"
          },
          "name": {
            "type": "string"
          },
          "mode": {
            "type": "string",
            "description": "The pacing mode."
          },
          "dial_type": {
            "type": "string",
            "description": "The internal campaign type, not the `type` value that `GET /campaigns` returns.",
            "enum": [
              "broadcast_dtmf",
              "broadcast_audio",
              "human_pool",
              "ai_agent",
              "auth_otp"
            ]
          },
          "parent_campaign_id": {
            "type": [
              "integer",
              "null"
            ]
          }
        }
      },
      "CampaignContactRef": {
        "type": "object",
        "description": "The contact an attempt dialled. `{ \"id\": null }` when the attempt has no contact row.",
        "required": [
          "id"
        ],
        "properties": {
          "id": {
            "type": [
              "integer",
              "null"
            ]
          },
          "phone": {
            "type": [
              "string",
              "null"
            ]
          },
          "name": {
            "type": [
              "string",
              "null"
            ]
          },
          "email": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "WebhookCampaignAttemptEndEvent": {
        "allOf": [
          {
            "$ref": "#/components/schemas/CampaignEventEnvelope"
          },
          {
            "type": "object",
            "required": [
              "campaign",
              "contact",
              "attempt",
              "press",
              "flow_answers",
              "whatsapp"
            ],
            "properties": {
              "type": {
                "type": "string",
                "enum": [
                  "campaign.attempt_end"
                ]
              },
              "event": {
                "type": "string",
                "enum": [
                  "attempt_end"
                ]
              },
              "campaign": {
                "$ref": "#/components/schemas/CampaignRef"
              },
              "contact": {
                "$ref": "#/components/schemas/CampaignContactRef"
              },
              "attempt": {
                "type": "object",
                "description": "`end_reason` is the raw carrier hangup cause recorded on the call row (for example `NORMAL_CLEARING` or `USER_BUSY`), not the call object's published end reason, and it is null when no cause was recorded.",
                "required": [
                  "attempt_no",
                  "outcome",
                  "amd_result",
                  "duration_sec",
                  "ended_at",
                  "call_uuid",
                  "disposition",
                  "end_reason"
                ],
                "properties": {
                  "attempt_no": {
                    "type": "integer"
                  },
                  "outcome": {
                    "type": "string"
                  },
                  "amd_result": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "duration_sec": {
                    "type": [
                      "integer",
                      "null"
                    ]
                  },
                  "ended_at": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "UTC ISO 8601."
                  },
                  "call_uuid": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "disposition": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "end_reason": {
                    "type": [
                      "string",
                      "null"
                    ]
                  }
                }
              },
              "press": {
                "type": [
                  "object",
                  "null"
                ],
                "description": "What the recipient chose. Null when nothing was pressed or said, and always null on a guided flow campaign, which reports `flow_answers` instead. `action` is `none` when the campaign gives that key no meaning; treat it as a real value, not a missing one.",
                "required": [
                  "digit",
                  "action",
                  "via"
                ],
                "properties": {
                  "digit": {
                    "type": "string"
                  },
                  "action": {
                    "type": "string",
                    "enum": [
                      "connect",
                      "interest",
                      "whatsapp",
                      "decline",
                      "none"
                    ]
                  },
                  "via": {
                    "type": "string",
                    "enum": [
                      "keypad",
                      "voice"
                    ]
                  }
                }
              },
              "flow_answers": {
                "type": [
                  "object",
                  "null"
                ],
                "description": "For a guided flow, the answer to each question keyed `q0`, `q1` and so on. `none` is a question that ran out of retries with no reply. Null when the campaign has no flow.",
                "additionalProperties": {
                  "type": "string",
                  "enum": [
                    "yes",
                    "no",
                    "none"
                  ]
                }
              },
              "whatsapp": {
                "type": [
                  "object",
                  "null"
                ],
                "description": "Present only when a WhatsApp message was owed for this attempt; null otherwise. When `sent` is false, `error` is one of `frequency_suppressed`, `not_connected`, `locked`, `no_template`, `bad_number`, `send_refused`, `network` or `pending`. `pending` means the send was still in flight when the event was sent.",
                "required": [
                  "sent",
                  "suppressed",
                  "error"
                ],
                "properties": {
                  "sent": {
                    "type": "boolean"
                  },
                  "suppressed": {
                    "type": "boolean"
                  },
                  "error": {
                    "type": [
                      "string",
                      "null"
                    ]
                  }
                }
              }
            }
          }
        ]
      },
      "WebhookCampaignDispositionSetEvent": {
        "allOf": [
          {
            "$ref": "#/components/schemas/CampaignEventEnvelope"
          },
          {
            "type": "object",
            "required": [
              "campaign",
              "contact",
              "call",
              "set_at"
            ],
            "properties": {
              "type": {
                "type": "string",
                "enum": [
                  "campaign.disposition_set"
                ]
              },
              "event": {
                "type": "string",
                "enum": [
                  "disposition_set"
                ]
              },
              "campaign": {
                "$ref": "#/components/schemas/CampaignRef"
              },
              "contact": {
                "$ref": "#/components/schemas/CampaignContactRef"
              },
              "call": {
                "type": "object",
                "required": [
                  "uuid",
                  "disposition",
                  "disposition_by",
                  "notes"
                ],
                "properties": {
                  "uuid": {
                    "type": "string"
                  },
                  "disposition": {
                    "type": "string"
                  },
                  "disposition_by": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "description": "The team member's user id."
                  },
                  "notes": {
                    "type": [
                      "string",
                      "null"
                    ]
                  }
                }
              },
              "set_at": {
                "type": "string",
                "description": "UTC ISO 8601."
              }
            }
          }
        ]
      },
      "WebhookIvrKeypressData": {
        "type": "object",
        "description": "One key press on a call menu.",
        "required": [
          "call_uuid",
          "from_number",
          "to_number",
          "flow_id",
          "flow_name",
          "key_path",
          "key_label"
        ],
        "properties": {
          "call_uuid": {
            "type": "string",
            "description": "The call the key was pressed on, as the uuid on its call record. All zeros on the test sample.",
            "examples": [
              "c7d2e1f0-4a3b-4c5d-8e9f-0a1b2c3d4e5f"
            ]
          },
          "from_number": {
            "type": "string",
            "description": "The number the caller called from, as the carrier presented it. It can arrive as +91 and ten digits, 91 and ten digits, 0091 and ten digits, 0 and ten digits, or the ten digits alone (today the wire carries 91 and the ten digits): match on the last ten digits rather than on the whole string. `+919876543210` on the test sample.",
            "examples": [
              "919812345678",
              "09812345678",
              "00919812345678",
              "9812345678"
            ]
          },
          "to_number": {
            "type": "string",
            "description": "The number the caller dialled, as the carrier presented it and the edge passed it on (whitespace trimmed). Today that is 91 and the ten digits. Never match on the whole string; match on the last ten digits. On the test sample only it is the account's first number in the stored national form (0 and the ten digits), or empty when the account holds no number yet.",
            "examples": [
              "911100000042"
            ]
          },
          "flow_id": {
            "type": "integer",
            "description": "The id of the call menu.",
            "examples": [
              128
            ]
          },
          "flow_name": {
            "type": "string",
            "description": "The name of the call menu, as shown in the dashboard.",
            "examples": [
              "Main office menu"
            ]
          },
          "key_path": {
            "type": "string",
            "description": "Which key was pressed. The digit alone on the main menu; on a sub-menu the path is dash-joined from the top, so `2-1` is key 1 on the menu that key 2 opened. One event per press.",
            "examples": [
              "1",
              "2-1"
            ]
          },
          "key_label": {
            "type": "string",
            "description": "The label given to that key on the call menu. Empty when the key has no label.",
            "examples": [
              "Sales"
            ]
          }
        }
      },
      "WebhookIvrKeypressEvent": {
        "type": "object",
        "description": "The body of the call menu key press webhook. There is no `org_id`.",
        "required": [
          "id",
          "type",
          "event",
          "created",
          "timestamp_ist",
          "test",
          "data"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "`evt_` and 20 hex characters. De-duplicate on it. The Test button's sample is prefixed `evt_test_` instead.",
            "examples": [
              "evt_5b8e02d94c17a3f6e0d2"
            ]
          },
          "type": {
            "type": "string",
            "enum": [
              "ivr.keypress"
            ]
          },
          "event": {
            "type": "string",
            "enum": [
              "ivr.keypress"
            ]
          },
          "created": {
            "type": "integer",
            "description": "Unix epoch seconds, the same value as `X-Agentive-Timestamp`."
          },
          "timestamp_ist": {
            "type": "string",
            "description": "The same instant at +05:30, ISO 8601 with milliseconds.",
            "examples": [
              "2026-06-04T22:12:22.518+05:30"
            ]
          },
          "test": {
            "type": "boolean",
            "description": "`true` only on the sample the Test button sends."
          },
          "data": {
            "$ref": "#/components/schemas/WebhookIvrKeypressData"
          }
        }
      }
    }
  }
}