Constaia
SDKs

SDK de JavaScript y TypeScript

@constaia/sdk: cliente oficial para Node.js 18+, Bun y Deno, sin dependencias. Análisis, lotes, webhooks, errores tipados y reintentos.

@constaia/sdk es el cliente oficial de Constaia para JavaScript y TypeScript. Usa fetch nativo, no tiene dependencias y trae los tipos incluidos.

npm i @constaia/sdk
# o: pnpm add @constaia/sdk · yarn add @constaia/sdk · bun add @constaia/sdk

Requisitos: Node.js 18 o superior (o Bun, o Deno). Funciona en ESM y CommonJS.

Solo en tu servidor

Las claves ck_live_… y ck_test_… son secretas. Si el SDK detecta que se ejecuta en un navegador con una de ellas, lanza un error. Para subir documentos desde el navegador usa el widget, que envía el fichero a tu backend.

Configuración

import { Constaia } from "@constaia/sdk";

const constaia = new Constaia(); // lee CONSTAIA_API_KEY del entorno

// o con opciones explícitas
const constaia2 = new Constaia({
  apiKey: process.env.CONSTAIA_API_KEY,
  timeout: 60_000, // ms por intento (por defecto 60 000)
  maxRetries: 2,   // reintentos en 429, 5xx y errores de red (por defecto 2)
});

constaia.livemode; // true con ck_live_, false con ck_test_
OpciónPor defectoDescripción
apiKeyprocess.env.CONSTAIA_API_KEYTu clave secreta.
baseUrlhttps://api.constaia.comCambia la URL base (o usa CONSTAIA_BASE_URL).
timeout60000Tiempo máximo por intento, en milisegundos.
maxRetries2Reintentos automáticos.
fetchglobalThis.fetchImplementación de fetch propia (tests, proxies).
defaultHeaders{}Cabeceras que se añaden a todas las peticiones.

Analizar un documento

import { Constaia } from "@constaia/sdk";
import { fromPath } from "@constaia/sdk/node"; // solo Node, Bun y Deno

const constaia = new Constaia();

const analysis = await constaia.analyze(await fromPath("dni_valid.jpg"), {
  expect: "es_dni",
  checks: { notExpired: true, holder: { fullName: "María García López" } },
  storage: "none",
  language: "es",
});

analysis.verdict?.status;               // "valid" | "invalid" | "review"
analysis.verdict?.reasons;              // [{ code, severity, message }]
analysis.fields.document_number?.value; // "12345678Z"
analysis.warnings;                      // ["blurry", …]

Entradas admitidas

EntradaEjemplo
Ruta local (Node, Bun, Deno)await fromPath("dni.jpg")
File o Blobel File de un FormData en tu route handler
Buffer, Uint8Array, ArrayBufferconstaia.analyze(buffer, { filename: "dni.jpg" })
ReadableStreamconstaia.analyze(stream, { filename: "dni.pdf" })
URLconstaia.analyze({ fileUrl: "https://…/dni.jpg" })
Base64constaia.analyze({ base64, filename: "dni.jpg" })

Cuando la entrada no trae nombre (buffers, streams), pasa filename: la extensión sirve para deducir el tipo MIME. Un string suelto no se acepta porque sería ambiguo: usa fromPath() para rutas o { fileUrl } para URLs.

Opciones en camelCase

El SDK usa camelCase y convierte a snake_case al enviar:

SDKAPI
ttlHoursttl_hours
keepResultskeep_results
checks.notExpiredchecks.not_expired
checks.referenceDatechecks.reference_date
checks.maxAgeDayschecks.max_age_days
checks.minAgeYears / maxAgeYearschecks.min_age_years / max_age_years
checks.holder.fullName, firstName, lastName, documentNumber, birthDatechecks.holder.full_name, …
checks.requireFields, requireSignature, requireStampchecks.require_fields, …
checks.expectedAmount, expectedIban, expectedReferencechecks.expected_amount, …

expect, extract, storage, async, export, metadata y language se llaman igual. Las claves de metadata y el esquema de extract se envían sin tocar. La lista completa de opciones está en Analizar un documento.

Clasificar

const result = await constaia.classify(await fromPath("passport.jpg"), { expect: ["es_dni", "passport"] });
result.document.type;   // "passport"
result.candidates;      // [{ type, confidence }, …]
result.verdict?.status; // solo si pasas expect

Análisis guardados

const analysis = await constaia.analyses.get("an_01J…");

// Una página
const page = await constaia.analyses.list({ limit: 20, status: "completed", type: "es_dni" });
page.data; page.has_more;

// Todas las páginas: el SDK sigue el cursor por ti
for await (const a of constaia.analyses.list({ metadata: { registration_id: "123" } })) {
  console.log(a.id);
}
const first100 = await constaia.analyses.list().toArray(100);

await constaia.analyses.delete("an_01J…"); // { id, deleted: true }

const res = await constaia.analyses.export("an_01J…", "xlsx"); // Response
const bytes = await res.arrayBuffer();

Lotes

const batch = await constaia.batches.create({
  files: [await fromPath("dni_valid.jpg"), await fromPath("nie.jpg")],
  options: { expect: ["es_dni", "es_nie"], export: ["xlsx"] },
});

// o con URLs y opciones por documento
await constaia.batches.create({
  items: [{ fileUrl: "https://example.com/1.jpg", options: { expect: "es_dni" } }],
});

await constaia.batches.get(batch.id);

Ver Lotes.

Catálogo, saldo y uso

const types = await constaia.documentTypes.list();   // DocumentTypeInfo[]
const dni = await constaia.documentTypes.get("es_dni");

const balance = await constaia.balance();            // { credits_available, credits_reserved, … }
const usage = await constaia.usage({ from: "2026-09-01", to: "2026-09-30" });

Webhooks

// Alta de endpoints
const endpoint = await constaia.webhookEndpoints.create({
  url: "https://tuapp.example/webhooks/constaia",
  events: ["analysis.completed", "analysis.failed"],
});
endpoint.secret; // whsec_… (solo en la creación)

// Verificación (Standard Webhooks, tolerancia de 5 minutos)
const event = await constaia.webhooks.verify(rawBody, request.headers, process.env.CONSTAIA_WEBHOOK_SECRET!);

verifyWebhook y signWebhook también se exportan sueltas desde @constaia/sdk (la segunda sirve para firmar payloads en tus tests). Ejemplos completos para Next.js y Express en Webhooks.

Opciones por petición

Todos los métodos aceptan un último argumento con opciones de esa petición:

const controller = new AbortController();

await constaia.analyze(file, { expect: "es_dni" }, {
  idempotencyKey: `registration-123-dni`, // si no la pasas, el SDK genera una
  timeout: 90_000,
  maxRetries: 0,
  signal: controller.signal,
  headers: { "X-Trace": "abc" },
});

En cada POST el SDK envía una Idempotency-Key y la reutiliza en los reintentos, así que un reintento nunca cobra dos veces. Pasa la tuya si quieres que también sea segura entre procesos o reinicios. Ver Idempotencia.

Errores

Todos los errores heredan de ConstaiaError y exponen status, type, code, param, requestId y headers.

ClaseCuándo
InvalidRequestError400/422: parámetros incorrectos, fichero no admitido…
AuthenticationError401: clave ausente o incorrecta.
PermissionError403: la clave no tiene permiso.
NotFoundError404: el recurso no existe.
InsufficientCreditsError402: no queda saldo.
RateLimitError429 tras agotar reintentos. Incluye retryAfter (segundos).
APIError5xx u otros errores del servidor.
APIConnectionErrorNo se pudo conectar.
APITimeoutErrorSe superó el timeout (hereda de APIConnectionError).
WebhookVerificationErrorLa firma de un webhook no es válida.
import { InsufficientCreditsError, InvalidRequestError, RateLimitError } from "@constaia/sdk";

try {
  await constaia.analyze(file, { expect: "es_dni" });
} catch (err) {
  if (err instanceof InsufficientCreditsError) {
    // avisa al administrador, compra un pack
  } else if (err instanceof InvalidRequestError && err.code === "unsupported_file_type") {
    // pide otro formato al usuario
  } else if (err instanceof RateLimitError) {
    console.log(`Reintenta en ${err.retryAfter} s`);
  } else {
    throw err;
  }
}

Reintentos

El SDK reintenta automáticamente (2 veces por defecto) las respuestas 408, 429 y 5xx y los errores de red, con espera exponencial. Si la API envía Retry-After, lo respeta (hasta 60 s). Configúralo con maxRetries en el cliente o por petición.

Tipos

Todos los tipos se exportan: Analysis, AnalyzeOptions, Checks, Verdict, ExtractedField, Classification, Batch, Balance, Usage, WebhookEvent, DocumentType… Las respuestas mantienen los nombres de la API en snake_case (analysis.created_at, balance.credits_available).

DocumentType, ReasonCode y WarningCode incluyen los valores conocidos pero admiten cualquier string, para que tu código no se rompa cuando se añadan tipos o códigos nuevos.

Siguiente paso

En esta página