{"openapi":"3.0.0","info":{"title":"Really Agent Number","version":"1.0.0","description":"Sandbox: persistent SMS phone numbers for AI agents.\n\nImportant: Your wallet address is your permanent account identity for this service. If you lose access to the wallet that paid for the subscription, you cannot recover your phone number or your remaining subscription time. Do not use ephemeral or per-session wallets. Recovery via signature requires the original wallet's private key.","contact":{"email":"support@really.com"},"x-service-info":{"categories":["communication","developer-tools","agentic-payments"],"documentation":"https://sandbox.agentnumber.really.com/sms/llms.txt"}},"servers":[{"url":"https://sandbox.agentnumber.really.com","description":"Sandbox"}],"tags":[{"name":"Subscriptions","description":"Acquire and renew a persistent US mobile number tied to your wallet."},{"name":"Auth","description":"Wallet-signature recovery for lost bearer tokens. No payment required when an active subscription exists."},{"name":"Discovery","description":"Free, no-auth endpoints for evaluating supply and scaffolding integrations before paying."},{"name":"SMS","description":"Send and receive SMS on your assigned number."},{"name":"Voice","description":"Place outbound calls and receive incoming-call signals over WebSocket."}],"paths":{"/sms/subscribe":{"post":{"tags":["Subscriptions"],"summary":"Subscribe to a persistent phone number","description":"Pay $1000/year via x402 (USDC on Base) or MPP to acquire a persistent US phone number bound to your wallet address. Re-paying with the same wallet renews and keeps the same number — the response will set `renewed: true`. \n\nA bare `POST /subscribe` (no payment header) returns `HTTP 402` with a payment challenge in the body listing every accepted scheme + asset for the current environment. **Always read the challenge — don't hardcode amounts or addresses** (sandbox uses Base-Sepolia testnet USDC; production uses Base mainnet USDC).\n\nImportant: Your wallet address is your permanent account identity for this service. If you lose access to the wallet that paid for the subscription, you cannot recover your phone number or your remaining subscription time. Do not use ephemeral or per-session wallets. Recovery via signature requires the original wallet's private key.","x-payment-info":{"amount":"1000000000","currency":"0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913","description":"One persistent US mobile number, 1-year subscription (SMS + voice).","intent":"charge","method":"x402","network":"base-sepolia"},"parameters":[{"name":"X-PAYMENT","in":"header","required":false,"description":"Base64-encoded x402 EIP-3009 transferWithAuthorization signature. Present after the agent has parsed the 402 challenge and signed.","schema":{"type":"string"}},{"name":"Authorization","in":"header","required":false,"description":"MPP path: `Payment <base64-credential>` carrying a Tempo TIP-20 charge. Mutually exclusive with `X-PAYMENT`.","schema":{"type":"string"}}],"responses":{"200":{"description":"Subscription confirmed (new or renewed).","content":{"application/json":{"schema":{"type":"object","required":["token","number","expires_at","renewed"],"properties":{"token":{"type":"string","description":"Bearer token for `/sms/send`, `/sms/receive`, `/sms/messages`, `/voice/dial`, and presence/stream WebSockets. Treat as a secret.","example":"an_123xyz123xyz123xyz123xyz123xyz"},"number":{"type":"string","description":"Assigned E.164 phone number.","example":"+15555550123"},"expires_at":{"type":"string","format":"date-time","description":"RFC3339 UTC timestamp when the subscription expires.","example":"2027-04-30T18:24:01.000Z"},"renewed":{"type":"boolean","description":"`true` if the same wallet had an existing subscription that was extended; `false` if a fresh number was provisioned.","example":false}}},"example":{"token":"an_123xyz123xyz123xyz123xyz123xyz","number":"+15555550123","expires_at":"2027-04-30T18:24:01.000Z","renewed":false}}}},"402":{"description":"Payment required. Body lists every accepted scheme + asset for the current environment. Sign per `accepts[]` and retry with `X-PAYMENT` or `Authorization: Payment`.","content":{"application/json":{"schema":{"type":"object","properties":{"x402Version":{"type":"integer"},"error":{"type":"string"},"accepts":{"type":"array","items":{"type":"object","properties":{"scheme":{"type":"string","example":"exact"},"network":{"type":"string","example":"base"},"amount":{"type":"string","example":"1000000000"},"asset":{"type":"string","example":"0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"},"payTo":{"type":"string","example":"0xEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE"},"maxTimeoutSeconds":{"type":"integer","example":300}}}}}},"example":{"x402Version":2,"error":"X-PAYMENT header is required","accepts":[{"scheme":"exact","network":"base","amount":"1000000000","asset":"0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913","payTo":"0xEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE","maxTimeoutSeconds":300,"extra":{"name":"USDC","version":"2"}}]}}}},"503":{"description":"Number pool exhausted. Check `/sms/availability` and join `/sms/waitlist`.","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Stable error code, safe to switch on programmatically."},"message":{"type":"string","description":"Human-readable diagnostic. Free-form, do not parse."}}},"example":{"error":"pool_exhausted","message":"No phone numbers currently available.","check_availability":"/sms/availability","join_waitlist":"/sms/waitlist","available":0}}}}}}},"/sms/auth/nonce":{"post":{"tags":["Auth"],"summary":"Issue a single-use nonce for wallet-signature recovery","description":"Step 1 of the recovery flow. Returns a fresh, single-use nonce with a 5-minute TTL. Sign `Sign in to really.com SMS\\nNonce: <nonce>` with the wallet that owns the subscription, then POST the signature to `/sms/auth`. Public — no bearer required.","requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"address":{"type":"string","description":"Optional. The wallet you intend to authenticate as. Server may use it to scope rate limits but does not require it.","example":"0xEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE"}}},"example":{"address":"0xEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE"}}}},"responses":{"200":{"description":"Nonce issued.","content":{"application/json":{"schema":{"type":"object","required":["nonce","expires_in"],"properties":{"nonce":{"type":"string","description":"Single-use random string. Sign exactly `Sign in to really.com SMS\\nNonce: <this value>`.","example":"n_abcdef0123456789abcdef0123456789"},"expires_in":{"type":"integer","description":"Seconds until the nonce is rejected (always 300).","example":300}}},"example":{"nonce":"n_abcdef0123456789abcdef0123456789","expires_in":300}}}}}}},"/sms/auth":{"post":{"tags":["Auth"],"summary":"Recover subscription access via wallet signature","description":"Step 2 of the recovery flow. Verify ownership of the wallet that paid for the subscription by submitting the signed nonce. On success, **revokes all prior tokens for the wallet** (every active agent on this subscription will need to re-auth) and issues a fresh one. The wallet doesn't need to be online or hold gas — signature verification is offline (EIP-191 / secp256k1 / keccak).\n\nImportant: Your wallet address is your permanent account identity for this service. If you lose access to the wallet that paid for the subscription, you cannot recover your phone number or your remaining subscription time. Do not use ephemeral or per-session wallets. Recovery via signature requires the original wallet's private key.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["wallet","nonce","signature"],"properties":{"wallet":{"type":"string","description":"0x-prefixed Ethereum-style address.","example":"0xEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE"},"nonce":{"type":"string","description":"Nonce previously issued by `/sms/auth/nonce`.","example":"n_abcdef0123456789abcdef0123456789"},"signature":{"type":"string","description":"0x-prefixed 65-byte EIP-191 personal_sign output of `Sign in to really.com SMS\\nNonce: <nonce>`.","example":"0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00"}}},"example":{"wallet":"0xEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE","nonce":"n_abcdef0123456789abcdef0123456789","signature":"0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00"}}}},"responses":{"200":{"description":"Recovery successful. Token rotated, fresh bearer issued.","content":{"application/json":{"schema":{"type":"object","required":["token","number","expires_at"],"properties":{"token":{"type":"string","example":"an_123xyz123xyz123xyz123xyz123xyz"},"number":{"type":"string","example":"+15555550123"},"expires_at":{"type":"string","format":"date-time","example":"2027-04-30T18:24:01.000Z"}}},"example":{"token":"an_123xyz123xyz123xyz123xyz123xyz","number":"+15555550123","expires_at":"2027-04-30T18:24:01.000Z"}}}},"400":{"description":"Request body is malformed (missing wallet, nonce, or signature).","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Stable error code, safe to switch on programmatically."},"message":{"type":"string","description":"Human-readable diagnostic. Free-form, do not parse."}}},"example":{"error":"invalid_request","message":"missing required fields: wallet, nonce, signature"}}}},"401":{"description":"Nonce was unknown / already consumed / expired, OR the signature did not recover the claimed wallet.","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Stable error code, safe to switch on programmatically."},"message":{"type":"string","description":"Human-readable diagnostic. Free-form, do not parse."}}},"example":{"error":"signature_mismatch","message":"signature did not recover the claimed wallet"}}}},"402":{"description":"No active subscription found for this wallet. Subscribe first via `/sms/subscribe`.","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Stable error code, safe to switch on programmatically."},"message":{"type":"string","description":"Human-readable diagnostic. Free-form, do not parse."}}},"example":{"error":"no_active_subscription","warning":"Your wallet address is your account identity. If you lose access to this wallet, you cannot recover your subscription. Do not use ephemeral wallets."}}}}}}},"/sms/availability":{"get":{"tags":["Discovery"],"summary":"Check current number-pool availability","description":"Returns the count of phone numbers currently free for new subscriptions. Free, no auth required. Does NOT expose total fleet size or occupied count. If `available` is `0`, join the `/sms/waitlist` for a notification when the next pool opens.","responses":{"200":{"description":"Available count.","content":{"application/json":{"schema":{"type":"object","required":["available"],"properties":{"available":{"type":"integer","minimum":0,"description":"Number of phone slots currently unassigned. Updated in real time.","example":12}}},"example":{"available":12}}}}}}},"/sms/waitlist":{"post":{"tags":["Discovery"],"summary":"Join the waitlist for the next number-pool release","description":"Numbers release in waves. Submit an email to be notified when the next pool opens. Idempotent — duplicate submissions return the original `registered_at` timestamp. Free, no auth required.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["email"],"properties":{"email":{"type":"string","format":"email","description":"Contact email for waitlist notifications.","example":"you@example.com"}}},"example":{"email":"you@example.com"}}}},"responses":{"200":{"description":"Email registered (or already on the waitlist — idempotent).","content":{"application/json":{"schema":{"type":"object","required":["status","registered_at"],"properties":{"status":{"type":"string","enum":["added"]},"registered_at":{"type":"string","format":"date-time","description":"RFC3339 timestamp of the original registration.","example":"2026-04-30T18:24:01.000Z"}}},"example":{"status":"added","registered_at":"2026-04-30T18:24:01.000Z"}}}},"400":{"description":"Email missing or malformed.","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Stable error code, safe to switch on programmatically."},"message":{"type":"string","description":"Human-readable diagnostic. Free-form, do not parse."}}},"example":{"error":"invalid_email"}}}}}}},"/sms/demo":{"post":{"tags":["Discovery"],"summary":"Simulated end-to-end flow (no payment, no real number)","description":"Returns canned responses matching the **exact field shapes** of `/sms/subscribe`, `/sms/send`, and `/sms/receive`. Use it to scaffold and test your integration before subscribing — if your code parses the demo response correctly, it will parse the real one. Free, no auth required, no DB writes.","responses":{"200":{"description":"Simulated subscribe + send + receive flow.","content":{"application/json":{"schema":{"type":"object","properties":{"demo":{"type":"boolean","enum":[true]},"simulated_subscribe":{"type":"object","properties":{"token":{"type":"string"},"number":{"type":"string"},"expires_at":{"type":"string","format":"date-time"},"renewed":{"type":"boolean"}}},"simulated_send":{"type":"object","properties":{"message_id":{"type":"string"},"status":{"type":"string"}}},"simulated_receive":{"type":"object","properties":{"messages":{"type":"array","items":{"type":"object"}},"count":{"type":"integer"}}},"note":{"type":"string"}}},"example":{"demo":true,"simulated_subscribe":{"token":"demo-token-not-real","number":"+15550000000","expires_at":"2027-04-30T18:24:01.000Z","renewed":false},"simulated_send":{"message_id":"demo_msg_001","status":"queued"},"simulated_receive":{"messages":[{"id":1,"from_number":"+15555550199","to_number":"+15550000000","body":"123456","message_id":"demo_inbound_001","created_at":"2026-04-30T18:24:01.000Z"}],"count":1},"note":"This is a simulated demo. Every field shape matches the real endpoints — use it to scaffold your integration, then subscribe for a real persistent number."}}}}}}},"/sms/number":{"get":{"tags":["Subscriptions"],"summary":"Look up your assigned number and subscription details","description":"Returns the phone number and subscription metadata for the wallet behind the bearer token. Useful for confirming which number a token is bound to (e.g. after a subscription renewal or token rotation).","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Subscription details.","content":{"application/json":{"schema":{"type":"object","required":["number","status","subscription"],"properties":{"number":{"type":"string","description":"E.164 phone number bound to the bearer.","example":"+15555550123"},"status":{"type":"string","enum":["active","expired"],"description":"Whether the subscription is currently usable."},"subscription":{"type":"object","properties":{"plan":{"type":"string","example":"yearly"},"created_at":{"type":"string","format":"date-time","example":"2026-04-30T18:24:01.000Z"},"expires_at":{"type":"string","format":"date-time","example":"2027-04-30T18:24:01.000Z"},"payer_address":{"type":"string","example":"0xEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE"}}}}},"example":{"number":"+15555550123","status":"active","subscription":{"plan":"yearly","created_at":"2026-04-30T18:24:01.000Z","expires_at":"2027-04-30T18:24:01.000Z","payer_address":"0xEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE"}}}}},"401":{"description":"Missing, invalid, revoked, or expired bearer token. Recover via `/sms/auth/nonce` + `/sms/auth`.","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Stable error code, safe to switch on programmatically."},"message":{"type":"string","description":"Human-readable diagnostic. Free-form, do not parse."}}},"example":{"error":"unauthorized","message":"invalid, revoked, or expired token"}}}}}}},"/sms/send":{"post":{"tags":["SMS"],"summary":"Send an SMS from your assigned number","description":"Outbound SMS dispatched from the number bound to the bearer token. Returns the carrier `message_id` once the message is queued for delivery. Long messages are concatenated client-side — there is no built-in segmentation.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["to","body"],"properties":{"to":{"type":"string","description":"Destination phone number in E.164 format (`+1...`).","example":"+15555550199"},"body":{"type":"string","description":"Message text. UTF-8.","example":"Hello from my agent."}}},"example":{"to":"+15555550199","body":"Hello from my agent."}}}},"responses":{"200":{"description":"Message queued for delivery.","content":{"application/json":{"schema":{"type":"object","required":["message_id","status"],"properties":{"message_id":{"type":"string","description":"Carrier-assigned message id. Use it to correlate with `/sms/messages`.","example":"msg_example_0000000000000000000000"},"status":{"type":"string","description":"Delivery status at queue time. Typically `queued`.","example":"queued"}}},"example":{"message_id":"msg_example_0000000000000000000000","status":"queued"}}}},"400":{"description":"Body missing `to` or `body` field.","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Stable error code, safe to switch on programmatically."},"message":{"type":"string","description":"Human-readable diagnostic. Free-form, do not parse."}}},"example":{"error":"invalid_request","message":"missing required fields: to, body"}}}},"401":{"description":"Missing, invalid, revoked, or expired bearer token.","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Stable error code, safe to switch on programmatically."},"message":{"type":"string","description":"Human-readable diagnostic. Free-form, do not parse."}}},"example":{"error":"unauthorized","message":"invalid, revoked, or expired token"}}}},"502":{"description":"Upstream phone-node returned an error. Usually transient — retry.","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Stable error code, safe to switch on programmatically."},"message":{"type":"string","description":"Human-readable diagnostic. Free-form, do not parse."}}},"example":{"error":"phone_node_error","message":"Mac returned 503"}}}}}}},"/sms/receive":{"get":{"tags":["SMS"],"summary":"Claim undelivered inbound SMS (long-poll-friendly)","description":"Returns inbound messages that haven't been claimed yet, atomically marking them delivered so a parallel call doesn't double-deliver. Returns immediately with whatever is currently undelivered (may be `count: 0`). For OTP flows, prefer the MCP `receive_sms` tool — it long-polls with progress notifications and sub-2-second latency. For history, use `/sms/messages` (does not mutate `delivered_at`).","security":[{"bearerAuth":[]}],"parameters":[{"name":"limit","in":"query","required":false,"description":"Max messages to return. Defaults to 20, capped at 100.","schema":{"type":"integer","minimum":1,"maximum":100,"default":20}}],"responses":{"200":{"description":"Undelivered messages claimed for this caller.","content":{"application/json":{"schema":{"type":"object","required":["messages","count"],"properties":{"messages":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer","example":4821},"from_number":{"type":"string","example":"+15555550199"},"to_number":{"type":"string","example":"+15555550123"},"body":{"type":"string","example":"Your code is 123456"},"message_id":{"type":"string","nullable":true,"example":"msg_example_0000000000000000000000"},"created_at":{"type":"string","format":"date-time","example":"2026-04-30T18:24:01.000Z"},"received_at":{"type":"string","format":"date-time","nullable":true,"description":"Android content-provider date_ms (ISO 8601). NULL for messages from before the field was added.","example":"2026-04-30T18:24:01.000Z"}}}},"count":{"type":"integer","example":1}}},"example":{"messages":[{"id":4821,"from_number":"+15555550199","to_number":"+15555550123","body":"Your code is 123456","message_id":"msg_example_0000000000000000000000","created_at":"2026-04-30T18:24:01.000Z","received_at":"2026-04-30T18:24:01.000Z"}],"count":1}}}},"401":{"description":"Missing, invalid, revoked, or expired bearer token.","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Stable error code, safe to switch on programmatically."},"message":{"type":"string","description":"Human-readable diagnostic. Free-form, do not parse."}}},"example":{"error":"unauthorized","message":"invalid, revoked, or expired token"}}}}}}},"/sms/messages":{"get":{"tags":["SMS"],"summary":"Read message history (sent + received), paginated","description":"Read-only paginated history across both inbound and outbound messages. Does **not** mutate `delivered_at` (use `/sms/receive` for that). Cursor-based keyset pagination — pass the previous response's `next_cursor` as `cursor` to fetch the next page.","security":[{"bearerAuth":[]}],"parameters":[{"name":"direction","in":"query","required":false,"description":"Filter to one direction or the other. Default: both.","schema":{"type":"string","enum":["sent","received","both"],"default":"both"}},{"name":"since","in":"query","required":false,"description":"RFC3339 lower bound on `created_at` (inclusive).","schema":{"type":"string","format":"date-time"}},{"name":"until","in":"query","required":false,"description":"RFC3339 upper bound on `created_at` (inclusive).","schema":{"type":"string","format":"date-time"}},{"name":"limit","in":"query","required":false,"description":"Max rows to return. Defaults to 50, capped at 100.","schema":{"type":"integer","minimum":1,"maximum":100,"default":50}},{"name":"cursor","in":"query","required":false,"description":"Opaque pagination token from a previous response's `next_cursor`.","schema":{"type":"string"}}],"responses":{"200":{"description":"Page of message history. `next_cursor` is `null` on the last page.","content":{"application/json":{"schema":{"type":"object","required":["messages","next_cursor"],"properties":{"messages":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer"},"direction":{"type":"string","enum":["sent","received"]},"from_number":{"type":"string"},"to_number":{"type":"string"},"body":{"type":"string"},"message_id":{"type":"string","nullable":true},"status":{"type":"string","nullable":true,"description":"Delivery status — only meaningful for outbound."},"created_at":{"type":"string","format":"date-time"},"delivered_at":{"type":"string","format":"date-time","nullable":true,"description":"Set when `/sms/receive` claimed the row — inbound only."}}}},"next_cursor":{"type":"string","nullable":true}}},"example":{"messages":[{"id":4821,"direction":"received","from_number":"+15555550199","to_number":"+15555550123","body":"Your code is 123456","message_id":"msg_example_0000000000000000000000","status":null,"created_at":"2026-04-30T18:24:01.000Z","delivered_at":"2026-04-30T18:24:01.000Z"},{"id":4820,"direction":"sent","from_number":"+15555550123","to_number":"+15555550199","body":"Hello from my agent.","message_id":"msg_01HM3PXJ4K8ZFQ5Y7B9W2N6V8Q","status":"queued","created_at":"2026-04-30T18:23:55.000Z","delivered_at":null}],"next_cursor":"4820|4810"}}}},"401":{"description":"Missing, invalid, revoked, or expired bearer token.","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Stable error code, safe to switch on programmatically."},"message":{"type":"string","description":"Human-readable diagnostic. Free-form, do not parse."}}},"example":{"error":"unauthorized","message":"invalid, revoked, or expired token"}}}}}}},"/sms/voice/dial":{"post":{"tags":["Voice"],"summary":"Place an outbound voice call","description":"Dials a destination number from one of the bearer's owned numbers. Returns a `call_id` and a `stream_url` (WebSocket) that the agent opens to send/receive 16 kHz PCM audio frames. The destination phone is ringing by the time this returns. Close the stream WS to hang up.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["phone_number","to"],"properties":{"phone_number":{"type":"string","description":"An E.164 phone number this bearer's subscription owns. The call is placed FROM this number.","example":"+15555550123"},"to":{"type":"string","description":"E.164 destination.","example":"+15555550199"}}},"example":{"phone_number":"+15555550123","to":"+15555550199"}}}},"responses":{"200":{"description":"Call placed; open the WebSocket at `stream_url` for audio.","content":{"application/json":{"schema":{"type":"object","required":["call_id","stream_url"],"properties":{"call_id":{"type":"string","description":"Server-assigned identifier for this call. Use it to correlate logs and presence events.","example":"call_example_0000000000000000000000"},"stream_url":{"type":"string","format":"uri","description":"WebSocket URL (`wss://...`) for binary 16 kHz PCM audio frames.","example":"wss://agentnumber.really.com/voice/stream/call_example_0000000000000000000000"}}},"example":{"call_id":"call_example_0000000000000000000000","stream_url":"wss://agentnumber.really.com/voice/stream/call_example_0000000000000000000000"}}}},"401":{"description":"Missing, invalid, revoked, or expired bearer token.","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Stable error code, safe to switch on programmatically."},"message":{"type":"string","description":"Human-readable diagnostic. Free-form, do not parse."}}},"example":{"error":"unauthorized","message":"invalid bearer token"}}}},"403":{"description":"Bearer doesn't own the requested `phone_number`.","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Stable error code, safe to switch on programmatically."},"message":{"type":"string","description":"Human-readable diagnostic. Free-form, do not parse."}}},"example":{"error":"forbidden","message":"phone_number not owned by this token"}}}},"409":{"description":"Number is already on an active call (single-active-call constraint per number).","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Stable error code, safe to switch on programmatically."},"message":{"type":"string","description":"Human-readable diagnostic. Free-form, do not parse."}}},"example":{"error":"conflict","message":"number is busy"}}}},"503":{"description":"Hardware temporarily unavailable. Retry shortly.","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Stable error code, safe to switch on programmatically."},"message":{"type":"string","description":"Human-readable diagnostic. Free-form, do not parse."}}},"example":{"error":"phone_node_unavailable"}}}}}}},"/sms/voice/hangup":{"post":{"tags":["Voice"],"summary":"End an active voice call","description":"Disconnect a live call by `call_id`. Returns once the hangup is dispatched to the phone; the presence WS will emit an `ended` event when the carrier confirms teardown. Equivalent to the MCP `hangup_call` tool.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["call_id"],"properties":{"call_id":{"type":"string","description":"Server-assigned call id from `/voice/dial` or a presence ringing event.","example":"call_example_0000000000000000000000"}}},"example":{"call_id":"call_example_0000000000000000000000"}}}},"responses":{"200":{"description":"Hangup dispatched.","content":{"application/json":{"schema":{"type":"object","required":["status"],"properties":{"status":{"type":"string","enum":["hangup_sent"],"description":"Confirms the hangup was dispatched to the phone."}}},"example":{"status":"hangup_sent"}}}},"401":{"description":"Missing, invalid, revoked, or expired bearer token.","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Stable error code, safe to switch on programmatically."},"message":{"type":"string","description":"Human-readable diagnostic. Free-form, do not parse."}}},"example":{"error":"unauthorized"}}}},"404":{"description":"`call_id` doesn't exist or has already ended.","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Stable error code, safe to switch on programmatically."},"message":{"type":"string","description":"Human-readable diagnostic. Free-form, do not parse."}}},"example":{"error":"not_found"}}}}}}},"/sms/voice/presence/{phone_number}":{"get":{"tags":["Voice"],"summary":"(WebSocket) Receive incoming-call signals","description":"**WebSocket endpoint** — OpenAPI 3.0 doesn't have a first-class WS shape; this is documented as a `GET` for tooling, but the real protocol is HTTP/1.1 `Upgrade: websocket`. Open with `Authorization: Bearer <token>` to signal 'I want incoming calls on this number.' Server auto-answers and pushes `{event: \"ringing\", call_id, caller, stream_url}` as a JSON text frame. Open the `stream_url` to take the audio.","security":[{"bearerAuth":[]}],"parameters":[{"name":"phone_number","in":"path","required":true,"description":"E.164 number you want to receive incoming calls on. Must be owned by the bearer.","schema":{"type":"string","example":"+15555550123"}}],"responses":{"101":{"description":"WebSocket upgrade. Server pushes JSON text frames per the description above."},"401":{"description":"Missing, invalid, revoked, or expired bearer token.","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Stable error code, safe to switch on programmatically."},"message":{"type":"string","description":"Human-readable diagnostic. Free-form, do not parse."}}},"example":{"error":"unauthorized"}}}},"403":{"description":"Bearer doesn't own the requested `phone_number`.","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Stable error code, safe to switch on programmatically."},"message":{"type":"string","description":"Human-readable diagnostic. Free-form, do not parse."}}},"example":{"error":"forbidden"}}}}}}},"/sms/voice/stream/{call_id}":{"get":{"tags":["Voice"],"summary":"(WebSocket) Per-call audio stream","description":"**WebSocket endpoint** — same OpenAPI caveat as `/voice/presence`. Binary frames carry 16 kHz mono PCM both ways. Open with `Authorization: Bearer <token>`. Close to hang up. If the remote party hangs up, the server closes the WS with a status code carrying the reason.","security":[{"bearerAuth":[]}],"parameters":[{"name":"call_id","in":"path","required":true,"description":"Call identifier from the `stream_url` returned by `/voice/dial` or pushed on the presence WS.","schema":{"type":"string","example":"call_example_0000000000000000000000"}}],"responses":{"101":{"description":"WebSocket upgrade. Binary frames carry 16 kHz mono PCM."},"401":{"description":"Missing or invalid bearer.","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Stable error code, safe to switch on programmatically."},"message":{"type":"string","description":"Human-readable diagnostic. Free-form, do not parse."}}},"example":{"error":"unauthorized"}}}},"404":{"description":"`call_id` doesn't exist or has already ended.","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Stable error code, safe to switch on programmatically."},"message":{"type":"string","description":"Human-readable diagnostic. Free-form, do not parse."}}},"example":{"error":"not_found"}}}}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer"}}}}