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 not published to the public npm registry. It is distributed by DUST and available to integration customers on request (contact support@dustidentity.com). npm install @dustid/apid-client will not find it.
If you’d rather not take the 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 — and call the API with plain fetch. The quickstart does it that way so it runs with nothing installed.
Prerequisites if you do take it
Section titled “Prerequisites if you do take it”Check these before planning around the client:
| Requirement | Detail |
|---|---|
| Runtime | A global fetch: Node.js 18+, Bun, or Deno. You can inject your own implementation through the fetcher option (proxies, retries, test doubles). |
| TypeScript toolchain | The package’s entry points resolve to TypeScript source, not compiled JavaScript. Your bundler or runtime must transpile it — plain node dist/app.js against untranspiled source will not work. |
| Runtime dependencies | The client is not dependency-free: it declares arktype (used to validate error payloads) plus DUST-internal helper packages, which come with the distribution. Budget for them in any vendoring, licence review, or air-gapped install. |
| Execution location | Server-side only — see the caution at the end of this page. |
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 — the same end-to-end flow with plain
fetch, so you can run it before deciding on a dependency. - Request conventions — the headers and error contract the client implements.
- Errors and scan outcomes — the codes behind
ApiError, and the outcome tables for scanning. - Full API reference — every endpoint and schema.