Skip to main content

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:

Bash
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:

Bash
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 (POST to a collection) are not deduplicated. If a create times out or returns a 5xx, 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 the call.initiated webhook 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:

TypeScript
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:

OperationLimitRetry-After
Real-time event streams (GET /v1/events, GET /v1/presence)Concurrent open connections per platformYes: seconds to wait before reconnecting
Sending a fax (POST /v1/faxes)A rolling hourly limit per accountNo: 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 on error. The error string may be reworded at any time.
  • When there is no code, decide on the HTTP status alone. Log the error string for debugging, but don't parse it.
  • On 429, respect Retry-After when 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.