{"openapi":"3.1.0","info":{"title":"AI Text-a-roo","description":"SMS conversation channel for AI agents. Text your AI. It texts back.","version":"2.0.0"},"paths":{"/v1/auth/verify":{"post":{"tags":["auth"],"summary":"Verify Phone","description":"Request a phone verification code (US and Canadian numbers by default).","operationId":"verify_phone_v1_auth_verify_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VerifyRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VerifyResponse"}}}},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Too Many Requests","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/auth/confirm":{"post":{"tags":["auth"],"summary":"Confirm Phone","description":"Confirm the verification code and receive a new API key.\n\nAdds a key named by the optional label (default \"API key\"); the\naccount's other keys and connected apps keep working. Up to 10 keys:\nan 11th removes the one unused the longest (never the one that forwards\nthe owner's texts while another can go), listed in \"retired\" with any\nforwarding it stopped. When the phone already has an account, its owner\nis texted that a key was added.","operationId":"confirm_phone_v1_auth_confirm_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConfirmRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConfirmResponse"}}}},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Too Many Requests","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/send":{"post":{"tags":["messaging"],"summary":"Send Message","description":"Send a message to the user or a third party.\n\nIf 'to' is omitted, the message is sent to the registered user.\nIf 'to' is a different number, a relay is created (paid tier only).\nRelay replies from the third party are forwarded to the owner's phone.","operationId":"send_message_v1_send_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SendRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SendResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Conflict","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Too Many Requests","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal Server Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/webhook":{"post":{"tags":["webhooks"],"summary":"Register Webhook","description":"Register or replace the webhook that receives each text the owner sends.\n\nIf the account has a security PIN, include it as `pin`. The owner is\ntexted before forwarding starts (and can reply UNHOOK to stop it); if\nthat text can't be sent, nothing changes. Optional auth_header is sent\nas the Authorization header on each delivery (stored encrypted, never\nreturned). Registering without it clears any previous value.\nForwarding is tied to the key that registers it: removing that key\nstops it.","operationId":"register_webhook_v1_webhook_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookResponse"}}}},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Conflict","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Too Many Requests","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Service Unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["webhooks"],"summary":"Remove Webhook","description":"Remove the webhook. The owner is texted that forwarding is off.\n\nStopping forwarding always works: it's never refused by the change limit.","operationId":"remove_webhook_v1_webhook_delete","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookResponse"}}}}}}},"/v1/account":{"get":{"tags":["account"],"summary":"Get Account","description":"Get account info and usage. daily_limit reflects the user's tier.\n\nmessages_today counts today's texts (UTC) against daily_limit: the texts\nthe agent sends (to the user or to contacts), contacts' replies forwarded\nto the user, and the user's \">\" replies to contacts. The user's texts to\nthe agent don't count.","operationId":"get_account_v1_account_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccountResponse"}}}}}}},"/v1/account/connections":{"get":{"tags":["account"],"summary":"List Connections","description":"What's connected to the account: its API keys and app sign-ins, and where texts are forwarded.\n\nMost recently used first. \"current\" marks the key making this request;\n\"forwarding\" marks the connection that set up forwarding.","operationId":"list_connections_v1_account_connections_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnectionsResponse"}}}}}}},"/v1/account/connections/{connection_id}":{"delete":{"tags":["account"],"summary":"Remove Connection","description":"Disconnect one API key or app sign-in. Send {\"pin\": \"4821\"} when the account has a PIN.\n\nIt stops working at once, and forwarding it set up stops too. The\nowner is texted. At most 5 removals an hour and 10 a day; the owner can\nremove anything, or sign out everything, at aitextaroo.com/dashboard.","operationId":"remove_connection_v1_account_connections__connection_id__delete","parameters":[{"name":"connection_id","in":"path","required":true,"schema":{"type":"string","title":"Connection Id"}}],"requestBody":{"content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/RemoveConnectionRequest"},{"type":"null"}],"title":"Body"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RemoveConnectionResponse"}}}},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"description":"Forbidden"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"description":"Not Found"},"409":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"description":"Conflict"},"429":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"description":"Too Many Requests"},"503":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"description":"Service Unavailable"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/account/pin":{"post":{"tags":["account"],"summary":"Set Pin","description":"Set the security PIN, or change it (send current_pin when one is set).\n\nThe owner is texted before the change; if that text can't be sent,\nnothing changes. Returns status \"pin_set\" or \"pin_changed\".","operationId":"set_pin_v1_account_pin_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetPinRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PinResponse"}}}},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Too Many Requests","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Service Unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["account"],"summary":"Remove Pin","description":"Remove the security PIN. Needs a JSON body with the current PIN: {\"current_pin\": \"4821\"}.\n\nThe owner is texted before the change. Returns status \"pin_removed\",\nor \"pin_not_set\" if there was no PIN.","operationId":"remove_pin_v1_account_pin_delete","requestBody":{"content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/RemovePinRequest"},{"type":"null"}],"title":"Body"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PinResponse"}}}},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Too Many Requests","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Service Unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/verify-pin":{"post":{"tags":["account"],"summary":"Verify Pin","description":"Verify the user's security PIN.\n\nCalled by the agent when it needs to confirm a sensitive action.\nOnly wrong PINs count toward the lockouts (3 per 5 minutes, 10 a day;\nthe owner is texted when the daily one starts).","operationId":"verify_pin_v1_verify_pin_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PinRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VerifyPinResponse"}}}},"400":{"description":"Bad Request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Too Many Requests","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Service Unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/health":{"get":{"tags":["account"],"summary":"Health Check","description":"Public health check endpoint.","operationId":"health_check_v1_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HealthResponse"}}}}}}},"/v1/inbound/telnyx":{"post":{"tags":["internal"],"summary":"Telnyx Inbound Webhook","description":"Receive inbound SMS from Telnyx.\n\nVerifies the Ed25519 webhook signature before processing.","operationId":"telnyx_inbound_webhook_v1_inbound_telnyx_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/v1/hooks/send-sms":{"post":{"tags":["hooks"],"summary":"Supabase Send Sms Hook","description":"Supabase send_sms hook — sends OTP via Telnyx instead of Twilio.\n\nAuthenticated by Standard Webhooks signature (or a legacy Bearer secret);\nsee the module docstring.\n\nSupabase sends payload like:\n{\n    \"user\": {\"phone\": \"+123****7890\"},\n    \"sms\": {\"otp\": \"123456\"}\n}\n\nMust return 200 with empty JSON object on success.","operationId":"supabase_send_sms_hook_v1_hooks_send_sms_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/v1/stream":{"get":{"tags":["stream"],"summary":"Stream","description":"Open an SSE stream for real-time inbound message delivery.\n\nOnly one stream per account (any of its keys). New connections evict\nold ones. Supports Last-Event-ID for message replay from the in-memory\nbuffer. Removing the stream's key ends it at once (\"credential_revoked\").","operationId":"stream_v1_stream_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/v1/agent-setup":{"get":{"tags":["setup"],"summary":"Agent Setup","description":"Plain-text setup instructions for AI agents.\n\nAny agent can fetch this URL and follow the steps to set up\nSMS messaging for its user. No authentication required.","operationId":"agent_setup_v1_agent_setup_get","responses":{"200":{"description":"Successful Response","content":{"text/plain":{"schema":{"type":"string"}}}}}}},"/v1/billing/checkout":{"post":{"tags":["billing"],"summary":"Create Checkout Session","description":"Create a Stripe Checkout session for the Pro plan.\n\nReturns a checkout_url for the client to redirect to.\nIncludes a 14-day free trial.","operationId":"create_checkout_session_v1_billing_checkout_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/v1/billing/portal":{"post":{"tags":["billing"],"summary":"Create Portal Session","description":"Create a Stripe Customer Portal session for subscription management.\n\nReturns a portal_url for the client to redirect to. User can cancel,\nupdate payment method, or view invoices.","operationId":"create_portal_session_v1_billing_portal_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/v1/messages/recent":{"get":{"tags":["messages"],"summary":"Recent Messages","description":"List the owner's texts from the last 5 minutes, oldest first.\n\nRead-only: includes texts already delivered elsewhere. If after_id is\nunknown or has expired, every retained text is returned.","operationId":"recent_messages_v1_messages_recent_get","parameters":[{"name":"after_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Only return texts after the one with this id (cursor for paging).","title":"After Id"},"description":"Only return texts after the one with this id (cursor for paging)."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecentMessagesResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/messages/wait":{"post":{"tags":["messages"],"summary":"Wait For Message","description":"Wait for the owner's next text (long poll).\n\nReturns at once if a reply that nobody has handled arrived since the\nlast text sent to the owner. Otherwise waits up to timeout_seconds\n(default 60, max 120). The returned text goes only to this caller,\nnot to the SSE stream or webhook. If this key is removed meanwhile,\nthe wait ends with no message.","operationId":"wait_for_message_v1_messages_wait_post","requestBody":{"content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/WaitRequest"},{"type":"null"}],"title":"Body"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WaitResponse"}}}},"429":{"description":"Too Many Requests","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/dashboard/session":{"get":{"tags":["dashboard"],"summary":"Check Session","description":"200 when this sign-in may manage its account: every check in supabase_session passed.\n\nThe website's billing routes ask this before going to Stripe, so the\nrule for who the owner is lives in one place.","operationId":"check_session_v1_dashboard_session_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DashboardSessionResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Too Many Requests","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Service Unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/dashboard/connections":{"get":{"tags":["dashboard"],"summary":"List Connections","description":"The account's keys and app sign-ins, most recently used first, and where texts are forwarded.","operationId":"list_connections_v1_dashboard_connections_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnectionsResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Too Many Requests","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Service Unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/dashboard/connections/{connection_id}":{"delete":{"tags":["dashboard"],"summary":"Remove Connection","description":"Remove a key or app sign-in. It stops working at once, and forwarding it set up stops too.","operationId":"remove_connection_v1_dashboard_connections__connection_id__delete","parameters":[{"name":"connection_id","in":"path","required":true,"schema":{"type":"string","title":"Connection Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RemoveConnectionResponse"}}}},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"description":"Unauthorized"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"description":"Not Found"},"429":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"description":"Too Many Requests"},"503":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"description":"Service Unavailable"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/dashboard/forwarding":{"delete":{"tags":["dashboard"],"summary":"Stop Forwarding","description":"Stop forwarding the owner's texts. Always works: no PIN and no rate limit.","operationId":"stop_forwarding_v1_dashboard_forwarding_delete","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StopForwardingResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Too Many Requests","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Service Unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/dashboard/sign-out-everything":{"post":{"tags":["dashboard"],"summary":"Sign Out Everything","description":"Remove every key and app sign-in, stop forwarding, and end the owner's other website sessions.\n\nThe owner is texted when something was removed (at most 3 times an\nhour). Then Supabase Auth is asked to sign out the owner's other\nbrowsers, keeping this one (best effort: browsers_signed_out says\nwhether it did).","operationId":"sign_out_everything_v1_dashboard_sign_out_everything_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SignOutEverythingResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Too Many Requests","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Service Unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/dashboard/pin":{"post":{"tags":["dashboard"],"summary":"Set Pin","description":"Set a security PIN (4-8 digits) when there's none: 409 pin_already_set otherwise.\n\nThe owner is texted first; if that text can't be sent, nothing changes.","operationId":"set_pin_v1_dashboard_pin_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PinRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PinResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Too Many Requests","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Service Unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Conflict","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["dashboard"],"summary":"Remove Pin","description":"Remove the security PIN (no current PIN needed here). \"pin_removed\" or \"pin_not_set\".\n\nThe owner is texted first; if that text can't be sent, nothing changes.\nNever refused by a rate limit.","operationId":"remove_pin_v1_dashboard_pin_delete","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PinResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Too Many Requests","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Service Unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Conflict","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/webhooks/stripe":{"post":{"tags":["stripe"],"summary":"Stripe Webhook","description":"Receive Stripe webhook events.\n\nSignature verified via STRIPE_WEBHOOK_SECRET. Returns 200 for all\nvalid events (even unhandled ones) to prevent Stripe retries.","operationId":"stripe_webhook_webhooks_stripe_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/oauth/register":{"post":{"tags":["oauth"],"summary":"Register Client","description":"Register a public OAuth client (RFC 7591). Nothing is stored.","operationId":"register_client_oauth_register_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/oauth/token":{"post":{"tags":["oauth"],"summary":"Token","description":"Exchange an authorization code or refresh token (public clients, PKCE).\n\nA code exchange starts a grant (a new app sign-in), and the owner is\ntexted about it after the response is sent (best effort). Refreshes\ndon't text.","operationId":"token_oauth_token_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/oauth/revoke":{"post":{"tags":["oauth"],"summary":"Revoke","description":"Revoke a token's whole grant (RFC 7009). Always 200 for a well-formed request.\n\nRevoking either an access or a refresh token ends the grant: its refresh\ntoken stops working and so do its access tokens, immediately, along\nwith the app's pending waits and any forwarding it set up.","operationId":"revoke_oauth_revoke_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}}},"components":{"schemas":{"AccountResponse":{"properties":{"tier":{"type":"string","title":"Tier"},"assigned_number":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Assigned Number"},"phone_verified":{"type":"boolean","title":"Phone Verified","default":true},"has_pin":{"type":"boolean","title":"Has Pin","default":false},"messages_today":{"type":"integer","title":"Messages Today"},"daily_limit":{"type":"integer","title":"Daily Limit"},"credits":{"type":"number","title":"Credits","default":0.0},"webhook":{"anyOf":[{"$ref":"#/components/schemas/WebhookStatus"},{"type":"null"}]}},"type":"object","required":["tier","assigned_number","messages_today","daily_limit"],"title":"AccountResponse"},"AppConnection":{"properties":{"id":{"type":"string","title":"Id"},"kind":{"type":"string","const":"app","title":"Kind"},"label":{"type":"string","title":"Label"},"verified":{"type":"boolean","title":"Verified"},"created_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created At"},"last_used_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Used At"},"current":{"type":"boolean","title":"Current"},"forwarding":{"type":"boolean","title":"Forwarding"}},"type":"object","required":["id","kind","label","verified","created_at","last_used_at","current","forwarding"],"title":"AppConnection","description":"One of the account's OAuth app sign-ins. Times are ISO 8601 UTC."},"ConfirmRequest":{"properties":{"phone":{"type":"string","title":"Phone"},"code":{"type":"string","title":"Code"},"label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Label"}},"type":"object","required":["phone","code"],"title":"ConfirmRequest","description":"Request to confirm a verification code, which adds an API key.\n\nlabel names the key in the owner's dashboard and texts (optional,\ndefault \"API key\"): at most 40 letters, numbers, spaces and . _ ( ) ' @ # : + -"},"ConfirmResponse":{"properties":{"status":{"type":"string","title":"Status","default":"verified"},"api_key":{"type":"string","title":"Api Key"},"key_id":{"type":"string","title":"Key Id"},"assigned_number":{"type":"string","title":"Assigned Number"},"tier":{"type":"string","title":"Tier","default":"free"},"note":{"type":"string","title":"Note","default":"Store this API key securely. It cannot be retrieved again. Text your assigned number to talk to your agent."},"retired":{"items":{"$ref":"#/components/schemas/RetiredKey"},"type":"array","title":"Retired","default":[]}},"type":"object","required":["api_key","key_id","assigned_number"],"title":"ConfirmResponse"},"ConnectionsResponse":{"properties":{"keys":{"items":{"$ref":"#/components/schemas/KeyConnection"},"type":"array","title":"Keys"},"apps":{"items":{"$ref":"#/components/schemas/AppConnection"},"type":"array","title":"Apps"},"forwarding":{"anyOf":[{"$ref":"#/components/schemas/ForwardingStatus"},{"type":"null"}]},"max_keys":{"type":"integer","title":"Max Keys"}},"type":"object","required":["keys","apps","forwarding","max_keys"],"title":"ConnectionsResponse"},"DashboardSessionResponse":{"properties":{"status":{"type":"string","title":"Status","default":"signed_in"}},"type":"object","title":"DashboardSessionResponse"},"ErrorDetail":{"properties":{"code":{"type":"string","title":"Code"},"message":{"type":"string","title":"Message"},"retry_after":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Retry After"}},"type":"object","required":["code","message"],"title":"ErrorDetail"},"ErrorResponse":{"properties":{"error":{"$ref":"#/components/schemas/ErrorDetail"}},"type":"object","required":["error"],"title":"ErrorResponse"},"ForwardingStatus":{"properties":{"host":{"type":"string","title":"Host"},"set_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Set By"},"set_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Set At"}},"type":"object","required":["host","set_by","set_at"],"title":"ForwardingStatus","description":"Where the owner's texts are forwarded (host only), and which connection set it up."},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"HealthResponse":{"properties":{"status":{"type":"string","title":"Status","default":"ok"},"version":{"type":"string","title":"Version"}},"type":"object","required":["version"],"title":"HealthResponse"},"InboundMessage":{"properties":{"id":{"type":"string","title":"Id"},"text":{"type":"string","title":"Text"},"received_at":{"type":"string","title":"Received At"},"channel":{"type":"string","title":"Channel","default":"sms"},"trust_level":{"type":"string","title":"Trust Level","default":"low"},"has_pin":{"type":"boolean","title":"Has Pin","default":false}},"type":"object","required":["id","text","received_at"],"title":"InboundMessage"},"KeyConnection":{"properties":{"id":{"type":"string","title":"Id"},"kind":{"type":"string","const":"key","title":"Kind"},"label":{"type":"string","title":"Label"},"hint":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Hint"},"created_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created At"},"last_used_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Used At"},"current":{"type":"boolean","title":"Current"},"forwarding":{"type":"boolean","title":"Forwarding"}},"type":"object","required":["id","kind","label","hint","created_at","last_used_at","current","forwarding"],"title":"KeyConnection","description":"One of the account's API keys. Times are ISO 8601 UTC."},"PinRequest":{"properties":{"pin":{"type":"string","title":"Pin"}},"type":"object","required":["pin"],"title":"PinRequest","description":"Request to verify a PIN."},"PinResponse":{"properties":{"status":{"type":"string","title":"Status"}},"type":"object","required":["status"],"title":"PinResponse"},"RecentMessagesResponse":{"properties":{"messages":{"items":{"$ref":"#/components/schemas/InboundMessage"},"type":"array","title":"Messages"},"retention_seconds":{"type":"integer","title":"Retention Seconds"}},"type":"object","required":["messages","retention_seconds"],"title":"RecentMessagesResponse"},"RemoveConnectionRequest":{"properties":{"pin":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Pin"}},"type":"object","title":"RemoveConnectionRequest","description":"Optional body for removing a connection: the PIN, required when the account has one."},"RemoveConnectionResponse":{"properties":{"status":{"type":"string","title":"Status","default":"removed"},"removed":{"$ref":"#/components/schemas/RemovedConnection"},"forwarding_stopped":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Forwarding Stopped"},"owner_notified":{"type":"boolean","title":"Owner Notified"},"was_current":{"type":"boolean","title":"Was Current"}},"type":"object","required":["removed","forwarding_stopped","owner_notified","was_current"],"title":"RemoveConnectionResponse"},"RemovePinRequest":{"properties":{"current_pin":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Current Pin"}},"type":"object","title":"RemovePinRequest","description":"Remove the PIN: current_pin is required."},"RemovedConnection":{"properties":{"id":{"type":"string","title":"Id"},"kind":{"type":"string","enum":["key","app"],"title":"Kind"},"label":{"type":"string","title":"Label"}},"type":"object","required":["id","kind","label"],"title":"RemovedConnection"},"RetiredKey":{"properties":{"id":{"type":"string","title":"Id"},"label":{"type":"string","title":"Label"},"forwarding_stopped":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Forwarding Stopped"}},"type":"object","required":["id","label","forwarding_stopped"],"title":"RetiredKey","description":"A key removed to stay at 10 when a new one was added; it stopped working at once."},"SendRequest":{"properties":{"text":{"type":"string","title":"Text"},"to":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"To"},"recipient_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Recipient Name"}},"type":"object","required":["text"],"title":"SendRequest","description":"Request to send an SMS message.\n\nThe 'to' field is optional — if omitted, the message is sent to the\nregistered user's phone number (the normal case). If provided with a\ndifferent number, a relay is created (paid tier only).\n\nThe 'recipient_name' is optional — used to display a friendly name\nwhen the relay recipient replies and the message is forwarded to the owner."},"SendResponse":{"properties":{"status":{"type":"string","title":"Status","default":"sent"},"message_id":{"type":"string","title":"Message Id"}},"type":"object","required":["message_id"],"title":"SendResponse"},"SetPinRequest":{"properties":{"pin":{"type":"string","title":"Pin"},"current_pin":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Current Pin"}},"type":"object","required":["pin"],"title":"SetPinRequest","description":"Set a first PIN, or change one: current_pin is required when a PIN exists."},"SignOutEverythingResponse":{"properties":{"status":{"type":"string","title":"Status","default":"signed_out"},"keys_removed":{"type":"integer","title":"Keys Removed"},"apps_removed":{"type":"integer","title":"Apps Removed"},"forwarding_stopped":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Forwarding Stopped"},"owner_notified":{"type":"boolean","title":"Owner Notified"},"browsers_signed_out":{"type":"boolean","title":"Browsers Signed Out"}},"type":"object","required":["keys_removed","apps_removed","forwarding_stopped","owner_notified","browsers_signed_out"],"title":"SignOutEverythingResponse"},"StopForwardingResponse":{"properties":{"status":{"type":"string","title":"Status","default":"removed"},"was_registered":{"type":"boolean","title":"Was Registered"},"host":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Host"}},"type":"object","required":["was_registered","host"],"title":"StopForwardingResponse"},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"VerifyPinResponse":{"properties":{"verified":{"type":"boolean","title":"Verified"},"attempts_remaining":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Attempts Remaining"}},"type":"object","required":["verified"],"title":"VerifyPinResponse"},"VerifyRequest":{"properties":{"phone":{"type":"string","title":"Phone"}},"type":"object","required":["phone"],"title":"VerifyRequest","description":"Request to send a verification code."},"VerifyResponse":{"properties":{"status":{"type":"string","title":"Status","default":"code_sent"},"expires_in":{"type":"integer","title":"Expires In","default":300}},"type":"object","title":"VerifyResponse"},"WaitRequest":{"properties":{"timeout_seconds":{"type":"integer","maximum":120.0,"minimum":1.0,"title":"Timeout Seconds","default":60}},"type":"object","title":"WaitRequest","description":"Request to wait for the owner's next text."},"WaitResponse":{"properties":{"message":{"anyOf":[{"$ref":"#/components/schemas/InboundMessage"},{"type":"null"}]},"timed_out":{"type":"boolean","title":"Timed Out"}},"type":"object","required":["timed_out"],"title":"WaitResponse"},"WebhookRequest":{"properties":{"url":{"type":"string","title":"Url"},"auth_header":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Auth Header"},"pin":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Pin"}},"type":"object","required":["url"],"title":"WebhookRequest","description":"Request to register a webhook URL.\n\nauth_header is an optional Authorization header value sent with every\ndelivery (e.g. \"Bearer <routine key>\"). Stored encrypted, never echoed.\nOmitting it clears any previous value. pin is required when the account\nhas a security PIN (see services.webhooks)."},"WebhookResponse":{"properties":{"status":{"type":"string","title":"Status"},"url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Url"},"secret":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Secret"},"has_auth_header":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Has Auth Header"},"owner_notified":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Owner Notified"},"was_registered":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Was Registered"}},"type":"object","required":["status"],"title":"WebhookResponse"},"WebhookStatus":{"properties":{"registered":{"type":"boolean","title":"Registered"},"host":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Host"},"has_auth_header":{"type":"boolean","title":"Has Auth Header","default":false}},"type":"object","required":["registered"],"title":"WebhookStatus","description":"Where the owner's texts are forwarded. Only the host, never the full URL."}}}}