Checks
Referencia de las opciones de checks (vigencia, antigüedad, edad, titular, firma, importes) y de las validaciones deterministas de NIF, MRZ, IBAN y facturas.
Los checks son las reglas de negocio que Constaia evalúa sobre un documento. Se mandan en options.checks de
POST /v1/analyze (y en las opciones de los lotes) y cada uno
produce uno o varios motivos en verdict.reasons. Cómo se combinan esos motivos en valid, invalid o review
está en veredictos.
Hay dos familias:
- Checks que pides tú (
options.checks): vigencia, antigüedad, edad, titular, campos obligatorios, firma, sello, importes, IBAN y referencia. - Comprobaciones deterministas (
checks[]en la respuesta): las ejecuta Constaia siempre que el tipo de documento las admite (letra del NIF, MRZ, IBAN, totales de factura…). No se configuran.
Los checks necesitan expect
Los checks se evalúan siempre, pero sus motivos viven dentro de verdict, y verdict solo existe si mandas
expect. Sin expect verás fields y checks[], pero no el resultado de not_expired, holder y demás.
Ejemplo
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,
"holder": { "full_name": "María García López", "document_number": "12345678Z" }
}
}'camelCase en el SDK de JavaScript, snake_case en la API
El SDK de JavaScript acepta las opciones en camelCase (notExpired, maxAgeDays, holder.fullName,
expectedAmount…) y las convierte a snake_case. La API, el SDK de PHP, Python y el MCP usan snake_case tal cual
(not_expired, max_age_days, holder.full_name, expected_amount). Las respuestas son siempre snake_case.
Referencia de opciones
| Opción (API) | SDK JS | Tipo | Por defecto | Motivo que produce |
|---|---|---|---|---|
not_expired | notExpired | boolean | true en tipos con caducidad | not_expired |
reference_date | referenceDate | "YYYY-MM-DD" | hoy | (cambia la fecha de las demás reglas) |
max_age_days | maxAgeDays | entero 0–36500 | — | max_age_days |
min_age_years | minAgeYears | entero 0–150 | — | age o min_age_years |
max_age_years | maxAgeYears | entero 0–150 | — | age o max_age_years |
holder | holder | objeto | — | holder |
require_fields | requireFields | string[] (máx. 50) | — | required_field_missing |
require_signature | requireSignature | boolean | — | require_signature |
require_stamp | requireStamp | boolean | — | require_stamp |
expected_amount | expectedAmount | number | — | expected_amount |
expected_iban | expectedIban | string (máx. 40) | — | expected_iban |
expected_reference | expectedReference | string (máx. 140) | — | expected_reference |
Los checks se aplican al tipo detectado (document.type) y leen sus campos. Para saber qué checks tienen
sentido en cada tipo, consulta checks y expiry_check_default en
GET /v1/document-types/{type} o el catálogo.
not_expired
Comprueba que la fecha de caducidad es igual o posterior a hoy (o a reference_date).
- Activado por defecto en los tipos con caducidad (
expiry_check_default: true, por ejemploes_dni,es_nieypassport). Para desactivarlo manda"not_expired": false. - Solo se evalúa si el tipo tiene fecha de caducidad.
infosi está vigente,errorsi ha caducado,warningsi no se pudo leer la fecha.
{ "code": "not_expired", "severity": "error", "message": "Caducado el 15/06/2020." }reference_date
Fecha YYYY-MM-DD que sustituye a "hoy" en not_expired, max_age_days y en el cálculo de edad. Útil para
evaluar a fecha de un evento: "¿estará vigente el DNI el día de la competición?", "¿tendrá 18 años el 1 de enero?".
{ "expect": "es_dni", "checks": { "reference_date": "2027-01-01", "min_age_years": 18 } }max_age_days
La fecha de emisión no puede tener más de N días. Típico en certificados: médico de menos de un año, certificado de delitos sexuales de menos de 90 días.
infosi cumple,errorsi es más antiguo (o si la fecha de emisión está más de un día en el futuro),warningsi no hay fecha de emisión.
{ "code": "max_age_days", "severity": "info", "message": "Emitido hace 14 días (máximo 90)." }min_age_years y max_age_years
Edad del titular calculada con la fecha de nacimiento a hoy (o a reference_date).
- Si cumple: un motivo
ageconinfo("El titular tiene 36 años."). - Si es más joven que el mínimo:
min_age_yearsconerror. Si es mayor que el máximo:max_age_yearsconerror. - Si no se pudo leer la fecha de nacimiento:
ageconwarning.
holder
Compara los datos del documento con los de la persona que esperas. Todos los subcampos son opcionales; manda solo los que tengas.
| Subcampo (API) | SDK JS | Cómo se compara |
|---|---|---|
full_name | fullName | Nombre completo con normalización (ver abajo). |
first_name | firstName | Con last_name, como nombre completo. Solo, basta con que sus palabras aparezcan en el nombre del documento. |
last_name | lastName | Igual que first_name. |
document_number | documentNumber | Número normalizado (sin espacios ni guiones, sin distinguir mayúsculas). |
birth_date | birthDate | Igualdad exacta en formato YYYY-MM-DD. |
Reglas de comparación de nombres:
- No distingue mayúsculas ni acentos:
María García=MARIA GARCIA. - Admite distinto orden de apellidos y nombre.
- Tolera errores tipográficos pequeños.
- Con solo
first_nameo sololast_name, las partículas (de,del…) no cuentan.
Resultado:
- Si todo lo que mandaste coincide: un motivo
holderconinfoque lista los campos ("Los datos del titular coinciden (full_name)."). - Cada dato que no coincide:
holderconerror, indicando lo leído y lo esperado. - Cada dato que el documento no muestra o no se pudo leer:
holderconwarning.
{ "code": "holder", "severity": "error", "message": "Holder mismatch: full_name is “MARÍA GARCÍA LÓPEZ”, expected “Juan Pérez”." }require_fields
Lista de campos que deben venir con valor. Admite rutas con punto para campos anidados (por ejemplo
seller.tax_id en invoice). Cada campo vacío produce un motivo required_field_missing con error. Los nombres de
campo de cada tipo están en GET /v1/document-types/{type}.
require_signature y require_stamp
Exigen firma o sello. Leen los campos signature_present y stamp_present, que existen en tipos como
medical_certificate_sport. info si están, error si no. Úsalos solo en tipos que tengan esos campos: en un tipo
sin ellos el resultado es error.
expected_amount, expected_iban, expected_reference
Para justificantes de pago (payment_receipt) y facturas (invoice):
| Opción | Qué compara |
|---|---|
expected_amount | amount (o total en invoice) con una tolerancia de medio céntimo. |
expected_iban | Que el IBAN aparezca entre los del documento (ordenante o beneficiario). Sin espacios y sin distinguir mayúsculas. |
expected_reference | Que el texto esperado, normalizado, esté contenido en reference o en concept. |
{
"expect": "payment_receipt",
"checks": {
"expected_amount": 45,
"expected_iban": "ES7921000813610123456789",
"expected_reference": "INSCRIPCION 123"
}
}[
{ "code": "type_match", "severity": "info", "message": "El documento es Justificante de pago / transferencia." },
{ "code": "expected_amount", "severity": "info", "message": "amount coincide con lo esperado." },
{ "code": "expected_iban", "severity": "info", "message": "iban coincide con lo esperado." },
{ "code": "expected_reference", "severity": "info", "message": "reference coincide con lo esperado." }
]Reglas propias de cada tipo
Algunas reglas se aplican siempre, sin pedirlas:
| Tipo | Código | Severidad |
|---|---|---|
medical_certificate_sport | not_fit_for_sport | error si el certificado no declara apto. |
es_sexual_offences_certificate, es_criminal_record_certificate | has_records | info sin antecedentes, error con antecedentes. |
Comprobaciones deterministas
Además de lo que pides, Constaia ejecuta validaciones algorítmicas (sin IA) según el tipo. Aparecen en checks[]:
"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." }
]| Código | Qué verifica |
|---|---|
nif_check_digit | Letra de control de cada DNI, NIE o CIF que aparece en el documento (titular, emisor, comprador…). Los números que no tienen formato español (pasaportes, identificadores extranjeros) se ignoran. |
mrz_checksums | Dígitos de control de la zona MRZ (DNI, NIE/TIE, pasaporte). En un DNI o NIE con una sola cara, sin MRZ, se añade el aviso side_missing. |
mrz_matches_visual | Con una MRZ válida: que número, fecha de nacimiento, caducidad y nombre coincidan con lo impreso. |
iban_checksum | Dígitos de control de cada IBAN (una entrada por IBAN: ordenante, beneficiario…). |
invoice_totals | Que líneas, base imponible, IVA, retención y total de la factura cuadren. |
csv_format | Solo el formato del código seguro de verificación de certificados españoles. |
date_consistency | Coherencia de fechas: nacimiento anterior a la emisión, emisión anterior a la caducidad, nacimiento no futuro. Solo aparece cuando falla. |
Qué validadores tiene cada tipo lo indica validators en GET /v1/document-types/{type}.
Cuando una comprobación falla (passed: false), se añade a verdict.reasons un motivo con el mismo código y
severidad error, así que el veredicto pasa a invalid. Además, el campo afectado queda con
fields.<campo>.validated: false; si pasa, validated: true. Los campos sin validador tienen validated: null.
csv_format no consulta al Ministerio
csv_format comprueba que el código tiene un formato válido, no que el certificado exista. Constaia no consulta el
servicio de verificación del Ministerio de Justicia. Si necesitas esa garantía, verifica el CSV en la sede
electrónica oficial del emisor.
Opciones estrictas: 422
options y options.checks son estrictos. Una clave desconocida, un tipo incorrecto o un valor fuera de rango
devuelven 422 con código invalid_parameter. param indica dónde está el problema:
- Valor no válido: la ruta completa, por ejemplo
options.checks.max_age_days. - Clave desconocida: la ruta del objeto que la contiene (
optionsuoptions.checks), y elmessagenombra la clave.
{
"error": {
"type": "invalid_request",
"code": "invalid_parameter",
"message": "Unrecognized key: \"not_expire\"",
"param": "options.checks",
"request_id": "req_01J..."
}
}Así un error tipográfico (not_expire, holder.name) nunca se ignora en silencio. En el SDK de JavaScript llega como
InvalidRequestError; en PHP como InvalidRequestException. Ver errores.
Siguientes pasos
Veredictos y motivos
Cómo se calcula el veredicto valid, invalid o review a partir de la severidad de los motivos, tabla completa de códigos estables y qué hacer en cada caso.
Errores
Formato de error de la API de Constaia, todos los códigos por estado HTTP, clases de error de los SDK de JavaScript y PHP y qué errores conviene reintentar.