Szybki start z API
Po przejściu tej strony wymienisz klucz API na token Bearer, poznasz organizację i zespół, w których może działać twoje poświadczenie, utworzysz rekord i odczytasz go z powrotem wraz z historią zdarzeń.
Obie poniższe ścieżki językowe są kompletne i niezależne: wszystko jest zdefiniowane przed użyciem, a żadna z nich nie zapożycza kroków z drugiej. Wybierz jedną kartę i trzymaj się jej.
Zanim zaczniesz
Dział zatytułowany „Zanim zaczniesz”Potrzebujesz:
- Klucza API konta usługi wydanego przez administratora organizacji. Osobiste klucze API nie istnieją — jeśli jeszcze nie masz klucza, zobacz Uwierzytelnianie i klucze API.
- Konto usługi musi być członkiem zespołu, w którym zapisujesz dane. Tworzenie rekordów wymaga bieżącego członkostwa w wybranym zespole; poświadczenie na poziomie organizacji, które nie należy do żadnego zespołu, może odczytać
/api/v1/me, ale nie może tworzyć rekordów. Jeśli krok 3 zwróci403 FORBIDDEN, poproś administratora o dodanie konta do zespołu. - Ścieżka curl:
curlijq(przykłady analizują za jego pomocą dane JSON; jeśli wolisz nie instalowaćjq, skopiuj wartości z odpowiedzi ręcznie). - Ścieżka TypeScript: środowiska uruchomieniowego, które bezpośrednio wykonuje kod TypeScript i udostępnia globalną funkcję
fetch— Node.js 22.18 lub nowszego, Bun albo Deno. W Node.js 18 lub 20 uruchom plik za pomocą programu ładującego, takiego jaktsx. Nie trzeba instalować żadnych pakietów: przykłady używają wyłącznie zwykłegofetch. Typowany klient jest dostępny osobno — zobacz Klient TypeScript.
Wszystkie żądania trafiają do https://apid.dustid.io; adresy URL pozostałych usług opisano w sekcji Środowiska.
-
Konfigurowanie środowiska
Dział zatytułowany „Konfigurowanie środowiska”Okno terminala export APID_URL="https://apid.dustid.io"export DUST_API_KEY="your-service-account-key" # read this from your secrets managerZapisz poniższy plik jako
quickstart.tsi uruchom go poleceniemnode quickstart.ts,bun quickstart.tslubdeno run --allow-net --allow-env quickstart.ts. Każdy kolejny krok dopisuje kod do tego samego pliku.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."); -
Wymiana klucza API na token Bearer
Dział zatytułowany „Wymiana klucza API na token Bearer”Klucze API nigdy nie są wysyłane do punktów końcowych
/api/v1/*. Wymień klucz jednorazowo wGET /api/auth/token, przekazując go w nagłówkux-api-key, a otrzymany token JWT wysyłaj jakoAuthorization: Bearer <token>w każdym kolejnym wywołaniu.Okno terminala 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" }Okno terminala 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"})`);Odpowiedź zawiera
token, a jeśli sam token JWT ma oświadczenie o wygaśnięciu — takżeexpiresIn(pozostały czas w sekundach) iexpiresAt(ISO 8601). Odczytuj czas ważności z odpowiedzi zamiast wpisywać go na stałe w kodzie: tokeny są obecnie krótkotrwałe (około 15 minut) i nie ma tokenu odświeżania, więc długo działające zadanie musi w trakcie pracy ponownie wymienić klucz. Pełny kontrakt dotyczący czasu ważności, implementację buforowania i wzorzec jednorazowego odświeżenia po401opisano w sekcji Uwierzytelnianie → Wygasanie i odświeżanie tokenu. -
Ustalanie organizacji
Dział zatytułowany „Ustalanie organizacji”GET /api/v1/meto jeden z nielicznych punktów końcowych, które nie wymagają nagłówków kontekstu. Opisuje samo poświadczenie: podmiot, organizacje, do których należy, oraz organizację aktywną.Okno terminala 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"] }]}Okno terminala # 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}`); -
Wybór zespołu (opcjonalnie)
Dział zatytułowany „Wybór zespołu (opcjonalnie)”Rekordy należą do zespołu w obrębie organizacji. Masz do wyboru dwie obsługiwane możliwości:
- Nie rób nic. Pomiń
Dust-Ctx-Team-Id, a API będzie działać w zespole głównym organizacji. W organizacji z jednym zespołem to cały krok 4; przykłady w kroku 5 korzystają właśnie z tej ścieżki. - Wskaż zespół.
GET /api/v1/teamszwraca listę zespołów, do których należy twoje poświadczenie, w postaci{ "teams": [ … ], "total": n }, a każdy z nich zawierateamId,orgIdiname. Wybrany zespół wyślij w nagłówkuDust-Ctx-Team-Id.
Okno terminala 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" }Okno terminala # Optional. Leave DUST_TEAM_ID unset to use the organization's root Team.export DUST_TEAM_ID="4e90…"Każde z poniższych żądań przekazuje
-H "Dust-Ctx-Team-Id: ${DUST_TEAM_ID:-}". Pusta wartość jest traktowana dokładnie tak samo jak brak nagłówka — oznacza zespół główny organizacji — więc ten sam skrypt działa niezależnie od tego, czy zmienna jest ustawiona.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 } : {}),}; - Nie rób nic. Pomiń
-
Tworzenie rekordu
Dział zatytułowany „Tworzenie rekordu”Rekord to zapis jednego składnika majątku lub przedmiotu.
POST /api/v1/threadsztype: "single"tworzy jeden rekord;thread.nameto jedyne wymagane pole, a opcjonalna tablicadatazawiera typowane pola.Okno terminala 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" }Okno terminala 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}`);Kod stanu to
201 Created, a treść ma postać{ "created": [ … ], "uploadResponses": [] }— jest to kształt wsadowy, ponieważ ten sam punkt końcowy tworzy wiele rekordów naraz przytype: "list"lubtype: "raw". Każdy wpis wcreatedto pełny zapis rekordu, łącznie z wygenerowanymthreadId.Wpisy pól wymagają
typeivalue, a kształtvaluezależy od typu:{ "text": "…" }dlatext,{ "number": 51 }dlanumber.nameto nazwa pola. Pełną listę typów pól zawiera przewodnik po rekordach. -
Odczyt utworzonego rekordu
Dział zatytułowany „Odczyt utworzonego rekordu”GET /api/v1/threads/{thread_id}zwraca rekord wraz z historią zdarzeń — każdy zapis jest odnotowywany jako zdarzenie, więc ścieżka audytu zaczyna się w chwili utworzenia.Okno terminala 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 eventOdpowiedź ma kształt
{ "thread": { … }, "events": [ … ] }. Dokładna liczba zdarzeń nie jest częścią kontraktu — oczekuj co najmniej jednego zdarzenia.
Jeśli coś nie zadziałało
Dział zatytułowany „Jeśli coś nie zadziałało”| Co widzisz | Co to oznacza |
|---|---|
401 UNAUTHORIZED podczas wymiany | Klucz API jest nieprawidłowy, unieważniony lub nie jest kluczem konta usługi. Klucze osobiste nie umożliwiają uwierzytelnienia. |
401 UNAUTHORIZED przy wywołaniu /api/v1/* | Token Bearer wygasł (tokeny są krótkotrwałe). Wymień klucz ponownie i ponów żądanie jeden raz. |
400 ORG_ID_REQUIRED | Pominięto Dust-Ctx-Org-Id w punkcie końcowym działającym w zakresie organizacji. |
400 INVALID_REQUEST ze wskazaniem nagłówka | Nagłówek kontekstu nie był identyfikatorem UUID. Nagłówki są walidowane przed uruchomieniem punktu końcowego. |
403 FORBIDDEN podczas tworzenia | Konto usługi nie jest członkiem wybranego zespołu. Poproś administratora o jego dodanie. |
404 podczas odczytu | Zwykle oznacza niewłaściwy kontekst, a nie brak rekordu — rekord jest widoczny tylko w organizacji i zespole, które są jego właścicielami lub którym go udostępniono. |
Każda treść błędu ma postać { code, message, status, detail? }, a każda odpowiedź zawiera nagłówek x-request-id, który warto zapisywać w logach. Pełna lista kodów, tabele wyników skanowania i wskazówki dotyczące ponawiania znajdują się w sekcji Błędy i wyniki skanowania.
Co dalej
Dział zatytułowany „Co dalej”- Konwencje żądań — nagłówki kontekstu, paginacja, lokalizacja.
- Błędy i wyniki skanowania — pełny kontrakt dotyczący niepowodzeń.
- Rekordy — typy pól, aktualizacje, archiwizacja, wyświetlanie list i wyszukiwanie.
- Identyfikatory — powiązanie i weryfikacja fizycznych identyfikatorów względem rekordów (punkty końcowe
/api/v1/tags/*). - Pliki — załączanie plików z dowodami do rekordów.
- Zespoły i udostępnianie — dostęp między zespołami.
- Klient TypeScript — typowana alternatywa dla surowego
fetch. - Pełna dokumentacja API — każdy punkt końcowy, wygenerowany na podstawie specyfikacji OpenAPI. Serwer API udostępnia też interaktywną dokumentację pod adresem
https://apid.dustid.io/api/docsoraz surową specyfikację pod adresem/api/openapi.json.