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 ruta | Qué hace |
|---|---|
POST /v1/dossiers | Crea un expediente con sus requisitos. |
POST /v1/dossiers/{id}/documents | Añade un documento: un fichero (se analiza) o un análisis ya hecho. |
GET /v1/dossiers | Lista 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| Campo | Tipo | Descripción |
|---|---|---|
reference | string | null | Tu referencia (hasta 200 caracteres). Sirve para filtrar el listado. |
template | string | null | Plantilla (tpl_…) que se aplica a cada documento que se analiza en el expediente. 422 template_not_found si no existe. |
requirements | objeto[] (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". |
metadata | objeto string → string | Hasta 20 claves. Se copia a cada análisis que se hace dentro del expediente. |
face_verification | { enabled, required? } | null | Requisito 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}/documentsHay dos maneras:
| Cómo | Cuerpo | Qué pasa |
|---|---|---|
| Un fichero | multipart/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 hecho | JSON { "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 suschecks) y se asigna al primer requisito sin documento cuyoexpectincluye 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
{
"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"
}| Campo | Descripción |
|---|---|
requirements[].status | missing (sin documento), processing, valid, invalid, review o failed (el análisis falló). |
requirements[].analysis_id | El 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.status | Veredicto 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. |
holder | Titular de referencia: nombre, número de documento y fecha de nacimiento del primer documento que los tiene, o null. |
verification_link_id | El 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.status | Cuándo (en este orden) |
|---|---|
invalid | Algún requisito es invalid o falla una comprobación cruzada. |
incomplete | Falta algún requisito, se está analizando o su análisis falló. |
review | Todo está, pero algún documento está en review. |
complete_valid | Todos 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
code | Qué comprueba | Si falla |
|---|---|---|
same_holder | Que 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_consistent | Que 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ámetro | Descripción |
|---|---|
verdict | complete_valid, incomplete, invalid o review. |
reference | Tu referencia exacta. |
metadata[clave] | Filtra por un valor de metadata. Repetible: deben coincidir todas. |
limit, starting_after | Paginació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
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 | reviewErrores
| HTTP | code | Cuándo |
|---|---|---|
422 | invalid_parameter | Un campo no es válido (param indica cuál). |
422 | duplicate_document_key | Dos requisitos con la misma key. |
422 | template_not_found | La plantilla no existe en la cuenta. |
422 | invalid_document_key | requirement_key no es un requisito del expediente. |
404 | resource_missing | El 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
Enlaces de verificación
Referencia de /v1/verification-links: crea una página alojada donde la persona sube sus documentos desde el móvil, recibe los resultados por webhook, callback o email y consulta, lista o cancela enlaces.
Sesiones y claves publicables
Referencia de /v1/sessions y de las claves publicables pk_: tu backend crea una sesión de 15 minutos y el navegador sube cada documento directamente a Constaia con el client_secret, sin exponer tu clave secreta.