{"openapi":"3.1.0","info":{"title":"UnlimCall API","version":"1.0.0","summary":"Flat-rate outbound calling, as a REST API.","description":"Originate calls from AI agents, pull call detail records and recordings, read your live balance, list SIP lines, and subscribe to call events via HMAC-signed webhooks. Authenticate with a scoped bearer token (`unl_live_…`, minted in the portal at /settings/integrations) or with OAuth 2.0 client_credentials.","contact":{"name":"UnlimCall Support","email":"support@unlimcall.com","url":"https://unlimcall.com/developers/api/"},"termsOfService":"https://unlimcall.com/legal/terms-of-service/"},"servers":[{"url":"https://api.unlimcall.com/v1","description":"Production"}],"security":[{"bearerAuth":[]},{"oauth2":[]}],"tags":[{"name":"Account","description":"Identity and balance."},{"name":"Calls","description":"Origination and call detail records."},{"name":"Agents","description":"SIP lines / AI agents on the account."},{"name":"Webhooks","description":"Subscribe to call events."}],"paths":{"/me":{"get":{"tags":["Account"],"operationId":"getMe","summary":"Identity of the token owner","description":"Cheap connectivity / token-validity probe. Any valid token.","responses":{"200":{"description":"Identity of the customer that owns the token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Me"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Account suspended.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/balance":{"get":{"tags":["Account"],"operationId":"getBalance","summary":"Live account balance","security":[{"bearerAuth":["read:balance"]},{"oauth2":["read:balance"]}],"responses":{"200":{"description":"Balance snapshot.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Balance"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"500":{"description":"Upstream snapshot fetch failed; retry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/cdrs":{"get":{"tags":["Calls"],"operationId":"listCdrs","summary":"List call detail records","security":[{"bearerAuth":["read:cdrs"]},{"oauth2":["read:cdrs"]}],"parameters":[{"name":"from","in":"query","required":false,"schema":{"type":"string","format":"date-time"},"description":"Window start (ISO 8601). Default: 24h ago."},{"name":"till","in":"query","required":false,"schema":{"type":"string","format":"date-time"},"description":"Window end. Must be after `from` and within 31 days. Default: now."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":1000,"default":100},"description":"Max rows. Default 100, max 1000. (No cursor pagination in v1.)"}],"responses":{"200":{"description":"Call detail records in the window.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CdrList"}}}},"400":{"description":"Invalid window (`invalid_timestamp`, `till_must_be_after_from`, `window_too_wide`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"409":{"description":"Account has no trunk allocated yet (`account_not_provisioned`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/agents":{"get":{"tags":["Agents"],"operationId":"listAgents","summary":"List SIP lines (agents)","description":"Credentials are NOT returned — read them live from the portal.","security":[{"bearerAuth":["read:agents"]},{"oauth2":["read:agents"]}],"responses":{"200":{"description":"SIP lines on the account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentList"}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/calls":{"post":{"tags":["Calls"],"operationId":"originateCall","summary":"Originate an outbound call","description":"An AI agent dials the destination and bridges to it on answer.","security":[{"bearerAuth":["write:calls"]},{"oauth2":["write:calls"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CallCreate"}}}},"responses":{"200":{"description":"Call queued.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CallQueued"}}}},"400":{"description":"Body not JSON or a field failed validation (`invalid_input`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"No agent with that ID on this account (`ai_agent_not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Agent or account not provisioned, or dialer add-on required (`ai_agent_not_provisioned`, `account_not_provisioned`, `dialer_required`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"Upstream rejected the originate (`originate_failed`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"Origination not available in this environment (`engine_not_configured`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/hooks":{"post":{"tags":["Webhooks"],"operationId":"createHook","summary":"Subscribe a webhook","description":"Subscribe a REST webhook to a single event type. Returns the signing secret (shown ONCE).","security":[{"bearerAuth":["write:hooks"]},{"oauth2":["write:hooks"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HookCreate"}}}},"responses":{"200":{"description":"Subscription created; secret returned once.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HookCreated"}}}},"400":{"description":"Body not JSON, `event` not in the allow-list, or `target_url` not https (`invalid_input`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/hooks/{id}":{"delete":{"tags":["Webhooks"],"operationId":"deleteHook","summary":"Unsubscribe a webhook","description":"Soft-delete — delivery history is preserved for debugging.","security":[{"bearerAuth":["write:hooks"]},{"oauth2":["write:hooks"]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"The `id` returned by `POST /hooks`."}],"responses":{"204":{"description":"Unsubscribed."},"400":{"description":"`id` is not a UUID (`invalid_input`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"Hook does not exist, is not owned by this account, or is already disabled (`not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}},"webhooks":{"call.ended":{"post":{"summary":"A dialer or AI-agent call hung up.","description":"Delivered to the `target_url` you registered. Signed with `X-Unlimcall-Signature: t=<unix>,v1=<hex_sha256_hmac>` over `${t}.${rawBody}`.","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CallEndedEvent"}}}},"responses":{"2xx":{"description":"Acknowledged. Non-2xx 5xx/timeout is retried with exponential backoff up to 24h; 4xx is a permanent failure."}}}},"call.transferred":{"post":{"summary":"A call was transferred to a human.","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEvent"}}}},"responses":{"2xx":{"description":"Acknowledged."}}}},"disposition.set":{"post":{"summary":"A call disposition was applied.","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEvent"}}}},"responses":{"2xx":{"description":"Acknowledged."}}}},"dnc.added":{"post":{"summary":"A contact was moved to Do-Not-Call.","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEvent"}}}},"responses":{"2xx":{"description":"Acknowledged."}}}},"lead.completed":{"post":{"summary":"A contact reached a terminal disposition.","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEvent"}}}},"responses":{"2xx":{"description":"Acknowledged."}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"Scoped API token (`unl_live_…`) minted in the portal at /settings/integrations."},"oauth2":{"type":"oauth2","description":"OAuth 2.0 client_credentials. Discovery: https://app.unlimcall.com/.well-known/oauth-authorization-server","flows":{"clientCredentials":{"tokenUrl":"https://app.unlimcall.com/oauth/token","scopes":{"read:balance":"Read live account balance.","read:cdrs":"Read call detail records.","read:agents":"List SIP lines (agents).","read:numbers":"List DID phone numbers on the account.","read:invoices":"List historical invoices.","write:calls":"Originate outbound calls via the AI agent.","write:hooks":"Create and delete REST webhook subscriptions."}}}}},"responses":{"Unauthorized":{"description":"Token invalid, expired, revoked, or missing the required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"schemas":{"Error":{"type":"object","properties":{"error":{"type":"string","description":"Stable machine code, e.g. `unauthorized`, `invalid_input`."},"message":{"type":"string","description":"Human-readable detail (optional)."}},"required":["error"]},"Me":{"type":"object","properties":{"customerId":{"type":"string","example":"1042"},"companyName":{"type":"string","example":"Acme Calling Co."},"primaryContactEmail":{"type":"string","format":"email","example":"yaro@acme.com"}},"required":["customerId","companyName","primaryContactEmail"]},"Balance":{"type":"object","properties":{"balance":{"type":"number","example":42.7831},"currency":{"type":"string","example":"USD"},"accountType":{"type":"string","example":"prepaid"},"creditLimit":{"type":["number","null"],"example":null},"blocked":{"type":"boolean","example":false},"stale":{"type":"boolean","example":false}},"required":["balance","currency","accountType","blocked","stale"]},"Cdr":{"type":"object","properties":{"id":{"type":"string","example":"1747852442.42"},"startTime":{"type":"string","format":"date-time"},"duration":{"type":"integer","description":"Total call seconds."},"billsec":{"type":"integer","description":"Billable seconds."},"src":{"type":"string","example":"+14155550100"},"dst":{"type":"string","example":"+442071838750"},"disposition":{"type":"string","example":"ANSWERED"},"sipUsername":{"type":"string","example":"unl_001"},"sellPriceCents":{"type":"integer","example":34},"currency":{"type":"string","example":"USD"},"country":{"type":"string","example":"United Kingdom"},"did":{"type":["string","null"],"example":null}}},"CdrList":{"type":"object","properties":{"count":{"type":"integer"},"calls":{"type":"array","items":{"$ref":"#/components/schemas/Cdr"}}},"required":["count","calls"]},"Agent":{"type":"object","properties":{"id":{"type":"string","example":"27"},"sipUsername":{"type":"string","example":"unl_001"},"label":{"type":"string","example":"SDR seat — Berlin"},"createdAt":{"type":"string","format":"date-time"}}},"AgentList":{"type":"object","properties":{"count":{"type":"integer"},"agents":{"type":"array","items":{"$ref":"#/components/schemas/Agent"}}},"required":["count","agents"]},"CallCreate":{"type":"object","properties":{"aiAgentId":{"type":"string","description":"ID of the AI agent that runs the call. Must belong to this account.","example":"27"},"toNumber":{"type":"string","description":"Destination in E.164 form.","example":"+14155551234"},"fromNumber":{"type":"string","description":"Caller ID override. Validated; future-reserved (not yet forwarded to the engine)."},"metadata":{"type":"object","additionalProperties":true,"description":"Free-form annotations. Accepted; not yet persisted."}},"required":["aiAgentId","toNumber"]},"CallQueued":{"type":"object","properties":{"callId":{"type":"string","format":"uuid"},"status":{"type":"string","example":"queued"}},"required":["callId","status"]},"HookCreate":{"type":"object","properties":{"event":{"type":"string","enum":["call.ended","call.transferred","disposition.set","dnc.added","lead.completed"]},"target_url":{"type":"string","format":"uri","description":"Must be `https://`, max 512 chars."}},"required":["event","target_url"]},"HookCreated":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"secret":{"type":"string","description":"HMAC signing secret (`whsec_…`). Shown once.","example":"whsec_2VR9aBcDeFgHiJkLmNoPqRsTuVwXyZ01"}},"required":["id","secret"]},"WebhookEvent":{"type":"object","description":"Base call-event payload. Specific events add fields.","additionalProperties":true,"properties":{"callId":{"type":"string"},"contactId":{"type":"string"},"phone":{"type":"string"},"dispositionCode":{"type":"string"},"actor":{"type":"string"}}},"CallEndedEvent":{"allOf":[{"$ref":"#/components/schemas/WebhookEvent"},{"type":"object","properties":{"durationSec":{"type":"integer"},"hangupReason":{"type":"string"},"sentimentLabel":{"type":"string"},"transferInitiated":{"type":"boolean"}}}]}}}}