TypeScript client (@dustid/apid-client)
@dustid/apid-client is the typed TypeScript client for the DUST API. It is the same client the DICE web application uses in production, and its request/response types are generated directly from the API’s OpenAPI spec, so it stays in lockstep with the server.
Availability
Section titled “Availability”The package is currently distributed by DUST rather than published to the public npm registry — it ships with the platform and is available to integration customers on request (contact support@dustidentity.com). If you’d rather not take a dependency, you can get the same type safety by generating types from the OpenAPI spec — that is exactly how this client’s own types are produced.
The client has no runtime dependencies beyond a fetch implementation (built into Node 18+, Bun, Deno, and browsers; you can inject your own via the fetcher option).
Construct a client
Section titled “Construct a client”import { ApidClient } from "@dustid/apid-client";
const client = new ApidClient({ baseUrl: "https://apid.dustid.io", bearerToken: token, // Authorization: Bearer <token> organizationId: orgId, // sent as Dust-Ctx-Org-Id teamId: teamId, // sent as Dust-Ctx-Team-Id});The full options type:
type ApidClientOptions = { baseUrl: string; bearerToken?: string; organizationId?: string; // Dust-Ctx-Org-Id header teamId?: string; // Dust-Ctx-Team-Id header fetcher?: typeof globalThis.fetch; // custom fetch (proxies, testing) defaultHeaders?: HeadersInit | (() => HeadersInit); // e.g. Dust-Ctx-Locale logger?: Logger; // debug/error request logging};organizationId and teamId map onto the context headers; when teamId is omitted the server defaults to the organization root team. Use defaultHeaders for anything extra you want on every request — for example localized error messages:
const client = new ApidClient({ baseUrl, bearerToken, organizationId, defaultHeaders: { "Dust-Ctx-Locale": "zh-CN" },});Switching context or token
Section titled “Switching context or token”Clients are immutable; two helpers return a re-configured copy, which makes per-request or per-user scoping cheap:
const asOtherTeam = client.withContext({ teamId: otherTeamId });const asFreshToken = client.withToken(newBearerToken);Resources and calls
Section titled “Resources and calls”The client groups endpoints into resources: client.me, client.threads, client.bundles, client.files, client.tags (Identifier operations — the /api/v1/tags/* endpoints), client.teams, client.sharing, client.templates, client.events, client.relations, client.threadLinks, client.assemblies, client.transfers, client.slices, client.imports, client.fabric, client.users, client.certificates, and client.certificateForms.
Method names mirror the reference. Two real examples with threads:
// GET /api/v1/threads — cursor-paginated listconst page = await client.threads.list({ pageSize: 50, q: "tire" });for (const thread of page.threads) { console.log(thread.threadId, thread.name);}if (page.next) { const nextPage = await client.threads.list({ pageSize: 50, cursor: page.next });}// POST /api/v1/threads — create, unwrapped to the single created recordconst created = await client.threads.createOne({ type: "single", thread: { name: "Tire SZ3J-11-ZJ17" }, data: [{ name: "Serial Number", type: "text", value: { text: "SZ3J-11-ZJ17" } }],});
// GET /api/v1/threads/{thread_id}const record = await client.threads.get(created.threadId);console.log(record.thread.name, record.events.length);threads.create returns the raw batch response ({ created, uploadResponses }); threads.createOne is a convenience that returns created[0] and throws if the server created nothing.
All request and response types are exported from the package root (ThreadCreateRequest, ThreadQueryResponse, ThreadGetResponse, …), along with the raw generated paths / components / operations types from the spec.
Error handling
Section titled “Error handling”Methods return the parsed JSON response on success and throw ApiError on any non-2xx status. ApiError carries the API’s standard error body:
import { ApiError } from "@dustid/apid-client";
try { await client.threads.get(threadId);} catch (error) { if (error instanceof ApiError) { // error.code stable error code, e.g. "NOT_FOUND", "UNAUTHORIZED" // error.status HTTP status number // error.message localized human-readable message // error.detail optional extra context (validation issues, etc.) // error.body the full { code, message, status, detail } payload if (error.code === "UNAUTHORIZED") { // token expired — re-exchange the API key and retry } } else { throw error; // network failure or non-JSON response }}Two edge behaviors worth knowing: 204/205 responses resolve to undefined, and a response that isn’t valid JSON throws a plain Error (not ApiError). If you pass a logger, every failed request is logged with its x-request-id for support correlation.
Alternative: generate your own types
Section titled “Alternative: generate your own types”The API serves its OpenAPI 3 spec at https://apid.dustid.io/api/openapi.json. openapi-typescript turns it into a fully typed paths/components definition you can use with plain fetch or any spec-driven fetch wrapper:
npx openapi-typescript@7 https://apid.dustid.io/api/openapi.json -o <generated-types-file>import type { paths } from "./dust-api";
type ThreadList = paths["/api/v1/threads"]["get"]["responses"]["200"]["content"]["application/json"];Remember to set the Authorization, Dust-Ctx-Org-Id, and Dust-Ctx-Team-Id headers yourself — see Request conventions. Regenerate the types whenever you pick up new API features; the spec at /api/openapi.json is always current for the server you fetched it from.
See also
Section titled “See also”- API quickstart — end-to-end flow using this client.
- Request conventions — the headers and error contract the client implements.
- Full API reference — every endpoint and schema.
