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.
Cuando una petición falla, la API responde con un estado HTTP distinto de 2xx y un cuerpo JSON con esta forma:
{
"error": {
"type": "invalid_request",
"code": "invalid_parameter",
"message": "Tipo de documento desconocido. Consulta GET /v1/document-types.",
"param": "options.expect",
"request_id": "req_01J9Z8Q3K4M5N6P7Q8R9S0T1V2"
}
}| Campo | Descripción |
|---|---|
type | Categoría del error. Hay siete, ver tabla de abajo. |
code | Código estable y concreto. Programa tu lógica contra code, no contra message. |
message | Explicación para personas (en español). Puede cambiar de redacción. |
param | Opcional. El parámetro o cabecera implicado: file, options.expect, options.checks.max_age_days, Idempotency-Key… |
request_id | Identificador de la petición. Es el mismo valor que la cabecera X-Request-Id. |
Un análisis fallido no es un error HTTP
Si el fichero se acepta pero el análisis no se puede completar, la respuesta es un análisis con status: "failed"
y error: { code, message } (por ejemplo processing_failed), no un error HTTP. Esos análisis no se cobran. Con
análisis asíncronos te llega el webhook analysis.failed.
Tipos y estados HTTP
| HTTP | type | Significado |
|---|---|---|
| 400 | invalid_request | Petición mal formada. |
| 401 | authentication | Falta la clave de API o no es válida. |
| 402 | insufficient_credits | No quedan créditos en modo real o se alcanzaría el tope de gasto mensual. |
| 403 | permission | La clave no puede hacer esa acción. |
| 404 | not_found | El recurso o la ruta no existen. |
| 409 | invalid_request | Conflicto de estado (idempotencia en curso, análisis sin terminar). |
| 413 | invalid_request | Fichero demasiado grande. |
| 415 | invalid_request | Tipo de fichero o de contenido no admitido. |
| 422 | invalid_request | Parámetros válidos en forma pero no en contenido, o documento ilegible. |
| 429 | rate_limited | Demasiadas peticiones por segundo, análisis simultáneos o páginas por minuto. |
| 500 | api_error | Error interno de Constaia. |
| 501 | invalid_request | Funcionalidad aún no disponible. |
| 503 | api_error | Servicio no disponible temporalmente o modo real no disponible. |
Todos los códigos
| HTTP | code | Qué significa | Qué hacer |
|---|---|---|---|
| 400 | invalid_json | El cuerpo no es JSON válido. | Revisa la serialización y Content-Type: application/json. |
| 400 | invalid_multipart | No se pudo leer el multipart/form-data. | Deja que tu cliente HTTP genere el boundary; no pongas la cabecera Content-Type a mano. |
| 400 | invalid_options | El campo options del multipart no es JSON válido. | Manda options como cadena JSON. |
| 400 | missing_file | No hay fichero: falta file, file_url o file_base64 (o un item del lote no tiene ninguno). | Añade el fichero. |
| 400 | empty_file | El fichero está vacío. | Comprueba que lees bien el fichero antes de enviarlo. |
| 400 | invalid_base64 | file_base64 no es base64 válido. | Codifica en base64 estándar (se tolera el prefijo data:…;base64,). |
| 400 | invalid_file_url | file_url no es una URL válida, no usa https o apunta a una dirección no permitida (IP privada). | Usa una URL https pública. |
| 400 | batch_too_large | El lote tiene más de 100 documentos. | Divide en lotes de hasta 100. |
| 400 | empty_batch | El lote no tiene documentos. | Añade al menos uno. |
| 400 | invalid_url | La URL de un endpoint de webhook no es https. | Usa https. |
| 400 | invalid_idempotency_key | Idempotency-Key supera 255 caracteres. | Acorta la clave. |
| 401 | missing_api_key | Falta Authorization: Bearer <clave>. | Añade la cabecera. |
| 401 | invalid_api_key | Clave con formato incorrecto, inexistente o revocada. | Comprueba la variable de entorno y la clave en el panel. |
| 402 | insufficient_credits | No quedan créditos para este análisis (o, en un lote, para todos los documentos). | Compra un pack o espera a la renovación mensual. Ver créditos. |
| 402 | monthly_cap_reached | El análisis superaría el tope de gasto mensual de la cuenta (monthly_credit_cap). No se cobra. | Sube el tope o espera al día 1 (UTC). No reintentes. Ver tope de gasto mensual. |
| 402 | email_not_verified | Intentas gastar los créditos gratis en modo real y nadie de la cuenta ha verificado su email. | Verifica el email desde el enlace que te enviamos (o reenvíalo en el panel). |
| 403 | forbidden | No tienes permiso para esta acción. | Revisa la clave y la cuenta. (En el panel, crear claves live sin email verificado da email_not_verified.) |
| 404 | resource_missing | El recurso no existe, es de otra cuenta, se borró o se creó con keep_results: false. | Comprueba el id. |
| 404 | route_not_found | La ruta no existe. | Revisa método y URL (/v1/…). |
| 409 | idempotency_in_progress | La petición original con esa Idempotency-Key sigue en curso. | Reintenta pasados unos segundos. Ver idempotencia. |
| 409 | analysis_not_completed | Pediste la exportación de un análisis que no ha terminado. | Espera a completed (webhook o GET /v1/analyses/{id}). |
| 413 | file_too_large | El fichero supera 20 MB. | Comprime o reduce la resolución. |
| 415 | unsupported_file_type | El contenido no es JPEG, PNG, WEBP, HEIC ni PDF (se detecta por los bytes, no por la extensión). | Convierte el fichero. |
| 415 | unsupported_content_type | La petición no es multipart/form-data ni application/json. | Usa uno de los dos. |
| 422 | invalid_parameter | Un parámetro no es válido: tipo desconocido en expect, opción inexistente, valor fuera de rango. param indica cuál. | Corrige el parámetro. Ver checks. |
| 422 | idempotency_key_reused | Esa Idempotency-Key ya se usó con otra petición distinta. | Usa una clave nueva por operación. |
| 422 | too_many_pages | El PDF tiene más de 30 páginas (síncrono) o de 200 (async o lotes). | Usa async: true o divide el PDF. |
| 422 | unreadable_image | No se pudo leer la imagen. | Pide otra foto. |
| 422 | unreadable_pdf | No se pudo leer el PDF (dañado o protegido). | Pide otro fichero. |
| 422 | file_url_unreachable | No se pudo descargar file_url (error de red, respuesta no 2xx o 15 s agotados). Si pesa más de 20 MB recibes 413 file_too_large. | Comprueba que la URL es pública y responde rápido, o sube el fichero. |
| 422 | processing_unavailable | El perfil de processing pedido no está disponible. | Usa el otro perfil o no envíes processing. Ver residencia de datos. |
| 429 | rate_limited | Superaste las peticiones por segundo de tu clave. | Espera lo que indica Retry-After y reintenta. Ver límites. |
| 429 | concurrency_limit | Tu cuenta ya tiene en curso el máximo de análisis síncronos simultáneos de su plan (solo modo real). | Espera a que terminen o usa async: true o lotes. |
| 429 | pages_rate_limited | Superaste las páginas por minuto de tu plan (solo modo real). | Espera lo que indica Retry-After. |
| 500 | internal_error | Error inesperado de Constaia. | Reintenta con la misma Idempotency-Key. Si persiste, escribe a soporte con el request_id. |
| 501 | not_implemented | La funcionalidad todavía no existe (hoy, POST /v1/verification-links). | No reintentes. |
| 503 | live_mode_unavailable | El entorno no tiene proveedores de IA reales configurados; las claves live nunca reciben resultados simulados. | Usa una clave ck_test_ mientras tanto. No lo reintentes en bucle. |
| 503 | billing_unavailable | El pago de packs no está disponible (solo en el panel). | Inténtalo más tarde. |
X-Request-Id
Todas las respuestas, correctas o no, llevan la cabecera X-Request-Id: req_…. En los errores también viene en
error.request_id. Guárdalo en tus logs: es lo primero que te pediremos si escribes a soporte
(hola@constaia.com).
HTTP/1.1 422 Unprocessable Entity
Content-Type: application/json
X-Request-Id: req_01J9Z8Q3K4M5N6P7Q8R9S0T1V2Clases de error en los SDK
Todos los errores del SDK de JavaScript extienden ConstaiaError y exponen status, type, code, param,
requestId, headers y raw. Los del SDK de PHP extienden Constaia\Exception\ConstaiaException y exponen
getHttpStatus(), getType(), getErrorCode(), getParam() y getRequestId(). Los del SDK de Python extienden
constaia.ConstaiaError y exponen .message, .status, .type, .code, .param y .request_id.
| Caso | JavaScript (@constaia/sdk) | PHP (Constaia\Exception\…) | Python (constaia) |
|---|---|---|---|
| 400, 409, 413, 415, 422 | InvalidRequestError | InvalidRequestException | InvalidRequestError |
| 401 | AuthenticationError | AuthenticationException | AuthenticationError |
| 402 | InsufficientCreditsError | InsufficientCreditsException | InsufficientCreditsError |
| 403 | PermissionError | PermissionException | PermissionDeniedError |
| 404 | NotFoundError | NotFoundException | NotFoundError |
| 429 | RateLimitError (.retryAfter en segundos) | RateLimitException (getRetryAfter()) | RateLimitError (.retry_after) |
| 501 | InvalidRequestError | ApiException | InvalidRequestError |
| 500, 503 y otros 5xx | APIError | ApiException | APIError |
| Red, DNS, TLS | APIConnectionError | ConnectionException | APIConnectionError |
| Tiempo agotado | APITimeoutError (extiende APIConnectionError) | ConnectionException | APITimeoutError (extiende APIConnectionError) |
| Firma de webhook | WebhookVerificationError | SignatureVerificationException | WebhookVerificationError |
En un navegador, el constructor de Constaia lanza un error con código secret_key_in_browser si le pasas una clave
ck_: la clave debe quedarse en tu servidor. Ver seguridad.
Qué reintentar
| Reintentable | No reintentable (corrige la petición) |
|---|---|
429 rate_limited (respeta Retry-After) | 400, 401, 403, 404 |
409 idempotency_in_progress | 402 insufficient_credits (hasta que recargues) |
500, 502, 503, 504 | 413, 415, 422 |
408 y errores de red o tiempo agotado | 409 analysis_not_completed (espera a que termine, no reintentes en bucle) |
501 not_implemented |
Los SDK de JavaScript, PHP y Python ya reintentan por ti los casos de la columna izquierda (2 reintentos por defecto,
maxRetries en JavaScript, max_retries en PHP y Python), respetan Retry-After y reutilizan la misma Idempotency-Key en cada reintento,
así que un reintento nunca cobra dos veces. Con 0 desactivas los reintentos y los gestionas tú. Si haces llamadas HTTP directas, manda tú una Idempotency-Key en cada
POST antes de reintentar. Ver idempotencia.
live_mode_unavailable
Es un 5xx y los SDK lo reintentan, pero no se resuelve solo en segundos. Si lo recibes, trabaja con una clave
ck_test_ y consulta estado y SLA.
Ejemplos de manejo
import {
Constaia,
ConstaiaError,
InsufficientCreditsError,
InvalidRequestError,
RateLimitError,
APIConnectionError,
} from "@constaia/sdk";
import { fromPath } from "@constaia/sdk/node";
const constaia = new Constaia({ maxRetries: 3 });
try {
const analysis = await constaia.analyze(await fromPath("./dni.jpg"), { expect: "es_dni" });
console.log(analysis.verdict?.status);
} catch (err) {
if (err instanceof InvalidRequestError) {
// 400/409/413/415/422: el problema está en la petición o en el fichero
console.error(`Petición no válida (${err.code}) en ${err.param ?? "-"}: ${err.message}`);
} else if (err instanceof InsufficientCreditsError) {
console.error("Sin créditos: compra un pack en el panel.");
} else if (err instanceof RateLimitError) {
console.error(`Límite alcanzado tras reintentar; vuelve a probar en ${err.retryAfter ?? 1} s.`);
} else if (err instanceof APIConnectionError) {
console.error("No se pudo conectar con Constaia:", err.message);
} else if (err instanceof ConstaiaError) {
console.error(`Error ${err.status} ${err.code} (request ${err.requestId})`);
} else {
throw err;
}
}Siguientes pasos
Verificación de firmas digitales en PDF
Cómo verifica Constaia la firma electrónica PAdES de un PDF: integridad, cadena de confianza y cambios posteriores, el objeto signature, require_valid_signature y lo que todavía no comprueba.
Límites de uso y cuotas
Límites por plan de la API de Constaia (peticiones por segundo, análisis simultáneos y páginas por minuto), cabeceras RateLimit, 429 con Retry-After, tope de gasto mensual y cómo reintentar con backoff.