Constaia
Endpoints

Expedientes

Referencia de /v1/dossiers: agrupa varios documentos de una misma persona o trámite con requisitos, añade documentos por fichero o analysis_id y obtén un veredicto global con comprobaciones cruzadas de titular y fechas.

Un expediente (dos_…) agrupa los documentos de una misma persona o trámite (el DNI, el certificado médico y el justificante de pago de una inscripción, por ejemplo) y calcula un veredicto global: si está todo, si algo falla o si hay que revisarlo. Además comprueba que todos los documentos son del mismo titular y que sus fechas son coherentes.

Método y rutaQué hace
POST /v1/dossiersCrea un expediente con sus requisitos.
POST /v1/dossiers/{id}/documentsAñade un documento: un fichero (se analiza) o un análisis ya hecho.
GET /v1/dossiersLista los expedientes del modo de la clave.
GET /v1/dossiers/{id}Recupera un expediente con su veredicto.
DELETE /v1/dossiers/{id}Borra el expediente (no borra los análisis).

Todas las rutas usan una clave secreta (ck_test_… o ck_live_…) desde tu backend y solo ven los expedientes del modo de la clave. En el panel están en Expedientes.

Los enlaces de verificación crean su expediente

Cada enlace de verificación crea un expediente con un requisito por documento pedido (dossier_id en el enlace, verification_link_id en el expediente). Cada subida de la persona se añade sola, y el veredicto del expediente llega en el webhook o callback verification_link.completed. Solo necesitas crear expedientes a mano cuando los documentos te llegan por tu propia subida.

Crear un expediente

POST /v1/dossiers
Content-Type: application/json
CampoTipoDescripción
referencestring | nullTu referencia (hasta 200 caracteres). Sirve para filtrar el listado.
templatestring | nullPlantilla (tpl_…) que se aplica a cada documento que se analiza en el expediente. 422 template_not_found si no existe.
requirementsobjeto[] (1–20)Documentos que tiene que tener el expediente. Cada uno: key (1–40 caracteres: letras, números, - o _, única), label (hasta 120; por defecto, el nombre del tipo esperado o la clave), expect (tipo o lista de tipos; si falta, el de la plantilla o cualquiera) y checks (como en analyze). Sin requirements y con template: uno, key: "document".
metadataobjeto string → stringHasta 20 claves. Se copia a cada análisis que se hace dentro del expediente.
face_verification{ enabled, required? } | nullRequisito de verificación facial (módulo opcional). Se cumple con POST /v1/face-verifications y dossier_id; obligatorio y sin hacer → incomplete; no coincide → invalid.

Un expediente sin requisitos (ni plantilla) también vale: acepta cualquier documento, y el veredicto se calcula con todos los que añadas.

curl https://api.constaia.com/v1/dossiers \
  -H "Authorization: Bearer $CONSTAIA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "reference": "inscripcion-4821",
    "metadata": { "registration_id": "4821" },
    "requirements": [
      { "key": "id_card", "label": "DNI o NIE", "expect": ["es_dni", "es_nie"], "checks": { "not_expired": true } },
      { "key": "medical", "label": "Certificado médico", "expect": "medical_certificate_sport", "checks": { "max_age_days": 180 } }
    ]
  }'

Responde 201 con el objeto dossier, con verdict.status: "incomplete" hasta que lleguen los documentos. Una clave repetida devuelve 422 duplicate_document_key; un expect o un check no válidos, 422 invalid_parameter con param: "requirements.<i>.<campo>".

Añadir documentos

POST /v1/dossiers/{id}/documents

Hay dos maneras:

CómoCuerpoQué pasa
Un ficheromultipart/form-data con file, requirement_key (opcional) y options (JSON, opcional); o JSON con file_url o file_base64 + filename.Se analiza al momento, igual que POST /v1/analyze (cobra créditos en live), con la plantilla del expediente, el expect y los checks del requisito y, encima, tus options.
Un análisis ya hechoJSON { "analysis_id": "an_…", "requirement_key": "…" }Se enlaza sin volver a analizar. Debe ser de la cuenta y del mismo modo que el expediente.

Responde 200 con el expediente actualizado.

# Fichero, para un requisito concreto
curl https://api.constaia.com/v1/dossiers/dos_01J9Z8Q3K4M5N6P7Q8R9S0T1V2/documents \
  -H "Authorization: Bearer $CONSTAIA_API_KEY" \
  -F "file=@dni.jpg" \
  -F "requirement_key=id_card"

# Análisis que ya tenías
curl https://api.constaia.com/v1/dossiers/dos_01J9Z8Q3K4M5N6P7Q8R9S0T1V2/documents \
  -H "Authorization: Bearer $CONSTAIA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "analysis_id": "an_01J9Z8Q3K4M5N6P7Q8R9S0T1V3", "requirement_key": "medical" }'

A qué requisito va. Si mandas requirement_key, a ese (si no existe: 422 invalid_document_key). Si no:

  • con un fichero y un solo requisito, a ese; con varios, se analiza aceptando la unión de sus expect (sin sus checks) y se asigna al primer requisito sin documento cuyo expect incluye el tipo detectado;
  • con analysis_id, al requisito del análisis si vino de un enlace con la misma clave o, si no, igual que arriba según su tipo;
  • si ningún requisito encaja, queda como documento suelto: no cubre ningún requisito, pero cuenta para las comprobaciones cruzadas.

Puedes añadir varios documentos al mismo requisito (una foto nueva tras un rechazo, por ejemplo). Cuenta el último; si el último falló, el último que se completó.

El objeto dossier

dossier
{
  "id": "dos_01J9Z8Q3K4M5N6P7Q8R9S0T1V2",
  "object": "dossier",
  "livemode": false,
  "reference": "inscripcion-4821",
  "template": null,
  "verification_link_id": null,
  "metadata": { "registration_id": "4821" },
  "requirements": [
    { "key": "id_card", "label": "DNI o NIE", "expect": ["es_dni", "es_nie"], "checks": { "not_expired": true }, "status": "valid", "analysis_id": "an_01J9Z8Q3K4M5N6P7Q8R9S0T1V3" },
    { "key": "medical", "label": "Certificado médico", "expect": ["medical_certificate_sport"], "checks": { "max_age_days": 180 }, "status": "missing", "analysis_id": null }
  ],
  "documents": [
    { "analysis_id": "an_01J9Z8Q3K4M5N6P7Q8R9S0T1V3", "requirement_key": "id_card", "type": "es_dni", "file_name": "dni.jpg", "status": "completed", "verdict_status": "valid", "final_status": "valid", "created_at": "2026-09-30T10:02:11Z" }
  ],
  "verdict": {
    "status": "incomplete",
    "reasons": [
      { "code": "requirement_missing", "severity": "warning", "message": "Falta el documento «Certificado médico».", "requirement_key": "medical" }
    ],
    "checks": [
      { "code": "same_holder", "passed": null, "severity": "warning", "message": "No hay datos suficientes para comprobar que todos los documentos son de la misma persona.", "analyses": ["an_01J9Z8Q3K4M5N6P7Q8R9S0T1V3"] },
      { "code": "dates_consistent", "passed": true, "severity": "info", "message": "Las fechas de los documentos son coherentes.", "analyses": ["an_01J9Z8Q3K4M5N6P7Q8R9S0T1V3"] }
    ]
  },
  "holder": { "name": "MARÍA GARCÍA LÓPEZ", "document_number": "12345678Z", "birth_date": "1990-04-12" },
  "created_at": "2026-09-30T10:00:00Z",
  "updated_at": "2026-09-30T10:02:12Z"
}
CampoDescripción
requirements[].statusmissing (sin documento), processing, valid, invalid, review o failed (el análisis falló).
requirements[].analysis_idEl análisis que cuenta para el requisito.
documents[]Todos los análisis del expediente, en orden de llegada: requisito, tipo detectado, nombre del fichero, estado del análisis, veredicto (verdict_status) y resultado final tras la revisión humana (final_status).
verdict.statusVeredicto global. Ver abajo.
verdict.reasons[]Por qué: code, severity, message (en el idioma de la petición) y requirement_key si se refiere a un requisito.
verdict.checks[]Comprobaciones cruzadas: code, passed (true, false o null si no hay datos), severity, message y analyses implicados.
holderTitular de referencia: nombre, número de documento y fecha de nacimiento del primer documento que los tiene, o null.
verification_link_idEl enlace que creó el expediente, o null.

Los mensajes de reasons y checks salen en el idioma de la cabecera Accept-Language (es, en, pt, fr).

Veredicto global

Cada requisito toma el estado de su análisis. Si una persona revisó el análisis, cuenta su decisión (final_status); si está pendiente de revisión, o no tiene veredicto, cuenta como review. El expediente:

verdict.statusCuándo (en este orden)
invalidAlgún requisito es invalid o falla una comprobación cruzada.
incompleteFalta algún requisito, se está analizando o su análisis falló.
reviewTodo está, pero algún documento está en review.
complete_validTodos los requisitos son valid y las comprobaciones cruzadas no fallan.

Sin requisitos, cada documento añadido cuenta como un requisito (y sin ningún documento, incomplete con el motivo no_documents). Los motivos por requisito son requirement_missing, requirement_processing, requirement_failed, requirement_invalid y requirement_review.

El veredicto se recalcula solo cuando termina un análisis del expediente o cuando una persona decide su revisión (en el panel o con POST /v1/analyses/{id}/review). Los expedientes no tienen webhook propio: consulta GET /v1/dossiers/{id} al recibir el analysis.completed o analysis.reviewed del documento, o usa un enlace de verificación y su evento verification_link.completed.

Comprobaciones cruzadas

codeQué compruebaSi falla
same_holderQue todos los documentos con titular son de la misma persona: nombre (comparación flexible), número de documento normalizado y fecha de nacimiento; solo los datos presentes en ambos documentos.passed: false, severity: "error" y el expediente pasa a invalid. Sin datos que comparar (por ejemplo, un solo documento con titular), passed: null y severity: "warning", que no cambia el veredicto.
dates_consistentQue la fecha de nacimiento es la misma en todos, que ninguna fecha de emisión es futura y que ninguna es anterior al nacimiento.passed: false, severity: "error" y el expediente pasa a invalid. Sin fechas que comparar, passed: null.

Solo cuentan los análisis completados (el que cuenta para cada requisito y los documentos sueltos). Los valores enmascarados con mask_fields no se comparan.

Listar, recuperar y borrar

curl -G https://api.constaia.com/v1/dossiers \
  -H "Authorization: Bearer $CONSTAIA_API_KEY" \
  --data-urlencode "verdict=review" \
  --data-urlencode "metadata[registration_id]=4821"
ParámetroDescripción
verdictcomplete_valid, incomplete, invalid o review.
referenceTu referencia exacta.
metadata[clave]Filtra por un valor de metadata. Repetible: deben coincidir todas.
limit, starting_afterPaginación: 1–100 (20 por defecto) y el id del último de la página anterior.

GET /v1/dossiers/{id} devuelve el objeto. DELETE /v1/dossiers/{id} devuelve { "id": "dos_…", "object": "dossier", "deleted": true } y no borra los análisis (para eso, ver borrado por metadatos). Un expediente de otro modo o que no existe devuelve 404 resource_missing.

SDK

dossier.ts
import { Constaia } from "@constaia/sdk";
import { fromPath } from "@constaia/sdk/node";

const constaia = new Constaia();

const dossier = await constaia.dossiers.create({
  reference: "inscripcion-4821",
  requirements: [
    { key: "id_card", expect: ["es_dni", "es_nie"] },
    { key: "medical", expect: "medical_certificate_sport", checks: { maxAgeDays: 180 } },
  ],
});

await constaia.dossiers.addDocument(dossier.id, await fromPath("dni.jpg"), { requirementKey: "id_card" });
const updated = await constaia.dossiers.addDocument(
  dossier.id,
  { analysisId: "an_01J9Z8Q3K4M5N6P7Q8R9S0T1V3" },
  { requirementKey: "medical" },
);

console.log(updated.verdict.status); // complete_valid | incomplete | invalid | review

Errores

HTTPcodeCuándo
422invalid_parameterUn campo no es válido (param indica cuál).
422duplicate_document_keyDos requisitos con la misma key.
422template_not_foundLa plantilla no existe en la cuenta.
422invalid_document_keyrequirement_key no es un requisito del expediente.
404resource_missingEl expediente o el analysis_id no existen (o son de otro modo).
402, 429…Al añadir un fichero, los mismos errores que analyze (saldo, límites, fichero no válido).

Siguientes pasos

En esta página