{"openapi":"3.1.0","info":{"title":"Vocaleo API","description":"\nGive a personal AI assistant the ability to make one phone call and read one result. You send a phone number and a task; Vocaleo places the call, and when it is over you\nread back what happened.\n\n## Getting started\n\n1. `POST /v1/accounts` with the owner's phone number, which needs no credential. Vocaleo texts a\n   one-time code to that number.\n2. `POST /v1/accounts/verify` with the number and the code. The response carries your API key\n   **once** — store it, it cannot be retrieved again. While the service-wide sign-up-credit cap\n   has room, the account opens with a credit enough for a first call; afterwards it opens at 0.\n   Read `balance_cents` before the first call. Repeating both steps later re-issues the key,\n   which is how a lost key is recovered.\n3. Send that key on every other request: `Authorization: Bearer vok_...`.\n4. `POST /v1/calls` with a number and a task. You get `202 Accepted` and a `call_id`; the call has\n   not happened yet.\n5. `GET /v1/calls/{call_id}` for the status, and once the call is over, the outcome, a summary,\n   and the transcript. Set `wait_seconds` to hold the request open rather than polling in a loop.\n\n## What a call costs\n\nMoney is in cents throughout. Placing a call holds `max_charge_cents_per_call` against your\nbalance, which is what makes the price knowable before the call is placed and stops the balance\ngoing negative. When the call ends, the time actually used is charged at `price_cents_per_minute`,\nrounded up to the next whole minute, and the rest of the hold is returned — so a 45-second call\ncosts one minute and a 61-second call costs two. `GET /v1/account` shows the balance and a payment\nlink for adding more.\n\n## Optional dedicated number\n\nAll users can request the optional dedicated number for USD 3 per month through Stripe. Call credit is separate.\nUse `POST /v1/account/number` to obtain a checkout link. A pending purchase does not activate the number.\n`GET /v1/account` reports the number, paid-through date, and active status.\nAfter paid access expires, new outbound calls use the shared number. Explain that change before the call.\nInbound answers stop until payment restores access.\nUse `/v1/account/number/portal` for payment changes or cancellation.\nCall forwarding is unavailable. The assistant handles the task or takes a message.\n`GET /v1/inbound-calls` lists return-call results and messages.\nUse `GET /v1/inbound-call-updates` with a saved cursor to poll for new completed results.\n\n## Text messages\n\nA dedicated number can also send and receive texts. `POST /v1/text-messages` sends one text from the\nactive number to a United States or Canada number and charges 3 cents, refunded if it fails. A newly\nbought number enables texts within a few minutes; a send before then is refused with `503`. There is\nno notification for an arriving text — poll `GET /v1/text-messages`, which lists both directions\nnewest first. An account may send 10 texts a day and 50 a month; there is no limit on texts received.\n\n## How responses work\n\n- Every error carries `{\"error\": {\"code\", \"message\"}}`. `code` is a fixed word to branch on;\n  `message` is a sentence written for a person.\n- A `402` refusal carries `needed_cents` and `payment_url` beside that envelope: how much more\n  credit this call needs, and where to buy it.\n- A `429` refusal carries a `Retry-After` header saying how many seconds to wait.\n\n## Limits\n\nVocaleo dials United States, Canada, and United Kingdom numbers only. North American premium-rate\nprefixes and area codes outside the United States and Canada are refused.\nThere is no limit on accounts per address. Each account may place 20 calls a minute and 120 a day, and\nany one number may be called 100 times a day; exceeding a cap returns `429 Too Many Requests`.\n","version":"1"},"paths":{"/v1/accounts":{"post":{"tags":["accounts"],"summary":"Start an account: text a one-time code to the owner's phone.","description":"Needs no credential. Vocaleo texts a code to `phone_number`. Ask the owner for that code, then send it to `POST /v1/accounts/verify` to receive the API key. The same two steps on a number that already owns an account re-issue its key and revoke the old one, which is how a lost key is recovered.","operationId":"startAccount","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PhoneNumberRequest"}}},"required":true},"responses":{"202":{"description":"A code is on its way to the number.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VerificationStarted"}}}},"default":{"description":"The request was refused. `error.code` says why.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/v1/accounts/verify":{"post":{"tags":["accounts"],"summary":"Prove the phone number with the code, and receive the API key.","description":"Needs no credential. `code` is the one `POST /v1/accounts` texted to `phone_number`. A right code creates the account and returns the API key once; store it, it cannot be retrieved again. While the service-wide sign-up-credit cap has room, the account opens with a credit enough for a first call; afterwards it opens at 0. Read `balance_cents`. If the number already owns an account, a right code re-issues that account's key and revokes the old one; the balance is kept and no second credit is granted. No limit on accounts per address.","operationId":"verifyAccount","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VerifyAccountRequest"}}},"required":true},"responses":{"201":{"description":"The account, its key, and a link for adding credit.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccountCreated"}}}},"default":{"description":"The request was refused. `error.code` says why.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/v1/account":{"get":{"tags":["accounts"],"summary":"Read the balance and payment link.","operationId":"getAccount","responses":{"200":{"description":"The balance, the payment link, and the prices this account pays.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccountDetail"}}}},"default":{"description":"The request was refused. `error.code` says why.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}},"security":[{"VocaleoApiKey":[]}]}},"/v1/account/inbound-settings":{"get":{"tags":["accounts"],"summary":"Get Inbound Settings","description":"Read the saved instructions, generated prompts, and objectives for unknown callers and the owner's registered phone.","operationId":"getInboundSettings","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InboundSettings"}}}},"default":{"description":"The request was refused. `error.code` says why.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}},"security":[{"VocaleoApiKey":[]}]}},"/v1/account/inbound-settings/{kind}":{"put":{"tags":["accounts"],"summary":"Put Inbound Instructions","description":"Set instructions for unknown callers or the owner's registered phone. Vocaleo generates the prompt before saving it. Use null to clear one rule. Owner rules take priority; other return calls retain their previous task. Caller ID does not prove identity or grant account access. AI and recording disclosure remain required. Both rules share a limit of 10 generation attempts per hour. Reads and clears remain available. Forwarding is unavailable. If the request disconnects, read get_inbound_settings before retry.","operationId":"setInboundInstructions","security":[{"VocaleoApiKey":[]}],"parameters":[{"name":"kind","in":"path","required":true,"schema":{"$ref":"#/components/schemas/InboundSettingsKind"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InboundInstructionsRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InboundSettings"}}}},"default":{"description":"The request was refused. `error.code` says why.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/v1/calls":{"post":{"tags":["calls"],"summary":"Start one call.","description":"Accepts the call only when the balance covers `max_charge_cents_per_call`, holds that amount, and dials. The call has not happened yet when this answers — poll `GET /v1/calls/{call_id}` for the result. Send an `Idempotency-Key` header to make a retry safe: the same key replays the original call instead of dialing twice, and the same key with a different body is refused with `409`.","operationId":"createCall","security":[{"VocaleoApiKey":[]}],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"description":"Unique per account. An exact replay returns the original call.","title":"Idempotency-Key"},"description":"Unique per account. An exact replay returns the original call."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateCallRequest"}}}},"responses":{"202":{"description":"The call was accepted and is being placed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CallAccepted"}}}},"default":{"description":"The request was refused. `error.code` says why.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/v1/calls/{call_id}":{"get":{"tags":["calls"],"summary":"Read status and, once the call is over, the result.","description":"Only the account that placed the call may read it; another account's call is simply not found. `wait_seconds` holds the request open, polling internally, until the call is over or the wait runs out.","operationId":"getCall","security":[{"VocaleoApiKey":[]}],"parameters":[{"name":"call_id","in":"path","required":true,"schema":{"type":"string","title":"Call Id"}},{"name":"wait_seconds","in":"query","required":false,"schema":{"type":"integer","maximum":60,"minimum":0,"description":"Seconds to hold the request open waiting for the call to finish. `0` answers immediately.","default":0,"title":"Wait Seconds"},"description":"Seconds to hold the request open waiting for the call to finish. `0` answers immediately."}],"responses":{"200":{"description":"The call, with its result once it is over.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CallDetail"}}}},"default":{"description":"The request was refused. `error.code` says why.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/v1/calls/{call_id}/recording-link":{"get":{"tags":["calls"],"summary":"Get a private recording link that expires after 15 minutes.","operationId":"getCallRecordingLink","security":[{"VocaleoApiKey":[]}],"parameters":[{"name":"call_id","in":"path","required":true,"schema":{"type":"string","title":"Call Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CallRecordingDownload"}}}},"default":{"description":"The request was refused. `error.code` says why.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/v1/calls/{call_id}/recording":{"get":{"tags":["calls"],"summary":"Download a call recording as WAV audio.","description":"Use your API key or a temporary download link. A call without a recording returns 404.","operationId":"getCallRecording","security":[{"VocaleoApiKey":[]}],"parameters":[{"name":"call_id","in":"path","required":true,"schema":{"type":"string","title":"Call Id"}},{"name":"expires","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Expires"}},{"name":"signature","in":"query","required":false,"schema":{"anyOf":[{"type":"string","pattern":"^[0-9a-f]{64}$"},{"type":"null"}],"title":"Signature"}}],"responses":{"200":{"description":"Successful Response","content":{"audio/wav":{"schema":{"type":"string","format":"binary"}}}},"default":{"description":"The request was refused. `error.code` says why.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/v1/account/number":{"post":{"tags":["accounts"],"summary":"Start Number Purchase","operationId":"start_number_purchase_v1_account_number_post","security":[{"VocaleoApiKey":[]}],"parameters":[{"name":"area_code","in":"query","required":false,"schema":{"anyOf":[{"type":"string","pattern":"^[2-9][0-9]{2}$","description":"Optional three-digit US or Canadian area code, such as 413."},{"type":"null"}],"title":"Area Code"}}],"requestBody":{"content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/NumberPurchaseRequest"},{"type":"null"}],"title":"Number Request"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NumberAddon"}}}},"default":{"description":"The request was refused. `error.code` says why.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/v1/account/number/refresh":{"post":{"tags":["accounts"],"summary":"Refresh Number","operationId":"refresh_number_v1_account_number_refresh_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NumberAddon"}}}},"default":{"description":"The request was refused. `error.code` says why.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}},"security":[{"VocaleoApiKey":[]}]}},"/v1/account/number/portal":{"post":{"tags":["accounts"],"summary":"Number Payment Portal","operationId":"number_payment_portal_v1_account_number_portal_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BillingPortal"}}}},"default":{"description":"The request was refused. `error.code` says why.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}},"security":[{"VocaleoApiKey":[]}]}},"/v1/account/number/cancel":{"post":{"tags":["accounts"],"summary":"Cancel Number","operationId":"cancel_number_v1_account_number_cancel_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NumberAddon"}}}},"default":{"description":"The request was refused. `error.code` says why.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}},"security":[{"VocaleoApiKey":[]}]}},"/v1/inbound-calls":{"get":{"tags":["calls"],"summary":"Inbound History","operationId":"inbound_history_v1_inbound_calls_get","security":[{"VocaleoApiKey":[]}],"parameters":[{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cursor"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"default":20,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InboundCallPage"}}}},"default":{"description":"The request was refused. `error.code` says why.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/v1/inbound-call-updates":{"get":{"tags":["calls"],"summary":"Inbound Updates","description":"Check for newly completed return calls. Omit cursor once to start watching from now; the first response has no historical calls. Persist next_cursor per account and monitor. Pass it on later polls. Process every page while has_more is true. Report each call once, including outcome, summary, and charge; achieved means the task succeeded. Poll every 60 seconds through the agent host scheduler while monitoring is requested. Empty results need no notification. This tool does not schedule checks or wake an idle agent. Use get_inbound_calls for history before monitoring.","operationId":"inbound_updates_v1_inbound_call_updates_get","security":[{"VocaleoApiKey":[]}],"parameters":[{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","minimum":0},{"type":"null"}],"title":"Cursor"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"default":20,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InboundCallUpdates"}}}},"default":{"description":"The request was refused. `error.code` says why.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/v1/text-messages":{"post":{"tags":["text-messages"],"summary":"Send one text from the account's dedicated number.","description":"Sends one text from the account's active dedicated number to a United States or Canada number, and charges 3 cents, refunded if the text fails. The number must have texts enabled; a newly bought number enables within a few minutes, and a send before then is refused with `503`. Refused with `409` without an active number, `402` without credit, and `429` past the limit of 10 texts a day or 50 a month.","operationId":"sendTextMessage","security":[{"VocaleoApiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SendTextMessageRequest"}}}},"responses":{"201":{"description":"The text was accepted and is being sent.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TextMessageDetail"}}}},"default":{"description":"The request was refused. `error.code` says why.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}},"get":{"tags":["text-messages"],"summary":"List texts sent and received, newest first.","description":"Lists this account's texts, both directions, newest first. There is no notification for an arriving text, so poll this route. Use `next_cursor` for the next page.","operationId":"listTextMessages","security":[{"VocaleoApiKey":[]}],"parameters":[{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cursor"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"default":20,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TextMessagePage"}}}},"default":{"description":"The request was refused. `error.code` says why.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/v1/feedback":{"post":{"tags":["feedback"],"summary":"Report an issue or suggest a feature.","description":"Records one report for the account behind the key and returns its id. Use `issue` for a problem you hit and `suggestion` for a change you would like. The report is stored for the Vocaleo team to read; it is not answered on this call.","operationId":"submitFeedback","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FeedbackRequest"}}},"required":true},"responses":{"201":{"description":"The report was recorded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FeedbackReceived"}}}},"default":{"description":"The request was refused. `error.code` says why.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}},"security":[{"VocaleoApiKey":[]}]}},"/v1/account/close":{"post":{"tags":["accounts"],"summary":"Close Current Account","description":"Permanently close the account only after the owner explicitly confirms. Require a fresh code sent to the registered phone. Account access and subscription renewal stop. Eligible live personal data is deleted automatically. The balance, financial records, dedicated number, and protected phone identifier remain. No automatic refund or undo. The same phone cannot register for 30 days and receives no further signup credit. Backups expire separately.","operationId":"close_current_account_v1_account_close_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CloseAccountRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccountClosureReceipt"}}}},"default":{"description":"The request was refused. `error.code` says why.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}},"security":[{"VocaleoApiKey":[]}]}},"/v1/account/close/verification":{"post":{"tags":["accounts"],"summary":"Request Closure Code","operationId":"request_closure_code_v1_account_close_verification_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VerificationStarted"}}}},"default":{"description":"The request was refused. `error.code` says why.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}},"security":[{"VocaleoApiKey":[]}]}}},"components":{"schemas":{"AccountClosureReceipt":{"properties":{"account_id":{"type":"string","minLength":1,"title":"Account Id"},"erasure_status":{"$ref":"#/components/schemas/ErasureStatus","description":"Status of eligible live data removal. Financial records and other data retained under the disclosed policy are outside this status."}},"type":"object","required":["account_id","erasure_status"],"title":"AccountClosureReceipt"},"AccountCreated":{"properties":{"account_id":{"type":"string","title":"Account Id"},"api_key":{"type":"string","title":"Api Key","description":"The bearer token for every other request. Shown once, here, and never again.","examples":["vok_0123abcd..."]},"balance_cents":{"type":"integer","title":"Balance Cents","description":"The opening balance: the sign-up credit while the service-wide cap has room, otherwise 0."},"payment_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Payment Url","description":"Where the account's owner buys more credit. Absent while no payment link is provisioned."},"price_cents_per_minute":{"type":"integer","title":"Price Cents Per Minute","description":"What one minute costs. Call time is rounded up to the next whole minute before charging, so a 45-second call is billed as one minute and a 61-second call as two."},"max_charge_cents_per_call":{"type":"integer","title":"Max Charge Cents Per Call","description":"The most one call can cost. This whole amount is held when a call is accepted, and the unused part is returned when it ends."},"pro_price_cents_per_minute":{"type":"integer","title":"Pro Price Cents Per Minute","description":"What one minute of a Pro call costs. Pro mode places the call through a premium voice provider; set pro_mode=true on a call to use it. Part-minutes are rounded up before charging, like the standard rate."},"pro_max_charge_cents_per_call":{"type":"integer","title":"Pro Max Charge Cents Per Call","description":"The most one Pro call can cost, and the amount held when a Pro call is accepted. It is larger than the standard hold; the unused part is returned when the call ends. The sign-up credit does not cover it."}},"type":"object","required":["account_id","api_key","balance_cents","payment_url","price_cents_per_minute","max_charge_cents_per_call","pro_price_cents_per_minute","pro_max_charge_cents_per_call"],"title":"AccountCreated"},"AccountDetail":{"properties":{"number_addon":{"$ref":"#/components/schemas/NumberAddon"},"account_id":{"type":"string","title":"Account Id"},"balance_cents":{"type":"integer","title":"Balance Cents","description":"Credit available right now. Money held against calls in progress is already subtracted."},"payment_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Payment Url","description":"Where the account's owner buys more credit. Absent while no payment link is provisioned."},"price_cents_per_minute":{"type":"integer","title":"Price Cents Per Minute","description":"What one minute costs. Call time is rounded up to the next whole minute before charging, so a 45-second call is billed as one minute and a 61-second call as two."},"max_charge_cents_per_call":{"type":"integer","title":"Max Charge Cents Per Call","description":"The most one call can cost. This whole amount is held when a call is accepted, and the unused part is returned when it ends."},"pro_price_cents_per_minute":{"type":"integer","title":"Pro Price Cents Per Minute","description":"What one minute of a Pro call costs. Pro mode places the call through a premium voice provider; set pro_mode=true on a call to use it. Part-minutes are rounded up before charging, like the standard rate."},"pro_max_charge_cents_per_call":{"type":"integer","title":"Pro Max Charge Cents Per Call","description":"The most one Pro call can cost, and the amount held when a Pro call is accepted. It is larger than the standard hold; the unused part is returned when the call ends. The sign-up credit does not cover it."}},"type":"object","required":["account_id","balance_cents","payment_url","price_cents_per_minute","max_charge_cents_per_call","pro_price_cents_per_minute","pro_max_charge_cents_per_call"],"title":"AccountDetail"},"BillingPortal":{"properties":{"portal_url":{"type":"string","title":"Portal Url"}},"type":"object","required":["portal_url"],"title":"BillingPortal"},"CallAccepted":{"properties":{"call_id":{"type":"string","title":"Call Id","description":"Read the call back with `GET /v1/calls/{call_id}`."},"status":{"$ref":"#/components/schemas/PublicCallStatus"},"from_phone_number":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"From Phone Number"},"dedicated_number_active":{"type":"boolean","title":"Dedicated Number Active","default":false},"held_cents":{"type":"integer","title":"Held Cents","description":"Credit currently held for this call. Zero after settlement, including a full refund."},"allow_user_join":{"type":"boolean","title":"Allow User Join","description":"Allow the agent to call the account owner at their verified number when needed. Enable only after an explicit user request for this call. Unavailable in Pro mode.","default":false},"pro_mode":{"type":"boolean","title":"Pro Mode","description":"Whether this call was placed in Pro mode.","default":false}},"type":"object","required":["call_id","status","held_cents"],"title":"CallAccepted"},"CallDetail":{"properties":{"call_id":{"type":"string","title":"Call Id"},"direction":{"$ref":"#/components/schemas/CallDirection","default":"outbound"},"status":{"$ref":"#/components/schemas/PublicCallStatus"},"to_phone_number":{"type":"string","title":"To Phone Number"},"task":{"type":"string","title":"Task"},"on_behalf_of":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"On Behalf Of"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"started_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Started At"},"ended_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Ended At"},"outcome":{"anyOf":[{"$ref":"#/components/schemas/CallOutcome"},{"type":"null"}],"description":"Whether the task was accomplished, judged from the transcript."},"summary":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Summary","description":"What happened on the call, in a few sentences."},"transcript":{"anyOf":[{"items":{"$ref":"#/components/schemas/TranscriptTurn"},"type":"array"},{"type":"null"}],"title":"Transcript","description":"Who said what, agent and person only."},"failure":{"anyOf":[{"$ref":"#/components/schemas/CallFailure"},{"type":"null"}],"description":"Why the call ended in `error`, when it did."},"allow_user_join":{"type":"boolean","title":"Allow User Join","description":"Allow the agent to call the account owner at their verified number when needed. Enable only after an explicit user request for this call. Unavailable in Pro mode.","default":false},"pro_mode":{"type":"boolean","title":"Pro Mode","description":"Whether this call was placed in Pro mode.","default":false},"held_cents":{"type":"integer","title":"Held Cents","description":"Credit currently held for this call. Zero after settlement, including a full refund."},"charged_cents":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Charged Cents","description":"What the call actually cost. Any unused credit was returned to the balance at settlement."},"balance_cents":{"type":"integer","title":"Balance Cents","description":"The account's current available balance. Active call holds are already subtracted."}},"type":"object","required":["call_id","status","to_phone_number","task","on_behalf_of","created_at","started_at","ended_at","outcome","summary","transcript","failure","held_cents","charged_cents","balance_cents"],"title":"CallDetail","description":"One call. `outcome`, `summary`, `transcript`, `failure`, and `charged_cents` stay null until\nthe call is over — that is, until `status` is `completed` or `error`."},"CallDirection":{"type":"string","enum":["inbound","outbound"],"title":"CallDirection"},"CallFailure":{"properties":{"code":{"$ref":"#/components/schemas/FailureCode"}},"type":"object","required":["code"],"title":"CallFailure"},"CallOutcome":{"type":"string","enum":["achieved","partial","not_achieved","unclear"],"title":"CallOutcome","description":"The analysis verdict, produced by the platform-owned Vocalis analysis schema."},"CallRecordingDownload":{"properties":{"download_url":{"type":"string","title":"Download Url","description":"A private download link. Anyone with this link can fetch this recording until expires_at."},"expires_at":{"type":"string","format":"date-time","title":"Expires At","description":"The time when the download link expires, in UTC."}},"type":"object","required":["download_url","expires_at"],"title":"CallRecordingDownload"},"CloseAccountRequest":{"properties":{"code":{"type":"string","pattern":"^[0-9]{4,8}$","title":"Code","description":"The fresh code sent to the registered phone."},"confirmation":{"type":"boolean","title":"Confirmation","description":"True only after the customer explicitly confirms permanent account closure."}},"additionalProperties":false,"type":"object","required":["code","confirmation"],"title":"CloseAccountRequest"},"CreateCallRequest":{"properties":{"to_phone_number":{"type":"string","pattern":"^\\+[1-9]\\d{1,14}$","title":"To Phone Number","description":"The number to call, in E.164 form. United States, Canada, and United Kingdom only.","examples":["+14155550123"]},"task":{"type":"string","maxLength":10000,"minLength":1,"title":"Task","description":"What the agent should accomplish on the call, in plain language.","examples":["Call Lume and book a table for 2 tomorrow at 7pm under Alex Rivera. Accept 6:30-8:00 as fallback."]},"on_behalf_of":{"anyOf":[{"type":"string","maxLength":120,"pattern":"^[^\\x00-\\x1f\\x7f]+$"},{"type":"null"}],"title":"On Behalf Of","description":"Who the agent says it is calling for. One line of ordinary text.","examples":["Alex Rivera"]},"allow_user_join":{"type":"boolean","title":"Allow User Join","description":"Allow the agent to call the account owner at their verified number when needed. Enable only after an explicit user request for this call. Unavailable in Pro mode.","default":false},"pro_mode":{"type":"boolean","title":"Pro Mode","description":"Place this call in Pro mode, through the premium voice provider at the account's Pro rate and reserve. False or omitted uses the standard provider. Defaults to false.","default":false}},"type":"object","required":["to_phone_number","task"],"title":"CreateCallRequest"},"ErasureStatus":{"type":"string","enum":["pending","complete"],"title":"ErasureStatus"},"ErrorBody":{"properties":{"code":{"$ref":"#/components/schemas/ErrorCode"},"message":{"type":"string","title":"Message"}},"type":"object","required":["code","message"],"title":"ErrorBody"},"ErrorCode":{"type":"string","enum":["unauthorized","not_found","invalid_request","task_rejected","insufficient_credit","rate_limited","conflict","temporarily_unavailable","executor_error"],"title":"ErrorCode"},"ErrorEnvelope":{"properties":{"error":{"$ref":"#/components/schemas/ErrorBody"}},"type":"object","required":["error"],"title":"ErrorEnvelope"},"FailureCode":{"type":"string","enum":["no_answer","busy","error"],"title":"FailureCode","description":"Why a call ended in ``error``."},"FeedbackKind":{"type":"string","enum":["issue","suggestion"],"title":"FeedbackKind","description":"What one :class:`Feedback` row reports. The two things the endpoint accepts, and no third:\na problem the agent hit, or a change it would like."},"FeedbackReceived":{"properties":{"feedback_id":{"type":"string","title":"Feedback Id","description":"The stored report's identifier."},"kind":{"$ref":"#/components/schemas/FeedbackKind"},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"When Vocaleo recorded the report, in UTC."}},"type":"object","required":["feedback_id","kind","created_at"],"title":"FeedbackReceived"},"FeedbackRequest":{"properties":{"kind":{"$ref":"#/components/schemas/FeedbackKind","description":"Whether this reports a problem the agent hit (`issue`) or a change it would like (`suggestion`).","examples":["issue"]},"message":{"type":"string","maxLength":10000,"minLength":1,"title":"Message","description":"The report in plain language: what happened, or what to add.","examples":["get_call sometimes stays `queued` for minutes after the call is clearly over."]}},"type":"object","required":["kind","message"],"title":"FeedbackRequest"},"InboundCallPage":{"properties":{"calls":{"items":{"$ref":"#/components/schemas/CallDetail"},"type":"array","title":"Calls"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor"}},"type":"object","required":["calls"],"title":"InboundCallPage"},"InboundCallUpdates":{"properties":{"calls":{"items":{"$ref":"#/components/schemas/CallDetail"},"type":"array","title":"Calls"},"next_cursor":{"type":"integer","minimum":0.0,"title":"Next Cursor","description":"Save after processing this page; pass as cursor on the next poll."},"has_more":{"type":"boolean","title":"Has More","default":false}},"type":"object","required":["calls","next_cursor"],"title":"InboundCallUpdates"},"InboundInstructionsRequest":{"properties":{"instructions":{"anyOf":[{"type":"string","maxLength":10000,"minLength":1},{"type":"null"}],"title":"Instructions"}},"type":"object","required":["instructions"],"title":"InboundInstructionsRequest"},"InboundPromptSettings":{"properties":{"instructions":{"type":"string","title":"Instructions"},"system_prompt":{"type":"string","title":"System Prompt"},"objective":{"type":"string","title":"Objective"}},"type":"object","required":["instructions","system_prompt","objective"],"title":"InboundPromptSettings"},"InboundSettings":{"properties":{"unknown":{"anyOf":[{"$ref":"#/components/schemas/InboundPromptSettings"},{"type":"null"}]},"owner":{"anyOf":[{"$ref":"#/components/schemas/InboundPromptSettings"},{"type":"null"}]}},"type":"object","title":"InboundSettings"},"InboundSettingsKind":{"type":"string","enum":["unknown","owner"],"title":"InboundSettingsKind"},"NumberAddon":{"properties":{"phone_number":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Phone Number"},"status":{"anyOf":[{"$ref":"#/components/schemas/NumberPurchaseStatus"},{"type":"null"}]},"active":{"type":"boolean","title":"Active","default":false},"paid_until":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Paid Until"},"monthly_price_cents":{"type":"integer","title":"Monthly Price Cents","default":300},"checkout_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Checkout Url"},"country_code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Country Code"}},"type":"object","title":"NumberAddon"},"NumberPurchaseRequest":{"properties":{"area_code":{"anyOf":[{"type":"string","pattern":"^[2-9][0-9]{2}$","description":"Optional three-digit US or Canadian area code, such as 413."},{"type":"null"}],"title":"Area Code"}},"additionalProperties":false,"type":"object","title":"NumberPurchaseRequest"},"NumberPurchaseStatus":{"type":"string","enum":["checkout","ordering","ready","refunding","failed"],"title":"NumberPurchaseStatus"},"PhoneNumberRequest":{"properties":{"phone_number":{"type":"string","pattern":"^\\+[1-9]\\d{1,14}$","title":"Phone Number","description":"The owner's phone number in E.164 form. Vocaleo texts a one-time code to it. This is the account's recovery handle, and one number may own only one account.","examples":["+16175550134"]}},"type":"object","required":["phone_number"],"title":"PhoneNumberRequest"},"PublicCallStatus":{"type":"string","enum":["queued","in_progress","completed","error"],"title":"PublicCallStatus","description":"What a customer sees. One fewer state than :class:`CallStatus`."},"SendTextMessageRequest":{"properties":{"to_phone_number":{"type":"string","pattern":"^\\+[1-9]\\d{1,14}$","title":"To Phone Number","description":"The number to text, in E.164 form. United States and Canada only.","examples":["+14155550123"]},"text":{"type":"string","maxLength":450,"minLength":1,"title":"Text","description":"The message, 1 to 450 characters.","examples":["Your table for 2 is confirmed for 7pm. Reply if you need to change it."]}},"type":"object","required":["to_phone_number","text"],"title":"SendTextMessageRequest"},"TextMessageDetail":{"properties":{"text_message_id":{"type":"string","title":"Text Message Id"},"direction":{"$ref":"#/components/schemas/TextMessageDirection"},"from_phone_number":{"type":"string","title":"From Phone Number"},"to_phone_number":{"type":"string","title":"To Phone Number"},"text":{"type":"string","title":"Text"},"status":{"$ref":"#/components/schemas/TextMessageStatus"},"failure_code":{"anyOf":[{"$ref":"#/components/schemas/TextMessageFailureCode"},{"type":"null"}],"description":"Why an outbound text ended in `failed`, when it did."},"charged_cents":{"type":"integer","title":"Charged Cents","description":"What this text cost. Zero for an inbound text and for a failed, refunded outbound text."},"created_at":{"type":"string","format":"date-time","title":"Created At"}},"type":"object","required":["text_message_id","direction","from_phone_number","to_phone_number","text","status","failure_code","charged_cents","created_at"],"title":"TextMessageDetail","description":"One text this account sent or received on its dedicated number. `failure_code` is set only on a\n`failed` outbound text; `charged_cents` is 3 while an outbound text stands and 0 once it fails or is\nrefunded, and always 0 for an inbound text."},"TextMessageDirection":{"type":"string","enum":["inbound","outbound"],"title":"TextMessageDirection","description":"Which way one text moved: out to a recipient, or in from one."},"TextMessageFailureCode":{"type":"string","enum":["recipient_opted_out","destination_invalid","destination_unreachable","sender_not_registered","carrier_rejected","unconfirmed"],"title":"TextMessageFailureCode","description":"Why an outbound text ended in ``failed``. The carrier codes map onto these, same names as\nResia; ``unconfirmed`` is the gateway's own marker for a send whose result it never learned."},"TextMessagePage":{"properties":{"text_messages":{"items":{"$ref":"#/components/schemas/TextMessageDetail"},"type":"array","title":"Text Messages"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor","description":"Pass back as `cursor` for the next page; null on the last page."}},"type":"object","required":["text_messages"],"title":"TextMessagePage"},"TextMessageStatus":{"type":"string","enum":["queued","sent","delivered","failed","received"],"title":"TextMessageStatus","description":"The stored lifecycle of one text. ``received`` is the only state an inbound text ever has;\nthe other four belong to an outbound text as it is charged, sent, and finally confirmed."},"TranscriptRole":{"type":"string","enum":["assistant","user"],"title":"TranscriptRole","description":"Who said one line of a published transcript. Two speakers, and no third.\n\nThe pipeline's own vocabulary is wider — system framing, pipeline events — and none of it is\npart of the conversation. Naming the two that are makes a stored transcript carrying anything\nelse fail to validate rather than reach a customer."},"TranscriptTurn":{"properties":{"role":{"$ref":"#/components/schemas/TranscriptRole"},"content":{"type":"string","title":"Content"}},"type":"object","required":["role","content"],"title":"TranscriptTurn"},"VerificationStarted":{"properties":{"phone_number":{"type":"string","title":"Phone Number","description":"The number the code was texted to."},"expires_in_seconds":{"type":"integer","title":"Expires In Seconds","description":"How long the code stays valid. Ask the owner for it, then send it to `POST /v1/accounts/verify`."}},"type":"object","required":["phone_number","expires_in_seconds"],"title":"VerificationStarted"},"VerifyAccountRequest":{"properties":{"phone_number":{"type":"string","pattern":"^\\+[1-9]\\d{1,14}$","title":"Phone Number","description":"The owner's phone number in E.164 form. Vocaleo texts a one-time code to it. This is the account's recovery handle, and one number may own only one account.","examples":["+16175550134"]},"code":{"type":"string","pattern":"^\\d{4,8}$","title":"Code","description":"The code Vocaleo texted to `phone_number`.","examples":["424242"]},"email":{"anyOf":[{"type":"string","maxLength":254,"pattern":"^[^@\\s]+@[^@\\s]+\\.[^@\\s]+$"},{"type":"null"}],"title":"Email","description":"An optional second way to reach the owner.","examples":["alex@example.com"]},"name":{"anyOf":[{"type":"string","maxLength":120},{"type":"null"}],"title":"Name","description":"An optional label for the account.","examples":["Alex's assistant"]}},"type":"object","required":["phone_number","code"],"title":"VerifyAccountRequest"}},"securitySchemes":{"VocaleoApiKey":{"type":"http","description":"The API key `POST /v1/accounts/verify` returned, sent as a bearer token.","scheme":"bearer","bearerFormat":"vok_ followed by 48 hexadecimal characters"}}},"tags":[{"name":"accounts","description":"Create an account and read its balance."},{"name":"calls","description":"Place one call and read its result."},{"name":"text-messages","description":"Send a text from a dedicated number and read the thread."},{"name":"feedback","description":"Report an issue or suggest a feature."}]}