Inicio rápido de la API
Al finalizar esta página, habrás intercambiado una clave de API por un token de portador, descubierto la organización y el Equipo en los que puede actuar tu credencial, creado una Ficha y vuelto a leerla junto con su historial de eventos.
Las dos rutas de lenguaje que aparecen a continuación son completas e independientes: todo se define antes de utilizarse y ninguna toma prestado un paso de la otra. Elige una pestaña y permanece en ella.
Antes de empezar
Sección titulada «Antes de empezar»Necesitas:
- Una clave de API de una Cuenta de servicio, emitida por un administrador de la organización. No existen las claves de API personales; consulta Autenticación y claves de API si aún no tienes una.
- La Cuenta de servicio debe pertenecer al Equipo en el que escribes. Para crear Fichas se requiere pertenecer actualmente al Equipo seleccionado; una credencial de nivel de organización que no pertenezca a ningún Equipo puede leer
/api/v1/me, pero no puede crear registros. Pide al administrador que la añada a un Equipo si el paso 3 devuelve403 FORBIDDEN. - Ruta con curl:
curlyjq(los ejemplos lo utilizan para analizar JSON; si prefieres no instalarjq, copia manualmente los valores de las respuestas). - Ruta con TypeScript: un entorno de ejecución que ejecute TypeScript directamente y disponga de un
fetchglobal: Node.js 22.18 o posterior, Bun o Deno. En Node.js 18 o 20, ejecuta el archivo con un cargador comotsx. No hay que instalar ningún paquete: los ejemplos solo utilizanfetchsin bibliotecas adicionales. También hay disponible un cliente tipado; consulta Cliente de TypeScript.
Todas las solicitudes se envían a https://apid.dustid.io; consulta Entornos para conocer las demás URL del servicio.
-
Configura tu entorno
Sección titulada «Configura tu entorno»Ventana de terminal export APID_URL="https://apid.dustid.io"export DUST_API_KEY="your-service-account-key" # read this from your secrets managerGuarda el archivo siguiente como
quickstart.tsy ejecútalo connode quickstart.ts,bun quickstart.tsodeno run --allow-net --allow-env quickstart.ts. Cada paso añade contenido al mismo archivo.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."); -
Intercambia la clave de API por un token de portador
Sección titulada «Intercambia la clave de API por un token de portador»Las claves de API nunca se envían a los endpoints
/api/v1/*. Intercambia la clave una vez medianteGET /api/auth/token, pasándola en el encabezadox-api-key, y envía el JWT resultante comoAuthorization: Bearer <token>en todas las llamadas posteriores.Ventana de terminal 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" }Ventana de terminal 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 respuesta contiene
tokeny, siempre que el propio JWT incluya una declaración de caducidad,expiresIn(segundos restantes) yexpiresAt(ISO 8601). Obtén la duración de la respuesta en lugar de codificarla de forma fija: actualmente, los tokens tienen una duración breve (unos 15 minutos) y no existe ningún token de actualización, por lo que un trabajo de larga duración debe volver a realizar el intercambio durante la ejecución. El contrato completo sobre la duración, una implementación de almacenamiento en caché y el patrón de actualizar una vez al recibir un401se encuentran en Autenticación → Caducidad y actualización del token. -
Descubre tu organización
Sección titulada «Descubre tu organización»GET /api/v1/mees uno de los pocos endpoints que no necesita encabezados de contexto. Describe la propia credencial: la entidad principal, las organizaciones a las que pertenece y cuál está activa.Ventana de terminal 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"] }]}Ventana de terminal # 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}`); -
Elige un Equipo (opcional)
Sección titulada «Elige un Equipo (opcional)»Los registros pertenecen a un Equipo dentro de la organización. Dispones de dos opciones compatibles:
- No hagas nada. Omite
Dust-Ctx-Team-Idy la API actuará en el Equipo raíz de la organización. Esto es todo el paso 4 para una organización con un solo Equipo, y los ejemplos del paso 5 siguen esta opción. - Especifica un Equipo.
GET /api/v1/teamsenumera los Equipos a los que pertenece tu credencial con la forma{ "teams": [ … ], "total": n }; cada uno incluyeteamId,orgIdyname. Envía el que quieras utilizar comoDust-Ctx-Team-Id.
Ventana de terminal 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" }Ventana de terminal # Optional. Leave DUST_TEAM_ID unset to use the organization's root Team.export DUST_TEAM_ID="4e90…"Todas las solicitudes siguientes incluyen
-H "Dust-Ctx-Team-Id: ${DUST_TEAM_ID:-}". Un valor vacío se trata exactamente igual que un encabezado ausente —el Equipo raíz de la organización—, por lo que el mismo script funciona tanto si estableces la variable como si no.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 } : {}),}; - No hagas nada. Omite
-
Crea una Ficha
Sección titulada «Crea una Ficha»Una Ficha es el registro de un activo o artículo.
POST /api/v1/threadscontype: "single"crea una;thread.namees el único campo obligatorio y el array opcionaldatacontiene campos tipados.Ventana de terminal 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" }Ventana de terminal 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}`);El estado es
201 Createdy el cuerpo tiene la forma{ "created": [ … ], "uploadResponses": [] }: una estructura de lote, porque el mismo endpoint crea varias Fichas a la vez contype: "list"otype: "raw". Cada entrada decreatedes un registro completo de Ficha que incluye suthreadIdgenerado.Las entradas de campo necesitan
typeyvalue, y la estructura devaluedepende del tipo:{ "text": "…" }paratexty{ "number": 51 }paranumber.namees la etiqueta del campo. La lista completa de tipos de campo se encuentra en la guía de Fichas. -
Vuelve a leerla
Sección titulada «Vuelve a leerla»GET /api/v1/threads/{thread_id}devuelve la Ficha junto con su historial de eventos: cada escritura queda registrada, por lo que el registro de auditoría comienza en el momento de la creación.Ventana de terminal 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 respuesta tiene la forma
{ "thread": { … }, "events": [ … ] }. La cantidad exacta de eventos no forma parte del contrato: espera al menos uno.
Si no ha funcionado
Sección titulada «Si no ha funcionado»| Lo que has visto | Lo que significa |
|---|---|
401 UNAUTHORIZED durante el intercambio | La clave de API es incorrecta, se ha revocado o no es una clave de Cuenta de servicio. Las claves personales no permiten autenticarse. |
401 UNAUTHORIZED en una llamada a /api/v1/* | El token de portador ha caducado (tienen una duración breve). Vuelve a realizar el intercambio y reintenta una vez. |
400 ORG_ID_REQUIRED | Has omitido Dust-Ctx-Org-Id en un endpoint limitado a una organización. |
400 INVALID_REQUEST que menciona un encabezado | Un encabezado de contexto no era un UUID. Los encabezados se validan antes de ejecutar el endpoint. |
403 FORBIDDEN durante la creación | La Cuenta de servicio no pertenece al Equipo seleccionado. Pide al administrador que la añada. |
404 al volver a leer el registro | Normalmente indica un contexto incorrecto, no un registro inexistente: una Ficha solo es visible en la organización y el Equipo que son sus propietarios o con los que se ha compartido. |
El cuerpo de todos los errores tiene la forma { code, message, status, detail? }, y cada respuesta incluye un encabezado x-request-id que conviene registrar. La lista completa de códigos, las tablas de resultados de escaneo y las instrucciones para reintentar se encuentran en Errores y resultados de escaneo.
Próximos pasos
Sección titulada «Próximos pasos»- Convenciones de las solicitudes — encabezados de contexto, paginación y localización.
- Errores y resultados de escaneo — el contrato de errores completo.
- Fichas — tipos de campo, actualizaciones, archivado, enumeración y búsqueda.
- Identificadores — vincular y verificar identificadores físicos con Fichas (los endpoints
/api/v1/tags/*). - Archivos — adjuntar archivos de evidencias a Fichas.
- Equipos y uso compartido — acceso entre Equipos.
- Cliente de TypeScript — una alternativa tipada al uso directo de
fetch. - Referencia completa de la API — todos los endpoints, generados a partir de la especificación OpenAPI. El servidor de la API también aloja una referencia interactiva en
https://apid.dustid.io/api/docsy la especificación sin procesar en/api/openapi.json.