Início rápido da API
No final desta página, terá trocado uma chave de API por um bearer token, identificado a organização e a Equipa em que a sua credencial pode atuar, criado um Registo e voltado a lê-lo com o respetivo histórico de eventos.
Os dois percursos por linguagem abaixo são completos e independentes: tudo é definido antes de ser utilizado e nenhum depende de um passo do outro. Escolha um separador e mantenha-se nele.
Antes de começar
Seção intitulada “Antes de começar”Precisa de:
- Uma chave de API de uma Conta de Serviço, emitida por um administrador da organização. Não existem chaves de API pessoais — consulte Autenticação e chaves de API caso ainda não tenha uma.
- A Conta de Serviço tem de ser membro da Equipa na qual pretende escrever. A criação de Registos exige que seja atualmente membro da Equipa selecionada; uma credencial ao nível da organização que não pertença a nenhuma Equipa pode ler
/api/v1/me, mas não pode criar registos. Peça ao administrador para a adicionar a uma Equipa se o passo 3 devolver403 FORBIDDEN. - Percurso com curl:
curlejq(os exemplos utilizam-no para analisar JSON; se preferir não instalar ojq, copie manualmente os valores das respostas). - Percurso com TypeScript: um ambiente de execução que execute TypeScript diretamente e disponibilize
fetchglobalmente — Node.js 22.18 ou mais recente, Bun ou Deno. No Node.js 18 ou 20, execute o ficheiro com um carregador como otsx. Não é necessário instalar pacotes: os exemplos utilizam apenasfetchsimples. Está disponível separadamente um cliente tipado; consulte Cliente TypeScript.
Todos os pedidos são enviados para https://apid.dustid.io; consulte Ambientes para conhecer os restantes URLs de serviço.
-
Configurar o ambiente
Seção intitulada “Configurar o ambiente”Terminal window export APID_URL="https://apid.dustid.io"export DUST_API_KEY="your-service-account-key" # read this from your secrets managerGuarde o ficheiro abaixo como
quickstart.tse execute-o comnode quickstart.ts,bun quickstart.tsoudeno run --allow-net --allow-env quickstart.ts. Cada passo acrescenta conteúdo ao mesmo ficheiro.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."); -
Trocar a chave de API por um bearer token
Seção intitulada “Trocar a chave de API por um bearer token”As chaves de API nunca são enviadas para endpoints
/api/v1/*. Troque a chave uma vez emGET /api/auth/token, passando-a no cabeçalhox-api-key, e envie o JWT resultante comoAuthorization: Bearer <token>em todas as chamadas seguintes.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"})`);A resposta contém
tokene — sempre que o próprio JWT tiver uma declaração de expiração —expiresIn(segundos restantes) eexpiresAt(ISO 8601). Leia o tempo de vida a partir da resposta em vez de o codificar diretamente: atualmente, os tokens têm uma duração curta (cerca de 15 minutos) e não existe refresh token, pelo que uma tarefa de longa duração tem de efetuar uma nova troca durante a execução. O contrato completo relativo ao tempo de vida, uma implementação de colocação em cache e o padrão de atualização única após um401encontram-se em Autenticação → Expiração e atualização de tokens. -
Identificar a sua organização
Seção intitulada “Identificar a sua organização”GET /api/v1/meé um dos poucos endpoints que não necessita de cabeçalhos de contexto. Descreve a própria credencial: o principal, as organizações a que pertence e qual delas está ativa.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}`); -
Escolher uma Equipa (opcional)
Seção intitulada “Escolher uma Equipa (opcional)”Os registos pertencem a uma Equipa dentro da organização. Existem duas opções suportadas:
- Não fazer nada. Omita
Dust-Ctx-Team-Ide a API atua na Equipa raiz da organização. Isto constitui a totalidade do passo 4 para uma organização com uma única Equipa, e os exemplos do passo 5 seguem este percurso. - Indicar uma Equipa.
GET /api/v1/teamsapresenta as Equipas das quais a sua credencial é membro, no formato{ "teams": [ … ], "total": n }, cada uma comteamId,orgIdename. Envie a Equipa pretendida comoDust-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…"Todos os pedidos abaixo enviam
-H "Dust-Ctx-Team-Id: ${DUST_TEAM_ID:-}". Um valor vazio é tratado exatamente como um cabeçalho ausente — é utilizada a Equipa raiz da organização — pelo que o mesmo script é executado quer defina ou não a variável.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 } : {}),}; - Não fazer nada. Omita
-
Criar um Registo
Seção intitulada “Criar um Registo”Um Registo corresponde ao registo de um único ativo ou item.
POST /api/v1/threadscomtype: "single"cria um;thread.nameé o único campo obrigatório e a matriz opcionaldatacontém campos tipados.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}`);O estado é
201 Createde o corpo é{ "created": [ … ], "uploadResponses": [] }— um formato de lote, porque o mesmo endpoint cria vários Registos de uma só vez comtype: "list"outype: "raw". Cada entrada emcreatedé um registo completo de Registo, incluindo o respetivothreadIdgerado.As entradas dos campos necessitam de
typeevalue, e a estrutura devaluevaria consoante o tipo:{ "text": "…" }paratexte{ "number": 51 }paranumber.nameé a etiqueta do campo. A lista completa de tipos de campo encontra-se no guia de Registos. -
Voltar a lê-lo
Seção intitulada “Voltar a lê-lo”GET /api/v1/threads/{thread_id}devolve o Registo juntamente com o respetivo histórico de eventos — todas as escritas são registadas, pelo que o histórico de auditoria começa na criação.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 eventA estrutura da resposta é
{ "thread": { … }, "events": [ … ] }. A contagem exata de eventos não faz parte do contrato — conte com pelo menos um.
Se não funcionou
Seção intitulada “Se não funcionou”| O que observou | O que significa |
|---|---|
401 UNAUTHORIZED durante a troca | A chave de API está incorreta, foi revogada ou não é uma chave de Conta de Serviço. As chaves pessoais não permitem a autenticação. |
401 UNAUTHORIZED numa chamada /api/v1/* | O bearer token expirou (os tokens têm uma duração curta). Efetue novamente a troca e repita uma vez. |
400 ORG_ID_REQUIRED | Omitiu Dust-Ctx-Org-Id num endpoint com âmbito de organização. |
400 INVALID_REQUEST que menciona um cabeçalho | Um cabeçalho de contexto não era um UUID. Os cabeçalhos são validados antes de o endpoint ser executado. |
403 FORBIDDEN durante a criação | A Conta de Serviço não é membro da Equipa selecionada. Peça ao administrador para a adicionar. |
404 ao voltar a ler | Normalmente significa que o contexto está incorreto, não que o registo está em falta — um Registo apenas é visível na organização e na Equipa que o possuem ou com as quais foi partilhado. |
Todos os corpos de erro têm o formato { code, message, status, detail? }, e todas as respostas incluem um cabeçalho x-request-id que deve ser registado. A lista completa de códigos, as tabelas de resultados da digitalização e as orientações sobre novas tentativas encontram-se em Erros e resultados da digitalização.
Próximos passos
Seção intitulada “Próximos passos”- Convenções dos pedidos — cabeçalhos de contexto, paginação e localização.
- Erros e resultados da digitalização — o contrato de falhas completo.
- Registos — tipos de campo, atualizações, arquivo, listagem e pesquisa.
- Identificadores — vincular e verificar identificadores físicos em relação a Registos (os endpoints
/api/v1/tags/*). - Ficheiros — anexar ficheiros de evidência a Registos.
- Equipas e partilha — acesso entre Equipas.
- Cliente TypeScript — uma alternativa tipada ao
fetchsimples. - Referência completa da API — todos os endpoints, gerados a partir da especificação OpenAPI. O servidor da API também aloja uma referência interativa em
https://apid.dustid.io/api/docse a especificação em bruto em/api/openapi.json.