POST /v1/analyze
Referencia completa de POST /v1/analyze: formas de envío, todas las opciones y checks, objeto analysis campo a campo, códigos de estado y ejemplos.
POST https://api.constaia.com/v1/analyze recibe un documento, lo clasifica, extrae sus campos y, si le dices qué esperas con expect, lo valida y devuelve un veredicto valid, invalid o review. Es el endpoint principal de la API.
Si solo necesitas saber qué documento es (por ejemplo, para enrutarlo), usa POST /v1/classify, que cuesta 0,2 créditos. Para muchos documentos a la vez, POST /v1/batches. Si dudas, mira Qué endpoint usar.
Enviar el documento
Hay dos formas de enviar la petición. Cualquier otro Content-Type devuelve 415 unsupported_content_type.
1. multipart/form-data
| Campo | Tipo | Descripción |
|---|---|---|
file | binario | El documento. Obligatorio. |
options | string (JSON) | Las opciones serializadas en JSON. Opcional. |
curl https://api.constaia.com/v1/analyze \
-H "Authorization: Bearer $CONSTAIA_API_KEY" \
-F file=@dni_valid.jpg \
-F 'options={"expect":"es_dni","checks":{"min_age_years":18}}'Atajo para pruebas rápidas: si no envías options, puedes pasar expect como campo del formulario, repetible para varios tipos:
curl https://api.constaia.com/v1/analyze \
-H "Authorization: Bearer $CONSTAIA_API_KEY" \
-F file=@nie.jpg -F expect=es_dni -F expect=es_nie -F expect=passportSi envías options, el campo expect suelto se ignora.
2. application/json
Con una URL pública o con el fichero en base64:
| Campo | Tipo | Descripción |
|---|---|---|
file_url | string | URL https:// del documento. Constaia la descarga con un límite de 20 MB y 15 s, sin seguir redirecciones y sin acceder a IPs privadas. |
file_base64 | string | Contenido del fichero en base64. Se tolera el prefijo data:…;base64,. |
filename | string | Nombre del fichero. Recomendado con file_base64; con file_url sustituye al nombre que sale de la URL. |
options | objeto | Las opciones. También puedes ponerlas directamente en la raíz del cuerpo. |
curl https://api.constaia.com/v1/analyze \
-H "Authorization: Bearer $CONSTAIA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"file_url": "https://example.com/uploads/justificante.pdf",
"options": {
"expect": "payment_receipt",
"checks": { "expected_amount": 45, "expected_reference": "INSCRIPCION 123" }
}
}'curl https://api.constaia.com/v1/analyze \
-H "Authorization: Bearer $CONSTAIA_API_KEY" \
-H "Content-Type: application/json" \
-d "{
\"file_base64\": \"$(base64 < dni_valid.jpg | tr -d '\n')\",
\"filename\": \"dni_valid.jpg\",
\"expect\": \"es_dni\",
\"language\": \"en\"
}"Usa una de las dos formas para las opciones: si el cuerpo trae options, las claves de la raíz (salvo file_url, file_base64 y filename) no se leen.
Ficheros admitidos
- Formatos: JPEG, PNG, WEBP, HEIC y PDF. El tipo se detecta por el contenido (magic bytes), no por la extensión.
- Tamaño: hasta 20 MB (
413 file_too_large). - Páginas: un PDF admite hasta 30 páginas en modo síncrono y hasta 200 con
async: trueo dentro de un lote. Por encima,422 too_many_pages. - Un DNI con anverso y reverso puede ir en un solo fichero (una imagen con las dos caras o un PDF de dos páginas).
- En modo test el nombre del fichero decide la respuesta simulada, así que cuida
filename.
Opciones
Las opciones son estrictas: una clave desconocida devuelve 422 invalid_parameter con param: "options.<clave>".
| Opción | Tipo | Por defecto | Descripción |
|---|---|---|---|
expect | string o string[] (1–20) | — | Tipo o tipos de documento que aceptas, del catálogo. Con expect recibes verdict; sin él, verdict es null. Un tipo desconocido devuelve 422. |
extract | boolean u objeto | true | true: campos de la plantilla del tipo. false: sin extracción de campos. Un objeto JSON Schema: extrae los campos de tu esquema. |
checks | objeto | {} | Reglas de validación. Ver Checks. |
storage | none | temporary | persistent | valor de la cuenta, si no none | Qué se hace con el fichero. Ver Almacenamiento y privacidad. |
ttl_hours | entero 1–720 | valor de la cuenta, si no 24 | Horas que se conserva el fichero con storage: "temporary". |
keep_results | boolean | true | false: los datos extraídos se devuelven una vez y no se guardan. Un GET posterior devuelve 404. |
async | boolean | false | true: responde 202 al momento con status: "queued" y el resultado llega por webhook. |
export | array de json, csv, xlsx, xml, vcard, pdf | [] | Genera exportaciones y devuelve URLs firmadas en exports (caducan a las 24 h). Ver Exportaciones. |
metadata | objeto string → string | {} | Hasta 20 claves (clave ≤ 40 caracteres, valor ≤ 500). Vuelve en la respuesta y en los webhooks, y sirve para filtrar el listado. |
language | es | en | pt | fr | es | Idioma de verdict.reasons[].message, de checks[].message y de document.label. |
processing | sovereign | standard | perfil de la cuenta | Qué proveedores de IA pueden procesar el documento. Ver Perfil de procesamiento. |
precise_bboxes | boolean | false | Fuerza el modo ocr+llm (OCR + modelo) para obtener source.bbox finos por campo. Sin él, las cajas son aproximadas. |
Perfil de procesamiento
processing elige qué proveedores de IA pueden tocar el documento en esta petición:
sovereign: solo proveedores con sede y operación en la UE (un modelo europeo o alojado por Constaia, OCR de Mistral en París y los lectores locales de MRZ y PDF417).standard: además puede usar Claude en AWS Bedrock (Fráncfort,eu-central-1) o Gemini en Google Vertex AI (región UE). Los datos se procesan en regiones de la UE, pero AWS y Google son empresas con sede en EE. UU. (exposición a la CLOUD Act).- Si no la envías, se usa el perfil de tu cuenta. Si el perfil pedido no está disponible, la API responde
422 processing_unavailableconparam: "options.processing".
La respuesta dice siempre qué se usó en el objeto processing. Detalles en Residencia de datos.
Checks
checks también es estricto. Cada check solo se aplica si el tipo detectado tiene el campo correspondiente (por ejemplo, generic no tiene fecha de caducidad). Qué checks admite cada tipo lo indica GET /v1/document-types/{type} en su lista checks. Explicación detallada en Checks.
| Check | Tipo | Qué comprueba |
|---|---|---|
not_expired | boolean | La fecha de caducidad es posterior a hoy (o a reference_date). Activado por defecto en los tipos con caducidad (expiry_check_default: true); pasa false para desactivarlo. |
reference_date | "YYYY-MM-DD" | Fecha con la que se comparan caducidad, antigüedad y edad, en lugar de hoy. |
max_age_days | entero | La fecha de emisión no tiene más de N días (certificados, justificantes). |
min_age_years | entero | El titular tiene al menos N años según la fecha de nacimiento. |
max_age_years | entero | El titular tiene como mucho N años. |
holder | objeto | Los datos del titular coinciden: full_name, first_name, last_name, document_number, birth_date (todos opcionales). Comparación normalizada: sin tildes ni mayúsculas, orden de apellidos flexible y tolerante a erratas pequeñas. |
require_fields | string[] | Estos campos deben venir con valor. |
require_signature | boolean | El documento está firmado (certificados). |
require_stamp | boolean | El documento tiene sello (certificados). |
require_valid_signature | boolean | El PDF lleva una firma electrónica PAdES íntegra, sin cambios posteriores y de un emisor de confianza. Una foto o un escaneo nunca la cumplen. Ver Firmas digitales en PDF. |
expected_amount | número | El importe coincide: amount en payment_receipt, total en invoice. |
expected_iban | string | El IBAN coincide. |
expected_reference | string | El texto aparece en reference o en concept. |
{
"expect": ["es_dni", "es_nie", "passport"],
"checks": {
"min_age_years": 18,
"holder": { "full_name": "María García López", "birth_date": "1990-05-14" }
},
"storage": "none",
"metadata": { "registration_id": "123" },
"language": "es"
}Extraer campos con tu propio esquema
Pasa un JSON Schema en extract y los campos de fields serán los de tu esquema. Útil con generic o cuando solo quieres unos pocos campos. Las description de cada propiedad guían la extracción.
{
"expect": "generic",
"extract": {
"type": "object",
"properties": {
"club_name": { "type": "string", "description": "Nombre del club que emite el documento" },
"member_name": { "type": "string", "description": "Nombre completo del socio" },
"season": { "type": "string", "description": "Temporada, p. ej. 2026-2027" }
}
},
"checks": { "require_fields": ["club_name", "member_name"] }
}Síncrono o asíncrono
- Por defecto la petición espera el resultado hasta 30 segundos y responde
200constatus: "completed"(o"failed"). - Si el análisis tarda más de 30 s, la API responde
202con el análisis enqueuedoprocessing. El trabajo sigue y el resultado llega por el webhookanalysis.completed; también puedes consultarGET /v1/analyses/{id}. - Con
async: truela respuesta es202inmediata constatus: "queued". Permite PDF de hasta 200 páginas.
Tu código debe tratar siempre el 202: comprueba status antes de leer verdict o fields.
Respuesta: el objeto analysis
{
"id": "an_01J9Z8Q3K4M5N6P7Q8R9S0T1V2",
"object": "analysis",
"status": "completed",
"livemode": false,
"created_at": "2026-09-29T10:00:00Z",
"completed_at": "2026-09-29T10:00:02Z",
"file": { "name": "dni_valid.jpg", "mime_type": "image/jpeg", "pages": 1, "size_bytes": 482133 },
"document": { "type": "es_dni", "label": "DNI (España)", "confidence": 0.97, "side": "both", "country": "ESP" },
"verdict": {
"expected": ["es_dni"],
"match": true,
"status": "valid",
"reasons": [
{ "code": "type_match", "severity": "info", "message": "El documento es DNI (España)." },
{ "code": "not_expired", "severity": "info", "message": "Vigente hasta el 12/03/2031." }
]
},
"fields": {
"document_number": {
"value": "12345678Z",
"confidence": 0.99,
"validated": true,
"source": { "page": 1, "bbox": [0.61, 0.12, 0.83, 0.16] }
}
},
"checks": [
{ "code": "nif_check_digit", "passed": true, "message": "La letra del documento 12345678Z es correcta." },
{ "code": "mrz_checksums", "passed": true, "message": "Los dígitos de control de la MRZ son correctos." },
{ "code": "mrz_matches_visual", "passed": true, "message": "La MRZ coincide con los datos impresos." }
],
"warnings": [],
"signature": null,
"exports": {},
"storage": { "mode": "none", "file_deleted_at": "2026-09-29T10:00:02Z", "expires_at": null },
"processing": {
"profile": "sovereign",
"region": "eu",
"mode": "vlm",
"providers": [{ "name": "mock", "region": "local", "role": "llm", "model": "mock-llm-1" }]
},
"usage": { "credits": 0, "pages": 1 },
"metadata": {}
}En el ejemplo, fields está recortado a un campo. Un DNI devuelve también first_name, last_name_1, last_name_2, sex, nationality, birth_date, expiry_date, issue_date, support_number, address, birth_place, parents y mrz, cada uno con la misma estructura.
| Campo | Tipo | Descripción |
|---|---|---|
id | string | Id del análisis, prefijo an_. |
object | "analysis" | Tipo de objeto. |
status | queued | processing | completed | failed | Estado del análisis. |
livemode | boolean | true con clave ck_live_, false con ck_test_. |
batch_id | string | Solo si el análisis pertenece a un lote. |
created_at | string ISO 8601 | Creación. |
completed_at | string | null | Fin del análisis; null mientras no termina. |
file | objeto | name, mime_type, pages, size_bytes del fichero recibido. |
document | objeto | null | Tipo detectado: type, label (en el idioma de language), confidence (0–1), side (front, back, both o null), country (ISO alfa-3 o null). |
verdict | objeto | null | Solo con expect. expected (tu lista), match (el tipo detectado está en expected), status y reasons. |
verdict.status | valid | invalid | review | invalid si algún motivo tiene severity: "error"; review si alguno tiene warning; valid en otro caso. Ver Veredictos. |
verdict.reasons[] | objeto | code, severity (info, warning, error) y message localizado. |
fields | objeto | Un objeto por campo: value, confidence (0–1), validated y source. |
fields.<campo>.validated | boolean | null | true/false si un validador determinista revisó el campo (letra del NIF, IBAN…); null si no aplica. |
fields.<campo>.source | objeto | null | page y bbox [x0, y0, x1, y1] normalizado de 0 a 1; bbox puede ser null. |
checks | array | Validadores deterministas ejecutados: code, passed, message. |
warnings | string[] | Señales de calidad o posible manipulación. Son indicios, no pruebas. |
signature | objeto | null | Firma electrónica del PDF (solo PDF; null en imágenes). Ver abajo. |
exports | objeto | Formato → URL firmada de descarga (24 h). Vacío si no pediste export. |
storage | objeto | mode, file_deleted_at (cuándo se borró el fichero) y expires_at (con temporary). |
processing | objeto | null | Perfil, región y proveedores que procesaron el documento. Ver abajo. |
usage | objeto | credits consumidos (0 en test y en análisis fallidos) y pages procesadas. |
metadata | objeto | Tu metadata, tal cual. |
error | objeto | Solo si status es failed: code (p. ej. processing_failed) y message. Los análisis fallidos no se cobran. |
El objeto processing
Dice qué perfil se aplicó y qué proveedores tocaron el documento, para que puedas demostrarlo en una auditoría:
{
"profile": "sovereign",
"region": "eu",
"mode": "vlm",
"providers": [
{ "name": "tesseract", "region": "local", "role": "local", "model": "mrz" },
{ "name": "openai_compat", "region": "de-fra", "role": "llm", "model": "mistral-small-3.2" }
]
}| Campo | Descripción |
|---|---|
profile | sovereign o standard: el de la petición o, si no lo enviaste, el de tu cuenta. |
region | Región de datos: hoy siempre eu. |
mode | vlm (una llamada multimodal; cajas aproximadas) u ocr+llm (OCR + modelo; cajas finas, con precise_bboxes o en PDF escaneados largos). |
providers[] | Cada proveedor que procesó el documento: name, region, role (ocr, llm o local) y model. Los lectores local (MRZ, PDF417) se ejecutan en los servidores de Constaia sin terceros. |
En modo test el único proveedor es mock. Mientras el análisis está en cola, processing puede ser null.
El objeto signature
Solo en PDF. Constaia verifica la firma PAdES del fichero original (integridad, cadena del certificado y cambios posteriores) sin coste adicional:
{
"status": "valid",
"reasons": ["signature_valid"],
"signer": "SELLO ELECTRONICO DEL MINISTERIO DE JUSTICIA",
"issuer": "AC Sector Público",
"signed_at": "2026-09-28T09:14:03Z",
"trusted": true,
"trust_anchor": "AC RAIZ FNMT-RCM",
"signatures": 1
}status es valid, invalid, missing, modified o untrusted. Qué garantiza cada estado y cómo exigirlo con require_valid_signature: Firmas digitales en PDF.
Motivos (verdict.reasons[].code)
El mismo código puede venir con severidad info (se cumple), warning (lleva a review) o error (lleva a invalid). Por ejemplo, un DNI caducado devuelve { "code": "not_expired", "severity": "error", "message": "Caducado el 15/06/2020." }.
El primer motivo siempre es sobre el tipo:
type_match(info): el tipo detectado está enexpect.type_mismatch(error, lleva ainvalid): se reconoce un tipo del catálogo, pero no es ninguno de los esperados.type_unknown(warning, lleva areview): el documento no se ha podido identificar (tipogeneric) o la confianza de la clasificación es menor de 0,5.
Lista completa de códigos: type_match, type_mismatch, type_unknown, not_expired, max_age_days, age, min_age_years, max_age_years, holder, required_field_missing, require_signature, require_stamp, expected_amount, expected_iban, expected_reference, not_fit_for_sport, has_records, low_quality, low_confidence, y el código de cualquier validador determinista que falle (normalmente con severidad error).
Además:
- Firma del PDF:
signature_valid(info),signature_invalid(siempreerror),signature_missing,document_modified_after_signingyuntrusted_signer. Sinrequire_valid_signatureestos tres últimos sonwarning(ysignature_missingsolo aparece en tipos que suelen ir firmados); con él, sonerror. Ver Firmas digitales en PDF. - Metadatos del PDF:
edited_suspected(warning): el PDF lo generó un editor conocido, su fecha de modificación es muy posterior a la de creación o cambió después de firmarse. - Formulario I-9 (EE. UU.):
i9_list(info) indica a qué lista (A, B o C) pertenece el documento. Ver Documentos de EE. UU..
Validadores (checks[].code)
| Código | Qué comprueba |
|---|---|
nif_check_digit | Letra de control de DNI, NIE o NIF. |
mrz_checksums | Dígitos de control de la zona MRZ. |
mrz_matches_visual | La MRZ coincide con los datos impresos. |
iban_checksum | Dígitos de control del IBAN. |
invoice_totals | Base, IVA y total de la factura cuadran. |
csv_format | Formato del código seguro de verificación (CSV). Solo el formato: Constaia no consulta el servicio del Ministerio. Verifícalo en la sede del emisor si lo necesitas. |
date_consistency | Coherencia entre fechas del documento. |
id_number_checksum | Dígito de control de un identificador nacional del catálogo (CPF, codice fiscale, PESEL…). |
id_number_format | Formato de un identificador sin dígito de control (número de permiso de cada estado de EE. UU., SSN, EIN, código ZIP…). |
id_number_matches_birth_date | La fecha de nacimiento codificada en el identificador coincide con la impresa. |
aamva_matches_visual | El código de barras PDF417 (AAMVA) del reverso de un permiso o ID de EE. UU./Canadá coincide con el anverso impreso. |
Avisos (warnings)
| Código | Significado |
|---|---|
low_quality | Calidad baja en general. |
blurry | Imagen desenfocada. |
cropped | El documento está recortado. |
glare | Reflejos que tapan datos. |
screen_photo_suspected | Posible foto de una pantalla. |
photocopy_suspected | Posible fotocopia. |
edited_suspected | Posible edición digital. |
multiple_documents | Hay más de un documento en el fichero. |
side_missing | Falta una cara. |
language_mismatch | El idioma no es el esperado para el tipo. |
Los warnings son indicios, no prueba de autenticidad.
Constaia no es un KYC biométrico y no compara caras.
Códigos de estado y errores
| HTTP | Cuándo |
|---|---|
200 | Análisis terminado (completed o failed). |
202 | En cola: async: true o el análisis superó los 30 s. |
400 | invalid_json, invalid_multipart, invalid_options (el campo options no es JSON), missing_file, empty_file, invalid_base64, invalid_file_url (no https o IP no permitida), invalid_idempotency_key. |
401 | missing_api_key, invalid_api_key. |
402 | insufficient_credits, monthly_cap_reached (se alcanzaría el tope de gasto mensual) o email_not_verified (créditos gratis en live sin email verificado). Solo claves live. |
409 | idempotency_in_progress: la petición original con esa Idempotency-Key aún está en curso. |
413 | file_too_large: más de 20 MB. |
415 | unsupported_file_type (no es JPEG, PNG, WEBP, HEIC ni PDF), unsupported_content_type. |
422 | invalid_parameter (con param, p. ej. options.expect), too_many_pages, unreadable_image, unreadable_pdf, file_url_unreachable, idempotency_key_reused, processing_unavailable (el perfil de processing pedido no está disponible). |
429 | rate_limited (peticiones/s), concurrency_limit (análisis síncronos simultáneos) o pages_rate_limited (páginas/min), con Retry-After. Ver Límites. |
503 | live_mode_unavailable: el modo live no está disponible; usa ck_test_ mientras tanto. |
Formato y tratamiento de errores en Errores. Para reintentar sin cobros dobles, envía la cabecera Idempotency-Key (Idempotencia).
Ejemplos completos
curl https://api.constaia.com/v1/analyze \
-H "Authorization: Bearer $CONSTAIA_API_KEY" \
-H "Idempotency-Key: registration-123-dni" \
-F file=@dni_valid.jpg \
-F 'options={
"expect": ["es_dni", "es_nie", "passport"],
"checks": { "min_age_years": 18, "holder": { "full_name": "María García López" } },
"metadata": { "registration_id": "123" }
}'JSON con file_url y file_base64
import { readFile } from "node:fs/promises";
import { Constaia } from "@constaia/sdk";
const constaia = new Constaia();
const fromUrl = await constaia.analyze(
{ fileUrl: "https://example.com/uploads/justificante.pdf" },
{ expect: "payment_receipt", checks: { expectedAmount: 45, expectedReference: "INSCRIPCION 123" } },
);
const fromBase64 = await constaia.analyze(
{ base64: (await readFile("./invoice.pdf")).toString("base64"), filename: "invoice.pdf" },
{ expect: "invoice", export: ["xlsx"] },
);
console.log(fromUrl.verdict?.status, fromBase64.exports.xlsx);Siguientes pasos
Autenticación
Autentica tus llamadas a la API de Constaia con claves Bearer ck_live_ y ck_test_, dónde crearlas y revocarlas, y por qué nunca van al navegador.
POST /v1/classify
Referencia de POST /v1/classify: identifica el tipo de un documento por 0,2 créditos, con candidatos y veredicto de tipo, para enrutarlo antes de analizarlo.