Making requests
The conventions every DialStack API request follows: where to send it, how to authenticate, how to say which customer it's for, and how to retry safely.
Base URL and authentication
All requests go to https://api.dialstack.ai, under the /v1 path. Request and response bodies are JSON.
Authenticate with a bearer token in the Authorization header:
curl https://api.dialstack.ai/v1/accounts \
-H "Authorization: Bearer sk_test_YOUR_SECRET_KEY"
Your backend uses your secret API key. Code that runs in a browser or on a device uses a short-lived session or user token instead. Which key acts on live or sandbox data, and how each token is minted, is covered in Authentication.
Choosing the account: DialStack-Account
Your secret key is scoped to your whole platform, so most requests also have to say which of your customers' accounts they act on. Pass the account's ID in the DialStack-Account header:
curl https://api.dialstack.ai/v1/users \
-H "Authorization: Bearer sk_test_YOUR_SECRET_KEY" \
-H "DialStack-Account: YOUR_ACCOUNT_ID"
Each operation in the API reference says whether it takes the header:
- Account-scoped operations (users, phone numbers, dial plans, calls, and most others) require it. Leaving it out returns
400. - Platform-wide operations don't take it. Creating and listing accounts, and minting session and user tokens (where the account or user goes in the request body), are examples.
- A few platform-wide operations accept it optionally, such as webhook endpoints: without the header they act on your platform's resources, and with it on that account's.
An account that belongs to another platform returns 403. An account from the other environment (a sandbox account with a live key, or the reverse) returns 404, the same as an account that doesn't exist.
Session tokens and user tokens are already bound to one account, so requests made with them don't need the header.
IDs
IDs are opaque strings of at most 255 characters. Store them exactly as the API returns them, and don't parse them or rely on their format.
Retries and idempotency
Most DialStack operations are not idempotent, and the API has no general Idempotency-Key mechanism. Whether a retry is safe depends on the operation:
- Reads (
GET) are always safe to retry. - Updates and deletes of a specific resource (
POST /v1/<resource>/<id>,DELETE /v1/<resource>/<id>) set a resource to a state, so repeating one usually leaves the same result. - Creates (
POSTto a collection) are not deduplicated. If a create times out or returns a5xx, the resource may already exist. Check for it (list it, or wait for its webhook) before you retry, or you may end up with two. - Placing a call (
POST /v1/calls) is the case to be most careful with. It takes no idempotency key, so a blind retry after an ambiguous failure can place a second call. Confirm against thecall.initiatedwebhook or the call log first. The reference for that operation explains which of its errors are safe to retry.
Where an operation guards against repeats itself, its reference says so. Hardware order checkout, for example, never debits the same order twice (see Error codes).
The Server SDK retries 5xx responses, 429 responses and timeouts on its own, up to maxNetworkRetries times (2 by default), for every method, including creates. Its idempotencyKey option sends an Idempotency-Key header, but no operation in the API reference reads that header, so setting it doesn't make a retry safe. For a create you don't want repeated, such as placing a call, pass maxNetworkRetries: 0 and handle the failure yourself:
await dialstack.calls.create(
{ user: user.id, dial_string: '+16175551234' },
{ dialstackAccount: account.id, maxNetworkRetries: 0 }
);
Webhook deliveries can arrive more than once. Deduplicate on the event's id; see Webhook events.
Rate limits
Some operations have limits of their own, and return 429 Too Many Requests when you exceed them:
| Operation | Limit | Retry-After |
|---|---|---|
Real-time event streams (GET /v1/events, GET /v1/presence) | Concurrent open connections per platform | Yes: seconds to wait before reconnecting |
Sending a fax (POST /v1/faxes) | A rolling hourly limit per account | No: back off and retry later |
When a response carries Retry-After, wait at least that many seconds before trying again. For a streaming connection, closing streams you no longer need also frees capacity.
Handling errors
Every error response has the same JSON shape, with a human-readable error and, where one exists, a stable machine-readable code. The Error codes page lists them.
- Branch on
code, not onerror. Theerrorstring may be reworded at any time. - When there is no
code, decide on the HTTP status alone. Log theerrorstring for debugging, but don't parse it. - On
429, respectRetry-Afterwhen the response includes it. - On
5xx, back off exponentially. Start at about 1 second, double up to about 60 seconds, and stop after about 5 attempts. Before retrying a create, read Retries and idempotency above.
Pagination
List operations return pages and a URL for the next one. See Pagination.