Avvio rapido dell’API
Al termine di questa pagina avrai scambiato una chiave API con un bearer token, individuato l’organizzazione e il Team in cui le tue credenziali possono operare, creato una Scheda e riletto la Scheda con la relativa cronologia degli eventi.
I due percorsi per linguaggio riportati di seguito sono completi e indipendenti: ogni elemento viene definito prima di essere utilizzato e nessuno dei due percorsi attinge a un passaggio dell’altro. Scegli una scheda e seguila fino alla fine.
Prima di iniziare
Sezione intitolata “Prima di iniziare”Occorrono:
- Una chiave API di un Account di servizio, rilasciata da un amministratore dell’organizzazione. Non esistono chiavi API personali — consulta Autenticazione e chiavi API se non ne hai ancora una.
- L’Account di servizio deve essere membro del Team in cui scrivi. Per creare Schede è necessaria l’appartenenza corrente al Team selezionato; una credenziale a livello di organizzazione che non appartiene ad alcun Team può leggere
/api/v1/me, ma non può creare registri. Chiedi all’amministratore di aggiungerla a un Team se il passaggio 3 restituisce403 FORBIDDEN. - Percorso curl:
curlejq(gli esempi lo utilizzano per analizzare il JSON; se preferisci non installarejq, copia manualmente i valori dalle risposte). - Percorso TypeScript: un runtime che esegua direttamente TypeScript e disponga di un
fetchglobale — Node.js 22.18 o versioni successive, Bun oppure Deno. Su Node.js 18 o 20, esegui invece il file con un loader cometsx. Non occorre installare pacchetti: gli esempi utilizzano esclusivamente il semplicefetch. È disponibile separatamente un client tipizzato; consulta Client TypeScript.
Tutte le richieste vengono inviate a https://apid.dustid.io; consulta Ambienti per gli URL degli altri servizi.
-
Configura l’ambiente
Sezione intitolata “Configura l’ambiente”Terminal window export APID_URL="https://apid.dustid.io"export DUST_API_KEY="your-service-account-key" # read this from your secrets managerSalva il file seguente come
quickstart.tsed eseguilo connode quickstart.ts,bun quickstart.tsoppuredeno run --allow-net --allow-env quickstart.ts. Ogni passaggio aggiunge contenuto allo stesso file.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."); -
Scambia la chiave API con un bearer token
Sezione intitolata “Scambia la chiave API con un bearer token”Le chiavi API non vengono mai inviate agli endpoint
/api/v1/*. Scambia una volta la chiave tramiteGET /api/auth/token, passandola nell’headerx-api-key, e invia il JWT risultante comeAuthorization: Bearer <token>in ogni chiamata successiva.Terminal window 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 window 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"})`);La risposta contiene
tokene, quando il JWT stesso include un’attestazione di scadenza,expiresIn(secondi rimanenti) edexpiresAt(ISO 8601). Ricava la durata dalla risposta anziché codificarla direttamente: attualmente i token hanno breve durata (circa 15 minuti) e non esiste un refresh token, pertanto un processo di lunga durata deve ripetere lo scambio durante l’esecuzione. Il contratto completo relativo alla durata, un’implementazione della memorizzazione nella cache e il modello che prevede un solo aggiornamento in caso di401sono disponibili in Autenticazione → Scadenza e aggiornamento del token. -
Individua la tua organizzazione
Sezione intitolata “Individua la tua organizzazione”GET /api/v1/meè uno dei pochi endpoint che non richiedono header di contesto. Descrive la credenziale stessa: il principale, le organizzazioni a cui appartiene e quale di esse è attiva.Terminal window 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 window # 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}`); -
Scegli un Team (facoltativo)
Sezione intitolata “Scegli un Team (facoltativo)”I registri appartengono a un Team all’interno dell’organizzazione. Sono disponibili due opzioni supportate:
- Non fare nulla. Ometti
Dust-Ctx-Team-Ide l’API opererà nel Team radice dell’organizzazione. Per un’organizzazione con un solo Team, questo è tutto ciò che occorre fare nel passaggio 4; gli esempi del passaggio 5 seguono questo percorso. - Specifica un Team.
GET /api/v1/teamselenca i Team di cui la tua credenziale è membro nel formato{ "teams": [ … ], "total": n }; ciascuno contieneteamId,orgIdename. Invia quello desiderato comeDust-Ctx-Team-Id.
Terminal window 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 window # Optional. Leave DUST_TEAM_ID unset to use the organization's root Team.export DUST_TEAM_ID="4e90…"Ogni richiesta riportata di seguito passa
-H "Dust-Ctx-Team-Id: ${DUST_TEAM_ID:-}". Un valore vuoto viene trattato esattamente come un header assente, ossia viene utilizzato il Team radice dell’organizzazione; lo stesso script funziona quindi sia che la variabile venga impostata, sia che non venga impostata.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 } : {}),}; - Non fare nulla. Ometti
-
Crea una Scheda
Sezione intitolata “Crea una Scheda”Una Scheda è il registro relativo a una singola risorsa o a un singolo articolo.
POST /api/v1/threadscontype: "single"ne crea una;thread.nameè l’unico campo obbligatorio e l’array facoltativodatacontiene i campi tipizzati.Terminal window 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 window 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}`);Lo stato è
201 Createde il corpo è{ "created": [ … ], "uploadResponses": [] }: una struttura per operazioni in blocco, poiché lo stesso endpoint crea più Schede contemporaneamente contype: "list"otype: "raw". Ogni voce increatedè un registro completo della Scheda, incluso il relativothreadIdgenerato.Le voci dei campi richiedono
typeevalue, mentre la struttura divaluedipende dal tipo:{ "text": "…" }pertext,{ "number": 51 }pernumber.nameè l’etichetta del campo. L’elenco completo dei tipi di campo è disponibile nella guida alle Schede. -
Rileggila
Sezione intitolata “Rileggila”GET /api/v1/threads/{thread_id}restituisce la Scheda insieme alla relativa cronologia degli eventi: ogni scrittura viene registrata, pertanto l’audit trail inizia al momento della creazione.Terminal window 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 eventLa struttura della risposta è
{ "thread": { … }, "events": [ … ] }. Il numero esatto di eventi non è definito dal contratto: aspettati almeno un evento.
Se non ha funzionato
Sezione intitolata “Se non ha funzionato”| Cosa hai riscontrato | Cosa significa |
|---|---|
401 UNAUTHORIZED durante lo scambio | La chiave API è errata, è stata revocata oppure non è una chiave di un Account di servizio. Le chiavi personali non consentono l’autenticazione. |
401 UNAUTHORIZED durante una chiamata a /api/v1/* | Il bearer token è scaduto (i token hanno breve durata). Ripeti lo scambio e ritenta una volta. |
400 ORG_ID_REQUIRED | Hai omesso Dust-Ctx-Org-Id in un endpoint con ambito di organizzazione. |
400 INVALID_REQUEST che indica un header | Un header di contesto non era un UUID. Gli header vengono convalidati prima dell’esecuzione dell’endpoint. |
403 FORBIDDEN durante la creazione | L’Account di servizio non è membro del Team selezionato. Chiedi all’amministratore di aggiungerlo. |
404 durante la rilettura | Solitamente indica un contesto errato, non un registro mancante: una Scheda è visibile solo nell’organizzazione e nel Team che la possiedono o con cui è stata condivisa. |
Il corpo di ogni errore è { code, message, status, detail? } e ogni risposta contiene un header x-request-id che è opportuno registrare nei log. L’elenco completo dei codici, le tabelle degli esiti di scansione e le indicazioni sui nuovi tentativi sono disponibili in Errori ed esiti di scansione.
Passaggi successivi
Sezione intitolata “Passaggi successivi”- Convenzioni per le richieste — header di contesto, paginazione, localizzazione.
- Errori ed esiti di scansione — il contratto completo relativo agli errori.
- Schede — tipi di campo, aggiornamenti, archiviazione, elenchi e ricerca.
- Identificatori — associa e verifica gli identificatori fisici rispetto alle Schede (gli endpoint
/api/v1/tags/*). - File — allega file di evidenza alle Schede.
- Team e condivisione — accesso tra Team.
- Client TypeScript — un’alternativa tipizzata al semplice
fetch. - Riferimento completo dell’API — tutti gli endpoint, generati dalla specifica OpenAPI. Il server API ospita inoltre direttamente un riferimento interattivo all’indirizzo
https://apid.dustid.io/api/docse la specifica non elaborata in/api/openapi.json.