API Overview
API Overview
Section titled “API Overview”SIPSTACK exposes a REST API at:
https://api.sipstack.comThere are three distinct surfaces, and it matters which one you use:
| Surface | Path prefix | Auth | Intended for |
|---|---|---|---|
| Portal API | /v2/portal/... | Session token (login) | The API that powers Switchboard itself |
| Developer API | /api/v2/... | API key (x-api-key) | Server-to-server SMS, message/call history, contacts & webhooks |
| Key & webhook management | /api/api-keys, /api/webhooks | Session token (login) | Creating and managing your API keys and webhook endpoints from a logged-in portal session |
Portal API (/v2/portal/...)
Section titled “Portal API (/v2/portal/...)”Everything you see in Switchboard is driven by this API — accounts, billing, PBX configuration, messaging, AI services. You authenticate by logging in (POST /v2/portal/auth/login) and presenting the returned JWT; see Authentication.
A documented subset is published in the API Reference (generated from the OpenAPI spec). Endpoints outside the published reference exist and work, but they are portal-internal: they evolve with the product and can change without notice. Build durable integrations against the documented endpoints, and treat anything else as best-effort.
Developer API (/api/v2/...)
Section titled “Developer API (/api/v2/...)”A stable surface for server-to-server use with API keys. Every endpoint is authenticated with the x-api-key header, scoped to your organization, and rate-limited. The API spans all three SIPSTACK products, each under its own path. Full request/response shapes are in the API Reference.
Flare — messaging & contacts
Section titled “Flare — messaging & contacts”| Method & path | Does |
|---|---|
POST /api/v2/messages/send | Send an outbound SMS — or an MMS from hosted URLs or an uploaded file. An alias of the canonical POST /api/v2/flare/messages, with identical behaviour |
GET /api/v2/messages | List message history, with direction / number / contact / from_date / to_date filters |
GET /api/v2/messages/{id} | Fetch a single message |
GET /api/v2/contacts | List & search contacts (search, phone, tag) |
POST /api/v2/contacts | Create a contact (create-only; 409 if the number already exists) |
GET / PATCH / DELETE /api/v2/contacts/{id} | Fetch, update, and soft-delete a contact |
POST /api/v2/contacts/{id}/opt-out | .../opt-in | Set/clear the real send gate (STOP list + is_opted_out) — the compliance-correct suppression |
GET / POST /api/v2/flare/campaigns | List and create A2P campaigns |
GET /api/v2/flare/campaigns/{id} | Fetch one campaign with rolled-up stats |
POST /api/v2/flare/campaigns/{id}/send | Send/schedule a campaign (runs the 10DLC content scan first) |
GET /api/v2/flare/campaigns/{id}/messages | .../analytics | Per-message status and delivery analytics |
GET / POST /api/v2/flare/contact-lists | List and create contact lists / segments |
GET / PATCH / DELETE /api/v2/flare/contact-lists/{id} | Fetch, update, and delete a list |
GET / POST / DELETE /api/v2/flare/contact-lists/{id}/contacts | Read and manage list membership |
Nova — voice calling (/api/v2/nova/*)
Section titled “Nova — voice calling (/api/v2/nova/*)”| Method & path | Does |
|---|---|
POST /api/v2/nova/calls | Click-to-call (answer-first callback) |
GET /api/v2/nova/calls | List Nova call history (CDRs) — direction / number / date filters |
GET /api/v2/nova/calls/{id} | Fetch a single CDR |
GET /api/v2/nova/numbers | List your phone numbers (DIDs) |
GET /api/v2/nova/extensions | List your extensions |
GET /api/v2/nova/voicemails | List voicemails (with an unread count) |
GET /api/v2/nova/voicemails/{id} | Fetch a single voicemail (with transcript) |
GET /api/v2/nova/voicemails/{id}/audio | Get a short-lived presigned URL to download the recording |
GET /api/v2/nova/calls/{id}/recording | Presigned URL to download a call recording (+ duration/recorded-at) |
GET /api/v2/nova/calls/{id}/transcription | The call’s transcript (entitlement-gated) + processing status |
Aura — AI voice agent (/api/v2/aura/*)
Section titled “Aura — AI voice agent (/api/v2/aura/*)”| Method & path | Does |
|---|---|
GET /api/v2/aura/calls | List agent calls — agent_id / outcome / date filters |
GET /api/v2/aura/calls/{id} | Fetch one agent call with its transcript, summary, and outcome |
GET /api/v2/aura/agents | List your voice agents (status + assigned numbers) |
GET /api/v2/aura/agents/{id} | Fetch one voice agent (+ its KB tags) |
GET / PUT /api/v2/aura/agents/{id}/kb | Read and set the agent’s knowledge-base tag-set |
The outbound-trigger endpoint (POST /api/v2/aura/calls) exists but is not generally available — it returns 403 until enabled.
Numbers — DID self-service (/api/v2/numbers/*)
Section titled “Numbers — DID self-service (/api/v2/numbers/*)”| Method & path | Does |
|---|---|
GET /api/v2/numbers | List your DID inventory (with limit metadata) |
GET /api/v2/numbers/search | Search available numbers — local (areaCode) or toll-free (type=tollfree) |
POST /api/v2/numbers/order | Buy a number — available by default (spends money; operators can disable it with the NUMBERS_PUBLIC_ORDER_ENABLED=false kill-switch) |
PUT /api/v2/numbers/{id}/e911 | Register or update E911 for one of your numbers |
Cross-product
Section titled “Cross-product”| Method & path | Does |
|---|---|
GET /api/v2/me | Identify the key’s organization |
GET /api/v2/events | Discover the valid webhook event catalog |
GET / POST / DELETE /api/v2/webhooks | List, create, and delete outbound webhook endpoints |
List endpoints take page (default 1) and limit (default 50, max 200) and return a pagination block. Four high-volume list endpoints — Flare messages & contacts, Nova calls, and webhooks — also support keyset pagination: pass a response’s next_cursor back as cursor to walk large result sets without drift (in cursor mode page/total/totalPages are null). See Pagination for the full matrix.
Subscription tier & scopes
Section titled “Subscription tier & scopes”API access is subscription-tier gated: the Flare messaging/contacts/campaigns surface requires Ultra, and the Nova/Aura calling surface requires Enterprise. The number self-service endpoints use the account’s Flare API entitlement. A key on a plan without API access gets a 403 with a plain “API access is not available on your current plan. Please upgrade to access the API.” message.
Keys also carry scopes — e.g. sms:send, calls:read, aura:read, numbers:read. Scopes and the tier gate are two independent checks: a key must hold the endpoint’s required scope and be on an entitled tier. Scope enforcement is fail-closed: calling an endpoint without its required scope (or *) returns 403 insufficient_scope, and a key with no scopes — including an unscoped or legacy key — is denied every scoped endpoint rather than treated as full access.
Number ordering (POST /api/v2/numbers/order) is available by default — it spends money, so operators can disable it account-wide with the NUMBERS_PUBLIC_ORDER_ENABLED=false kill-switch (a 403 while disabled). Outbound calling (POST /api/v2/nova/calls, POST /api/v2/aura/calls) places calls at API velocity and is gated behind per-feature switches — it returns 403 until enabled for your organization. Search, inventory, E911, and every read endpoint are available today. If you need a gated capability, tell support; the public surface is prioritized by demand.
Click-to-call (Nova)
Section titled “Click-to-call (Nova)”POST /api/v2/nova/calls places a call with an answer-first callback: SIPSTACK rings your own extension (from) for about 2 rings, and only once you answer does it dial the destination (to) — exactly as if you’d picked up your deskphone and dialed. This means:
- No voicemail. An unanswered
fromleg fails cleanly after ~2 rings; it never rolls to voicemail, and the destination is never dialed. - Offline device → nothing happens. If your extension is not registered, the call fails at the switch with no destination call placed.
- The outbound caller-ID and all outbound gating come from your extension’s own dialplan — a caller cannot present another number.
Click-to-call must be enabled for API access on your organization; until it is, the endpoint returns 403. The Nova read endpoints (calls, numbers, extensions, voicemails) are available without that switch.
Sending SMS
Section titled “Sending SMS”Today outbound SMS is the most common entry point:
curl -s -X POST https://api.sipstack.com/api/v2/messages/send \ -H "x-api-key: sk_live_your_api_key" \ -H "Content-Type: application/json" \ -d '{ "to": "+14165550100", "from": "+16135550200", "message": "Hi! Your appointment is confirmed for tomorrow at 2pm." }'Response:
{ "success": true, "message": "Message sent successfully", "data": { "messageId": "msg_...", "to": "+14165550100", "from": "+16135550200", "status": "sent" }}from is optional — if omitted, your organization’s first active SMS-capable number is used. API access is plan-gated; if your plan does not include it the endpoint returns 403.
Sending MMS
Section titled “Sending MMS”An MMS may omit the text body. Note that an MMS bills a flat minimum of 4 SMS segments regardless of body length — and regardless of how the media arrived.
There are three ways to attach media, and they can be combined. Whichever you use, the total number of media parts per message is at most 10.
1. Hosted URLs — you host it, the carrier fetches it
Section titled “1. Hosted URLs — you host it, the carrier fetches it”Add a media array of absolute HTTPS URLs (also accepted as mediaUrls, or a single mediaUrl string; each URL is at most 2048 characters):
curl -s -X POST https://api.sipstack.com/api/v2/messages/send \ -H "x-api-key: sk_live_your_api_key" \ -H "Content-Type: application/json" \ -d '{ "to": "+14165550100", "message": "Here is your receipt.", "media": ["https://cdn.example.com/receipt.jpg"] }'2. Upload a file — multipart/form-data
Section titled “2. Upload a file — multipart/form-data”Post the file itself and skip hosting it. Send each file as a file part under the field name media, repeating the field once per file; every other field goes in the same form as an ordinary text field. Do not set Content-Type yourself — let curl set the multipart boundary:
curl -s -X POST https://api.sipstack.com/api/v2/messages/send \ -H "x-api-key: sk_live_your_api_key" \ -F "to=+14165550100" \ -F "from=+16135550200" \ -F "message=Here is your receipt." \ -F "media=@receipt.png;type=image/png" \ -F "media=@catalogue.jpg;type=image/jpeg"Hosted links can ride along in the same form — put them in mediaUrls (repeat the field once per URL), and they count toward the same limit of 10:
curl -s -X POST https://api.sipstack.com/api/v2/messages/send \ -H "x-api-key: sk_live_your_api_key" \ -F "to=+14165550100" \ -F "message=Your receipt and our new catalogue." \ -F "media=@catalogue.jpg;type=image/jpeg" \ -F "mediaUrls=https://cdn.example.com/receipt.jpg"3. Upload a file — base64 in the JSON body
Section titled “3. Upload a file — base64 in the JSON body”When multipart is awkward for your client, send mediaFiles — an array of file objects (a single object is also accepted):
curl -s -X POST https://api.sipstack.com/api/v2/messages/send \ -H "x-api-key: sk_live_your_api_key" \ -H "Content-Type: application/json" \ -d '{ "to": "+14165550100", "message": "Here is your receipt.", "mediaFiles": [ { "data": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==", "contentType": "image/png", "filename": "receipt.png" } ] }'data may also be a full data URI ("data:image/png;base64,iVBORw0KGgo..."), in which case contentType is optional and is read from the URI. filename is always optional and has no effect on delivery.
Upload limits and accepted types
Section titled “Upload limits and accepted types”| Limit | Value |
|---|---|
| Media parts per message | 10 — hosted URLs and uploads combined |
| Size of one uploaded file | 3,500,000 bytes (3.5 MB) |
| Size of all uploaded files in one message | 3,500,000 bytes (3.5 MB) total |
| Accepted upload types | image/jpeg, image/png, image/gif — and only these |
The size ceiling is Bandwidth’s documented MMS limit: a larger payload cannot be delivered by the carrier, so SIPSTACK rejects it up front rather than failing at the carrier.
The declared Content-Type must be in the allowlist and must match the file’s actual magic bytes. A mismatch is rejected with 400 — a file is never silently relabelled or transcoded. Video and audio MMS, and image/webp, are not currently accepted. Hosted URLs (option 1) are not type-checked by SIPSTACK; the carrier fetches and validates them.
What comes back
Section titled “What comes back”Uploaded media is stored in your organization’s regional object storage and handed to the carrier as a short-lived signed URL. Nothing is stored until the message passes validation, entitlement and quota checks — a rejected send stores nothing.
The media_urls in the send response and in GET /api/v2/messages / GET /api/v2/messages/{id} are time-limited signed URLs, valid for 24 hours, for uploaded media. Hosted URLs are returned exactly as you supplied them. Re-fetch the message for a fresh signed URL rather than storing one.
Requirements and errors
Section titled “Requirements and errors”Uploading uses the exact same authorization, gating and metering as the hosted-URL path — nothing new to enable. As with any Flare send, you need the sms:send scope, a Flare Pro-or-above plan with an active or trialing Flare subscription, and a from number that is a Flare A2P (team inbox) number. A failure of any of those returns 403; an exhausted messaging quota returns 429.
Media validation failures return 400 with a machine-readable code:
{ "success": false, "code": "MEDIA_TOO_LARGE", "error": "Media exceeds the 3,500,000 byte limit" }| Code | Cause |
|---|---|
TOO_MANY_MEDIA | More than 10 media parts (hosted + uploaded) |
MEDIA_TOO_LARGE | A single file, or the total of all uploaded files, is over 3,500,000 bytes |
MEDIA_TYPE_NOT_ALLOWED | Declared type is not image/jpeg, image/png or image/gif |
MEDIA_TYPE_MISMATCH | The file’s bytes are not what the declared type claims |
MEDIA_MALFORMED | Empty file, invalid base64, or a malformed mediaFiles entry |
Partner API
Section titled “Partner API”There is no partner API today. Partner operations — statements, earnings, customer management — are available in the Partner Portal UI; the endpoints powering it are portal-internal (/v2/portal/partner/...) and carry the same may-change-without-notice caveat as the rest of the portal API. If a read-only partner API (statements, earnings, customers) would unblock your workflow, tell support — demand shapes what gets published.
Request Format
Section titled “Request Format”Request bodies are JSON with the Content-Type: application/json header.
Response Conventions
Section titled “Response Conventions”Successful responses generally wrap their payload:
{ "success": true, "data": { ... } }Errors come in two shapes depending on the endpoint:
// Central error handler (most portal endpoints)// `error` duplicates `message`; there is no `statusCode` field in the body.{ "error": "Token expired", "message": "Token expired", "code": "TOKEN_EXPIRED" }
// Inline errors (messaging API and some portal endpoints){ "success": false, "error": "Recipient phone number (to) is required" }Always branch on the HTTP status code first, then inspect the body. See Error Codes for the full list of machine-readable code values.
Rate Limits
Section titled “Rate Limits”Requests are rate-limited per client; sensitive endpoints (login, password reset, sending) have stricter limits, and SMS sending is also subject to your plan’s message allowance. See Rate Limits.
Health & Versions
Section titled “Health & Versions”GET /v2/health— service health and running API version- The Switchboard footer shows the running portal and API versions (e.g.
Switchboard v2.6.x · API v2.9.x) — include them in bug reports - Status page: status.sipstack.com
Where to Go Next
Section titled “Where to Go Next”- Authentication — login flow, tokens, API keys
- API Reference — endpoint documentation from the OpenAPI spec
- Webhooks — receive events instead of polling
- Support — open a ticket from Switchboard (avatar menu → Support)