Reference

API Conventions

One section captures the rules that apply across every Throttle endpoint: naming, error shapes, pagination, idempotency, and workspace environments. When in doubt, this page wins over the per-endpoint docs.

Field naming: camelCase, single shape

Every request and response field on the wire is camelCase. One name per concept — no aliases, no alternate forms. Type generation from the OpenAPI spec is the intended workflow.

Request body
// POST /api/v1/carts request body
{
  "applicationId": "e7efb0a6-892e-46b2-97ab-296bb04c5b29",
  "currency": "USD"
}
Response body
// POST /api/v1/carts response
{
  "data": {
    "id": "9f6e...",
    "workspaceId": "6659b411-9cd7-40ac-9a73-1e7801d89f55",
    "applicationId": "e7efb0a6-892e-46b2-97ab-296bb04c5b29",
    "currency": "USD",
    "netN": null,
    "lineItems": []
  },
  "meta": {
    "requestId": "..."
  }
}
Migrating from older snake_case responses
Throttle public request and response bodies use camelCase. Use applicationId, customerId, lineItems, unitPrice, and externalId; stale snake_case or store aliases are rejected with a validation error. Generated [email protected]+ already uses the current shape.

Historical store terminology may still appear in dashboard labels or internal database migrations, but it is not exposed on the public wire.

Errors

Every non-2xx response follows the same envelope:

Error envelope
{
  "error": {
    "code": "missing_application_id",         // stable, machine-readable
    "message": "applicationId is required",   // human-readable
    "details": [                              // optional, field-level
  { "field": "applicationId", "code": "required", "message": "Required" }
    ]
  }
}

error.code is the contract — use it for programmatic handling. Reach for error.message only for surfacing a human-readable string in your UI. Field-level details identify unrecognized keys and may include a suggestion when the server can identify the intended canonical camelCase field:

Validation error
{
  "error": {
    "code": "validation_error",
    "message": "Request body validation failed",
    "details": [{
  "path": "",
  "code": "unrecognized_keys",
  "message": "Unsupported field(s): unexpectedField",
  "received": ["unexpectedField"]
    }]
  }
}

Typed errors in the SDK

The envelope is published as a named ErrorEnvelope model, so @usethrottle/api-client exposes it as a type rather than leaving you to re-parse the body:

text
import { ApiError } from '@usethrottle/api-client';
import type { ErrorEnvelope } from '@usethrottle/api-client';

try {
  await QuotesService.postApiV1QuoteRequests({ formToken, email });
} catch (err) {
  if (err instanceof ApiError) {
    err.code;     // 'too_fast'  — stable, branch on this
    err.message;  // the server's own explanation, not a canned status string
    err.status;   // 400
    err.details;  // optional structured context
    const body = err.body as ErrorEnvelope; // fully typed
  }
}

ApiError extends ThrottleError, so one catch covers every Throttle SDK. err.message is the message the server sent; if a response carries no message, it falls back to a description of what that status means on this API, and the raw description stays available as err.statusDescription.

Pagination

List endpoints accept ?limit + ?cursor and return:

Paginated response
{
  "data": [...],
  "meta": {
    "pagination": {
  "cursor": "<opaque>",   // pass back as ?cursor= to fetch next page
  "hasMore": true,
  "limit": 25
    }
  }
}

When hasMore is false, cursor is null. You can use if (cursor) as a "more pages?" check without re-reading hasMore.

Identifier prefixes

Resource ids are UUID v7 strings. API keys, embed tokens, and session ids carry a short prefix that encodes their type and, for keys, the workspace environment slug:

  • sk_<environment-slug>_* — secret key for one workspace environment.
  • pk_<environment-slug>_* — publishable key for one workspace environment.
  • sk_live_* and pk_live_* — production environment keys.
  • whsec_* — webhook signing secret.

Timestamps and timezones

Every timestamp in an API request or response is ISO 8601 in UTC, always, and is unaffected by any setting. Parse it as an instant and render it wherever you like.

Separately, each application carries a timezone — an IANA zone name such as America/Los_Angeles. That setting is what Throttle's own rendered surfaces use: the dashboard, quote PDFs, and transactional emails. null means UTC.

The API contract does not change
Setting a timezone never changes an API payload. If you need local time in your own UI, read timezone from the application and format the UTC instant yourself.

Like currency and branding, the timezone is stored per workspace environment, so a store can read its test data in one zone and its production data in another.

Quote and order numbers

quoteNumber and orderNumber are human-facing labels that count up per application, per workspace environment. Two applications each issue their own Q-1; neither ever sees a gap caused by the other.

Both the prefix and the next number are configurable, from the dashboard or over the API:

text
// PATCH /api/v1/workspaces/{workspaceId}/applications/{applicationId}
{
  "timezone": "America/Los_Angeles",
  "quoteNumberPrefix": "Q-",
  "nextQuoteSeq": 187,
  "orderNumberPrefix": "ORD-",
  "nextOrderSeq": 536
}
  • quoteNumberPrefix / orderNumberPrefix — 1–12 characters of letters, digits, - or _. Defaults are Q- and ORD-.
  • nextQuoteSeq / nextOrderSeq — the next number to issue. It must be greater than every number this environment has already issued under that prefix; otherwise the request fails with 409 starting_number_already_used.
Do not parse these for ordering or counting
A document number is a display label, not a cursor. Sequences are per store, restart under a new prefix, and can be moved forward deliberately. Sort and paginate on createdAt plus the id instead, and never infer volume from a number.

Test environments keep their own counters, so QA traffic never consumes a number a live document would otherwise get.