Skip to content

Request conventions: context headers, errors, localization

Every org-scoped /api/v1/* endpoint shares the same request contract: a bearer token, two context headers that pick the organization and team you act in, JSON bodies (file uploads use multipart or tus instead), one error shape, and cursor pagination on list endpoints. This page is the contract; per-domain pages assume it.

Almost everything in the DUST API belongs to an organization, and within it a team. You choose which org/team a request acts in with two headers:

Header Required Value
Dust-Ctx-Org-Id Yes, on org-scoped endpoints Organization UUID.
Dust-Ctx-Team-Id No Team UUID. Defaults to the organization’s root team when omitted.
Terminal window
curl -fsS "https://apid.dustid.io/api/v1/threads" \
-H "Authorization: Bearer $DUST_TOKEN" \
-H "Dust-Ctx-Org-Id: $DUST_ORG_ID" \
-H "Dust-Ctx-Team-Id: $DUST_TEAM_ID"

Details that matter in practice:

  • Header values must be UUIDs; a malformed value is rejected with 400 INVALID_REQUEST before the endpoint runs.
  • X--prefixed variants (X-Dust-Ctx-Org-Id, X-Dust-Ctx-Team-Id, and the legacy pair) are accepted as aliases.
  • Endpoints that require context but don’t receive it fail with error codes ORG_ID_REQUIRED or TEAM_ID_REQUIRED.
  • A few endpoints are user-scoped and need no context — GET /api/v1/me is the common one.

Authorization is evaluated for your user acting in the team named by the headers. The same call with a different Dust-Ctx-Team-Id can return different results: what you can list, read, and write is what that team can see — its own records plus whatever has been shared with it. Sending a context you don’t belong to doesn’t escalate anything; requests are checked against your actual memberships. See Teams and sharing.

The optional Dust-Ctx-Locale header selects the language for server-generated, user-facing text — most visibly error message strings:

Dust-Ctx-Locale: zh-CN

Supported locales are en (default) and zh-CN. When the header is absent the server falls back to the standard Accept-Language header, then to English. Error codes are stable identifiers and never localized — branch on code, display message.

Failed requests return a JSON body with a single, consistent shape:

{
"code": "UNAUTHORIZED",
"message": "You are not authorized to perform this action",
"status": 401,
"detail": { }
}
Field Type Meaning
code string Stable, machine-readable error code. Branch on this.
message string Human-readable description, localized per Dust-Ctx-Locale.
status number Mirrors the HTTP status code.
detail object (optional) Extra context for this error, e.g. validation specifics.

Codes you will encounter early:

Code Typical status When
INVALID_REQUEST 400 Malformed body, query, or header (validation detail in detail).
UNAUTHORIZED 401 Missing, expired, or invalid bearer token.
FORBIDDEN 403 Authenticated, but this team context may not do that.
NOT_FOUND / NO_DATA_FOUND 404 No such record visible in this context.
ORG_ID_REQUIRED / TEAM_ID_REQUIRED 400 Context header missing on a scoped endpoint.
THREAD_DATA_CONFLICT 409 Optimistic-concurrency conflict: your view of the thread was stale.

Every response also carries an x-request-id header. Log it, and include it when contacting support — it pinpoints your request in server traces.

List endpoints (threads, bundles, files, events, templates, …) use cursor pagination:

  • Request: pageSize (page length) and cursor (opaque string from a previous page) query parameters.
  • Response: the items array plus optional next and prev cursor strings. A missing next means you’re on the last page.
Terminal window
# First page
curl -fsS "https://apid.dustid.io/api/v1/threads?pageSize=50" \
-H "Authorization: Bearer $DUST_TOKEN" \
-H "Dust-Ctx-Org-Id: $DUST_ORG_ID"
# Follow the cursor
curl -fsS "https://apid.dustid.io/api/v1/threads?pageSize=50&cursor=$NEXT" \
-H "Authorization: Bearer $DUST_TOKEN" \
-H "Dust-Ctx-Org-Id: $DUST_ORG_ID"
{
"threads": [ ... ],
"next": "eyJjcmVhdGVkQXQiOi...",
"prev": "eyJjcmVhdGVkQXQiOi..."
}

Cursors are opaque — persist and replay them, never parse them. List endpoints that support ordering take order (asc/desc) and an endpoint-specific orderCol (for threads: createdAt, updatedAt, name).

Every operation requires one of three privilege levels, and the path prefix tells you which before you read a single schema:

Prefix Who can call it Notes
/api/v1/org/* Organization admins — users with the admin (or owner) role in the Organization named by Dust-Ctx-Org-Id Team creation, team updates, memberships
/api/v1/connections/* Team admins — admins of the acting Team named by Dust-Ctx-Team-Id Connection lifecycle and amendments
everything else Members of the request context, unless the operation says otherwise Standard feature surface

Each operation also carries an x-required-role extension in the OpenAPI spec (member, publisher, team-admin, or org-admin) — treat that as the authoritative per-operation policy; an operation without the annotation requires member. publisher is a grant on a Team membership rather than a tier of its own: it is required to make a Team’s data publicly readable, and Team admins always have it. Calling an operation above your tier returns 403 FORBIDDEN regardless of payload.

  • Requests are Content-Type: application/json unless an endpoint explicitly takes multipart form data (identifier scans on /api/v1/tags/*, file uploads).
  • IDs are RFC 4122 UUID strings (threadId, eventId, organization and team ids, …). Treat them as opaque.
  • Timestamps (createdAt, updatedAt, archivedAt, …) are UTC timestamp strings.
  • Writes are evented: mutating a thread appends to its event history rather than silently overwriting — reads like GET /api/v1/threads/{thread_id} return { thread, events }.