API-Schnellstart
Am Ende dieser Seite haben Sie einen API-Schlüssel gegen ein Bearer-Token ausgetauscht, die Organisation und das Team ermittelt, in denen Ihre Anmeldedaten agieren können, einen Datensatz erstellt und ihn samt Ereignisverlauf zurückgelesen.
Beide nachfolgenden Sprachvarianten sind vollständig und unabhängig voneinander: Alles wird definiert, bevor es verwendet wird, und keine übernimmt einen Schritt aus der anderen. Wählen Sie einen Tab aus und bleiben Sie darin.
Bevor Sie beginnen
Abschnitt betitelt „Bevor Sie beginnen“Sie benötigen:
- Einen Service-Account-API-Schlüssel, der von einem Organisationsadministrator ausgestellt wurde. Persönliche API-Schlüssel gibt es nicht — siehe Authentifizierung und API-Schlüssel, falls Sie noch keinen besitzen.
- Der Service Account muss Mitglied des Teams sein, in das Sie schreiben. Das Erstellen von Datensätzen erfordert eine aktuelle Mitgliedschaft im ausgewählten Team; Anmeldedaten auf Organisationsebene, die keinem Team angehören, können
/api/v1/melesen, aber keine Datensätze erstellen. Bitten Sie Ihren Administrator, den Service Account einem Team hinzuzufügen, wenn Schritt 3403 FORBIDDENzurückgibt. - curl-Variante:
curlundjq(die Beispiele analysieren damit JSON; wenn Siejqnicht installieren möchten, kopieren Sie die Werte manuell aus den Antworten). - TypeScript-Variante: eine Laufzeitumgebung, die TypeScript direkt ausführt und über ein globales
fetchverfügt — Node.js 22.18 oder neuer, Bun oder Deno. Führen Sie die Datei unter Node.js 18 oder 20 stattdessen mit einem Loader wietsxaus. Es müssen keine Pakete installiert werden: Die Beispiele verwenden ausschließlich einfachesfetch. Ein typisierter Client ist separat verfügbar, siehe TypeScript-Client.
Alle Anfragen werden an https://apid.dustid.io gesendet; die anderen Service-URLs finden Sie unter Umgebungen.
-
Umgebung einrichten
Abschnitt betitelt „Umgebung einrichten“Terminal-Fenster export APID_URL="https://apid.dustid.io"export DUST_API_KEY="your-service-account-key" # read this from your secrets managerSpeichern Sie die nachfolgende Datei als
quickstart.tsund führen Sie sie mitnode quickstart.ts,bun quickstart.tsoderdeno run --allow-net --allow-env quickstart.tsaus. Jeder Schritt ergänzt dieselbe Datei.quickstart.ts // Makes the file an ES module, which is what lets the top-level `await`s// below run. (A `.mts` extension, or "type": "module" in package.json,// does the same job.)export {};const apidUrl = "https://apid.dustid.io";const apiKey = process.env.DUST_API_KEY;if (!apiKey) throw new Error("Set DUST_API_KEY in the environment."); -
API-Schlüssel gegen ein Bearer-Token austauschen
Abschnitt betitelt „API-Schlüssel gegen ein Bearer-Token austauschen“API-Schlüssel werden niemals an
/api/v1/*-Endpunkte gesendet. Tauschen Sie den Schlüssel einmal überGET /api/auth/tokenaus, indem Sie ihn im Headerx-api-keyübergeben, und senden Sie das resultierende JWT bei jedem nachfolgenden Aufruf alsAuthorization: Bearer <token>.Terminal-Fenster curl -fsS "$APID_URL/api/auth/token" -H "x-api-key: $DUST_API_KEY"{ "token": "eyJhbGciOi...", "expiresIn": 900, "expiresAt": "2026-09-20T22:40:00.000Z" }Terminal-Fenster export DUST_TOKEN="$(curl -fsS "$APID_URL/api/auth/token" -H "x-api-key: $DUST_API_KEY" | jq -r '.token')"quickstart.ts type TokenResponse = { token: string; expiresIn?: number; expiresAt?: string };async function exchangeToken(): Promise<TokenResponse> {const response = await fetch(`${apidUrl}/api/auth/token`, {headers: { "x-api-key": apiKey! },});if (!response.ok) {throw new Error(`Token exchange failed: ${response.status} ${await response.text()}`);}return (await response.json()) as TokenResponse;}const { token, expiresIn, expiresAt } = await exchangeToken();console.log(`Token valid for ${expiresIn ?? "unknown"}s (until ${expiresAt ?? "unknown"})`);Die Antwort enthält
tokensowie — sofern das JWT selbst einen Ablaufzeitpunkt enthält —expiresIn(verbleibende Sekunden) undexpiresAt(ISO 8601). Lesen Sie die Gültigkeitsdauer aus der Antwort aus, anstatt sie fest im Code zu hinterlegen: Token sind derzeit kurzlebig (etwa 15 Minuten), und es gibt kein Refresh-Token. Ein lang laufender Auftrag muss den Austausch daher während der Ausführung erneut durchführen. Die vollständigen Regeln zur Gültigkeitsdauer, eine Caching-Implementierung und das Muster für eine einmalige Aktualisierung bei401finden Sie unter Authentifizierung → Token-Ablauf und -Aktualisierung. -
Ihre Organisation ermitteln
Abschnitt betitelt „Ihre Organisation ermitteln“GET /api/v1/meist einer der wenigen Endpunkte, die keine Kontext-Header benötigen. Er beschreibt die Anmeldedaten selbst: den Principal, die Organisationen, denen er angehört, und welche davon aktiv ist.Terminal-Fenster curl -fsS "$APID_URL/api/v1/me" -H "Authorization: Bearer $DUST_TOKEN"{"userId": "6a1f…","email": "sap-connector@example.com","name": "SAP Connector","activeOrganizationId": "b2c7…","organizations": [{ "id": "b2c7…", "name": "Anchor Electronics", "slug": "anchor-electronics", "roles": ["member"] }]}Terminal-Fenster # Prefer the active organization; fall back to the first membership.export DUST_ORG_ID="$(curl -fsS "$APID_URL/api/v1/me" -H "Authorization: Bearer $DUST_TOKEN" \| jq -er '.activeOrganizationId // .organizations[0].id')"echo "Organization: $DUST_ORG_ID"quickstart.ts type Organization = { id: string; name: string; slug: string; roles: string[] };type MeResponse = {userId: string;email: string;activeOrganizationId?: string | null;organizations: Organization[];};const auth = { Authorization: `Bearer ${token}` };const meResponse = await fetch(`${apidUrl}/api/v1/me`, { headers: auth });if (!meResponse.ok) {throw new Error(`/me failed: ${meResponse.status} ${await meResponse.text()}`);}const me = (await meResponse.json()) as MeResponse;const organizationId =me.activeOrganizationId ?? me.organizations[0]?.id;if (!organizationId) {throw new Error("This credential belongs to no organization — ask your admin.");}console.log(`Organization: ${organizationId}`); -
Ein Team auswählen (optional)
Abschnitt betitelt „Ein Team auswählen (optional)“Datensätze gehören zu einem Team innerhalb der Organisation. Sie haben zwei unterstützte Möglichkeiten:
- Nichts unternehmen. Lassen Sie
Dust-Ctx-Team-Idweg, und die API agiert im Stamm-Team der Organisation. Für eine Organisation mit nur einem Team ist das bereits der gesamte Schritt 4, und die Beispiele in Schritt 5 verwenden diesen Weg. - Ein Team angeben.
GET /api/v1/teamslistet die Teams auf, deren Mitglied Ihre Anmeldedaten sind, und zwar als{ "teams": [ … ], "total": n }; jedes enthältteamId,orgIdundname. Senden Sie das gewünschte Team alsDust-Ctx-Team-Id.
Terminal-Fenster curl -fsS "$APID_URL/api/v1/teams?pageSize=50" \-H "Authorization: Bearer $DUST_TOKEN" \-H "Dust-Ctx-Org-Id: $DUST_ORG_ID" \| jq '.teams[] | { teamId, name }'{ "teamId": "b2c7…", "name": "Anchor Electronics" }{ "teamId": "4e90…", "name": "Line 3 Receiving" }Terminal-Fenster # Optional. Leave DUST_TEAM_ID unset to use the organization's root Team.export DUST_TEAM_ID="4e90…"Jede nachfolgende Anfrage übergibt
-H "Dust-Ctx-Team-Id: ${DUST_TEAM_ID:-}". Ein leerer Wert wird genauso behandelt wie ein fehlender Header — es wird das Stamm-Team der Organisation verwendet. Daher funktioniert dasselbe Skript unabhängig davon, ob Sie die Variable festlegen.quickstart.ts type Team = { teamId: string; orgId: string; name: string | null };const teamsResponse = await fetch(`${apidUrl}/api/v1/teams?pageSize=50`, {headers: { ...auth, "Dust-Ctx-Org-Id": organizationId },});if (!teamsResponse.ok) {throw new Error(`/teams failed: ${teamsResponse.status} ${await teamsResponse.text()}`);}const { teams } = (await teamsResponse.json()) as { teams: Team[]; total: number };for (const team of teams) console.log(`${team.teamId} ${team.name ?? "(unnamed)"}`);// Optional. Leave DUST_TEAM_ID unset to act in the organization's root Team.const teamId = process.env.DUST_TEAM_ID;// Context headers for every call from here on. The Team header is present// only when a Team was chosen — an undefined value must not be sent.const context: Record<string, string> = {...auth,"Dust-Ctx-Org-Id": organizationId,...(teamId ? { "Dust-Ctx-Team-Id": teamId } : {}),}; - Nichts unternehmen. Lassen Sie
-
Einen Datensatz erstellen
Abschnitt betitelt „Einen Datensatz erstellen“Ein Datensatz ist der Eintrag für ein einzelnes Asset oder Objekt.
POST /api/v1/threadsmittype: "single"erstellt einen Datensatz;thread.nameist das einzige Pflichtfeld, und das optionale Arraydataenthält typisierte Felder.Terminal-Fenster curl -fsS "$APID_URL/api/v1/threads" \-H "Authorization: Bearer $DUST_TOKEN" \-H "Dust-Ctx-Org-Id: $DUST_ORG_ID" \-H "Dust-Ctx-Team-Id: ${DUST_TEAM_ID:-}" \-H "Content-Type: application/json" \-d '{"type": "single","thread": {"name": "Tire SZ3J-11-ZJ17","description": "Production asset"},"data": [{ "name": "Serial Number", "type": "text", "value": { "text": "SZ3J-11-ZJ17" } },{ "name": "Max PSI", "type": "number", "value": { "number": 51 } }]}' | tee /tmp/created.json | jq '.created[0] | { threadId, name }'{ "threadId": "0f13c0de-2f1a-4a2e-9f60-6d2f7b9f0a11", "name": "Tire SZ3J-11-ZJ17" }Terminal-Fenster export THREAD_ID="$(jq -r '.created[0].threadId' /tmp/created.json)"quickstart.ts type ThreadRecord = { threadId: string; name: string | null };const createResponse = await fetch(`${apidUrl}/api/v1/threads`, {method: "POST",headers: { ...context, "Content-Type": "application/json" },body: JSON.stringify({type: "single",thread: { name: "Tire SZ3J-11-ZJ17", description: "Production asset" },data: [{ name: "Serial Number", type: "text", value: { text: "SZ3J-11-ZJ17" } },{ name: "Max PSI", type: "number", value: { number: 51 } },],}),});if (!createResponse.ok) {const error = await createResponse.json();throw new Error(`create failed: ${error.code} — ${error.message}`);}const { created } = (await createResponse.json()) as { created: ThreadRecord[] };const threadId = created[0]?.threadId;if (!threadId) throw new Error("The server created no Thread.");console.log(`Created ${threadId}`);Der Status lautet
201 Created, und der Antwortkörper hat die Form{ "created": [ … ], "uploadResponses": [] }— eine Batch-Struktur, da derselbe Endpunkt mittype: "list"odertype: "raw"mehrere Datensätze gleichzeitig erstellt. Jeder Eintrag increatedist ein vollständiger Datensatz einschließlich seiner generiertenthreadId.Feldeinträge benötigen
typeundvalue; die Struktur vonvaluerichtet sich nach dem Typ:{ "text": "…" }fürtext,{ "number": 51 }fürnumber.nameist die Bezeichnung des Feldes. Die vollständige Liste der Feldtypen finden Sie im Leitfaden zu Datensätzen. -
Den Datensatz zurücklesen
Abschnitt betitelt „Den Datensatz zurücklesen“GET /api/v1/threads/{thread_id}gibt den Datensatz zusammen mit seinem Ereignisverlauf zurück — jeder Schreibvorgang wird erfasst, sodass die Audit-Trail bereits mit der Erstellung beginnt.Terminal-Fenster curl -fsS "$APID_URL/api/v1/threads/$THREAD_ID" \-H "Authorization: Bearer $DUST_TOKEN" \-H "Dust-Ctx-Org-Id: $DUST_ORG_ID" \-H "Dust-Ctx-Team-Id: ${DUST_TEAM_ID:-}" \| jq '{ name: .thread.name, fields: [.thread.data[]?.name], events: (.events | length) }'{ "name": "Tire SZ3J-11-ZJ17", "fields": ["Serial Number", "Max PSI"], "events": 1 }quickstart.ts const getResponse = await fetch(`${apidUrl}/api/v1/threads/${threadId}`, {headers: context,});if (!getResponse.ok) {const error = await getResponse.json();throw new Error(`read failed: ${error.code} — ${error.message}`);}const record = (await getResponse.json()) as {thread: { name: string | null };events: unknown[];};console.log(record.thread.name); // "Tire SZ3J-11-ZJ17"console.log(record.events.length); // at least 1 — creation is an eventDie Antwort hat die Form
{ "thread": { … }, "events": [ … ] }. Die genaue Anzahl der Ereignisse ist nicht vertraglich festgelegt — rechnen Sie mit mindestens einem.
Wenn es nicht funktioniert hat
Abschnitt betitelt „Wenn es nicht funktioniert hat“| Was Sie gesehen haben | Was es bedeutet |
|---|---|
401 UNAUTHORIZED beim Austausch | Der API-Schlüssel ist falsch, wurde widerrufen oder ist kein Service-Account-Schlüssel. Persönliche Schlüssel können nicht zur Authentifizierung verwendet werden. |
401 UNAUTHORIZED bei einem /api/v1/*-Aufruf | Das Bearer-Token ist abgelaufen (diese Token sind kurzlebig). Tauschen Sie es erneut aus und wiederholen Sie den Aufruf einmal. |
400 ORG_ID_REQUIRED | Sie haben Dust-Ctx-Org-Id bei einem organisationsbezogenen Endpunkt weggelassen. |
400 INVALID_REQUEST mit Nennung eines Headers | Ein Kontext-Header war keine UUID. Header werden validiert, bevor der Endpunkt ausgeführt wird. |
403 FORBIDDEN beim Erstellen | Der Service Account ist kein Mitglied des ausgewählten Teams. Bitten Sie Ihren Administrator, ihn hinzuzufügen. |
404 beim Zurücklesen | In der Regel ist der Kontext falsch und der Datensatz fehlt nicht — ein Datensatz ist nur in der Organisation und dem Team sichtbar, denen er gehört oder mit denen er geteilt wurde. |
Jeder Fehlerkörper hat die Form { code, message, status, detail? }, und jede Antwort enthält einen Header x-request-id, der protokolliert werden sollte. Die vollständige Codeliste, die Tabellen der Scanergebnisse und Hinweise zu Wiederholungsversuchen finden Sie unter Fehler und Scanergebnisse.
Nächste Schritte
Abschnitt betitelt „Nächste Schritte“- Anfragekonventionen — Kontext-Header, Paginierung und Lokalisierung.
- Fehler und Scanergebnisse — der vollständige Fehlervertrag.
- Datensätze — Feldtypen, Aktualisierungen, Archivierung, Auflistung und Suche.
- Kennungen — physische Kennungen mit Datensätzen verknüpfen und dagegen verifizieren (die Endpunkte
/api/v1/tags/*). - Dateien — Dateien mit Nachweisen an Datensätze anhängen.
- Teams und Freigabe — teamübergreifender Zugriff.
- TypeScript-Client — eine typisierte Alternative zu einfachem
fetch. - Vollständige API-Referenz — alle aus der OpenAPI-Spezifikation generierten Endpunkte. Der API-Server stellt außerdem selbst eine interaktive Referenz unter
https://apid.dustid.io/api/docsund die Rohspezifikation unter/api/openapi.jsonbereit.