Docs menu

Conventions

Every /api/v1 endpoint shares the same rules for auth, errors, rate limits, and pagination. Learn them once here; they hold everywhere.

Authentication

Authenticate with an API key as a Bearer token: Authorization: Bearer dk_.... Keys are never accepted as query parameters. A missing or invalid key returns a uniform 401 unauthorized — the same response whether the key is absent, malformed, or revoked (no oracle). Test keys are prefixed dk_test_ and live keysdk_live_; the prefix decides which quota, mode, and billing a request draws.

bash
curl -sS "$DARB_API_BASE/api/v1/usage" \
  -H "Authorization: Bearer $DARB_KEY"

The exact security scheme is published in theAPI reference.

Errors

Every non-2xx response is one envelope: a nested error object with a machinecode, a human message, and optional structured details. Thecode is the contract you branch on; the message is advisory and may change without notice.

json
{
  "error": {
    "code": "quota_exceeded",
    "message": "Monthly route quota exhausted.",
    "details": { "metric": "routes", "used": 100, "limit": 100 }
  }
}

The ErrorEnvelope schema is documented on every operation in theAPI reference — this table lists the discriminators each status can carry:

StatuscodeMeaning
401unauthorizedMissing or invalid API key.
402quota_exceeded, subscription_locked, subscription_canceledQuota or subscription refusal.
403test_mode_unavailable, email_unverified, paid_plan_required, live_key_browser_forbiddenRefused (paid-plan gate, unverified email, or a live key used from a browser).
404not_foundUnknown or foreign resource id — no cross-tenant oracle.
405method_not_allowedWrong HTTP method for the path.
409idempotency_key_conflict, webhook_limit_reachedA reused Idempotency-Key with a different body, or the per-tenant webhook cap.
422validation_errorRequest failed validation; field errors under details.errors.
429rate_limitedRate limit exceeded; honest Retry-After + X-RateLimit-* headers.
500internal_errorUnexpected server error; no internals leak.
503service_unavailableRate-limit backend unavailable; retry after Retry-After.

Rate limits

Each API key is limited to 60 requests per minute. Every successful response carries X-RateLimit-Limit and X-RateLimit-Remaining; a throttled request returns 429 rate_limited with an honest Retry-After alongside theX-RateLimit-* headers. A 429 reserves no quota — being rate-limited never spends a route, WhatsApp, or SMS unit (a 429 is not a402).

http
HTTP/1.1 429 Too Many Requests
Retry-After: 30
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 0

Asynchronous jobs (202 + poll)

Spend endpoints are asynchronous. POST /api/v1/routes andPOST /api/v1/campaigns return 202 Accepted immediately with anid and an in-progress status (optimizing /collecting). Poll the matching GET until the status reaches a terminal value (optimized / failed for routes;completed / expired for campaigns). There is no callback requirement — though webhooks can push the same completion events.

Quotas & billing

Each spend reserves a unit before the work runs and refunds it on failure. When a metric is exhausted the API returns 402 quota_exceeded withdetails.metric/used/limit; a locked or canceled subscription returns 402 subscription_locked / subscription_canceled. Test-mode and live-mode quotas are entirely separate: a dk_test_ key draws the test caps (100 routes, 200 WhatsApp, 100 SMS per month) and spends no money, while adk_live_ key draws your plan's live quota. Read either throughGET /api/v1/usage — its livemode flag tells you which namespace the numbers describe.

Not found

An unknown id and a resource that belongs to another tenant both return the same404 not_found — the API never distinguishes "does not exist" from "not yours", so a key can't probe for foreign resources. Cross-mode reads follow the same rule: adk_test_ key polling a live route (or vice versa) gets 404 not_found.

Idempotency

Send an Idempotency-Key header on a spend POST to make retries safe. A replay with the same key and body echoes the original resource (with anIdempotent-Replay: true response header) and reserves no new quota; reusing a key with a different body returns 409 idempotency_key_conflict. The key is optional and additive — omit it and the create behaves exactly as before.

Pagination

The public API does not paginate yet. Its only list endpoint,GET /api/v1/webhooks, returns the full set of your subscriptions as a bare JSON array (newest first), bounded by the per-tenant subscription cap — there is nonext_cursor, no cursor query parameter, and no wrapping envelope. Read the array directly.

Should a future list endpoint need paging, it will add keyset ("Load more") paginationadditively — an optional cursor query parameter and anext_cursor field alongside the existing data, under the same additive-onlyv1 promise described below. Until then, don't write cursor-handling code against/api/v1 responses.

Versioning

The API is versioned in the path (/api/v1). Changes within v1 areadditive only — new optional request fields, new response fields, new endpoints — and are enforced by a contract-diff gate on every release, so an integration built againstv1 keeps working. A breaking change would ship under a new version path, never insidev1.