Constaia
Conceptos

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:

Cuerpo de error
{
  "error": {
    "type": "invalid_request",
    "code": "invalid_parameter",
    "message": "Tipo de documento desconocido. Consulta GET /v1/document-types.",
    "param": "options.expect",
    "request_id": "req_01J9Z8Q3K4M5N6P7Q8R9S0T1V2"
  }
}
CampoDescripción
typeCategoría del error. Hay siete, ver tabla de abajo.
codeCódigo estable y concreto. Programa tu lógica contra code, no contra message.
messageExplicación para personas (en español). Puede cambiar de redacción.
paramOpcional. El parámetro o cabecera implicado: file, options.expect, options.checks.max_age_days, Idempotency-Key…
request_idIdentificador 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

HTTPtypeSignificado
400invalid_requestPetición mal formada.
401authenticationFalta la clave de API o no es válida.
402insufficient_creditsNo quedan créditos en modo real o se alcanzaría el tope de gasto mensual.
403permissionLa clave no puede hacer esa acción.
404not_foundEl recurso o la ruta no existen.
409invalid_requestConflicto de estado (idempotencia en curso, análisis sin terminar).
413invalid_requestFichero demasiado grande.
415invalid_requestTipo de fichero o de contenido no admitido.
422invalid_requestParámetros válidos en forma pero no en contenido, o documento ilegible.
429rate_limitedDemasiadas peticiones por segundo, análisis simultáneos o páginas por minuto.
500api_errorError interno de Constaia.
501invalid_requestFuncionalidad aún no disponible.
503api_errorServicio no disponible temporalmente o modo real no disponible.

Todos los códigos

HTTPcodeQué significaQué hacer
400invalid_jsonEl cuerpo no es JSON válido.Revisa la serialización y Content-Type: application/json.
400invalid_multipartNo se pudo leer el multipart/form-data.Deja que tu cliente HTTP genere el boundary; no pongas la cabecera Content-Type a mano.
400invalid_optionsEl campo options del multipart no es JSON válido.Manda options como cadena JSON.
400missing_fileNo hay fichero: falta file, file_url o file_base64 (o un item del lote no tiene ninguno).Añade el fichero.
400empty_fileEl fichero está vacío.Comprueba que lees bien el fichero antes de enviarlo.
400invalid_base64file_base64 no es base64 válido.Codifica en base64 estándar (se tolera el prefijo data:…;base64,).
400invalid_file_urlfile_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.
400batch_too_largeEl lote tiene más de 100 documentos.Divide en lotes de hasta 100.
400empty_batchEl lote no tiene documentos.Añade al menos uno.
400invalid_urlLa URL de un endpoint de webhook no es https.Usa https.
400invalid_idempotency_keyIdempotency-Key supera 255 caracteres.Acorta la clave.
401missing_api_keyFalta Authorization: Bearer <clave>.Añade la cabecera.
401invalid_api_keyClave con formato incorrecto, inexistente o revocada.Comprueba la variable de entorno y la clave en el panel.
402insufficient_creditsNo 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.
402monthly_cap_reachedEl 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.
402email_not_verifiedIntentas 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).
403forbiddenNo 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.)
404resource_missingEl recurso no existe, es de otra cuenta, se borró o se creó con keep_results: false.Comprueba el id.
404route_not_foundLa ruta no existe.Revisa método y URL (/v1/…).
409idempotency_in_progressLa petición original con esa Idempotency-Key sigue en curso.Reintenta pasados unos segundos. Ver idempotencia.
409analysis_not_completedPediste la exportación de un análisis que no ha terminado.Espera a completed (webhook o GET /v1/analyses/{id}).
413file_too_largeEl fichero supera 20 MB.Comprime o reduce la resolución.
415unsupported_file_typeEl contenido no es JPEG, PNG, WEBP, HEIC ni PDF (se detecta por los bytes, no por la extensión).Convierte el fichero.
415unsupported_content_typeLa petición no es multipart/form-data ni application/json.Usa uno de los dos.
422invalid_parameterUn 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.
422idempotency_key_reusedEsa Idempotency-Key ya se usó con otra petición distinta.Usa una clave nueva por operación.
422too_many_pagesEl PDF tiene más de 30 páginas (síncrono) o de 200 (async o lotes).Usa async: true o divide el PDF.
422unreadable_imageNo se pudo leer la imagen.Pide otra foto.
422unreadable_pdfNo se pudo leer el PDF (dañado o protegido).Pide otro fichero.
422file_url_unreachableNo 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.
422processing_unavailableEl perfil de processing pedido no está disponible.Usa el otro perfil o no envíes processing. Ver residencia de datos.
429rate_limitedSuperaste las peticiones por segundo de tu clave.Espera lo que indica Retry-After y reintenta. Ver límites.
429concurrency_limitTu 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.
429pages_rate_limitedSuperaste las páginas por minuto de tu plan (solo modo real).Espera lo que indica Retry-After.
500internal_errorError inesperado de Constaia.Reintenta con la misma Idempotency-Key. Si persiste, escribe a soporte con el request_id.
501not_implementedLa funcionalidad todavía no existe (hoy, POST /v1/verification-links).No reintentes.
503live_mode_unavailableEl 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.
503billing_unavailableEl 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).

Respuesta
HTTP/1.1 422 Unprocessable Entity
Content-Type: application/json
X-Request-Id: req_01J9Z8Q3K4M5N6P7Q8R9S0T1V2

Clases 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.

CasoJavaScript (@constaia/sdk)PHP (Constaia\Exception\…)Python (constaia)
400, 409, 413, 415, 422InvalidRequestErrorInvalidRequestExceptionInvalidRequestError
401AuthenticationErrorAuthenticationExceptionAuthenticationError
402InsufficientCreditsErrorInsufficientCreditsExceptionInsufficientCreditsError
403PermissionErrorPermissionExceptionPermissionDeniedError
404NotFoundErrorNotFoundExceptionNotFoundError
429RateLimitError (.retryAfter en segundos)RateLimitException (getRetryAfter())RateLimitError (.retry_after)
501InvalidRequestErrorApiExceptionInvalidRequestError
500, 503 y otros 5xxAPIErrorApiExceptionAPIError
Red, DNS, TLSAPIConnectionErrorConnectionExceptionAPIConnectionError
Tiempo agotadoAPITimeoutError (extiende APIConnectionError)ConnectionExceptionAPITimeoutError (extiende APIConnectionError)
Firma de webhookWebhookVerificationErrorSignatureVerificationExceptionWebhookVerificationError

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

ReintentableNo reintentable (corrige la petición)
429 rate_limited (respeta Retry-After)400, 401, 403, 404
409 idempotency_in_progress402 insufficient_credits (hasta que recargues)
500, 502, 503, 504413, 415, 422
408 y errores de red o tiempo agotado409 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

src/analyze-with-errors.ts
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

En esta página