Constaia
Concepts

Errors

Constaia API error format, every error code by HTTP status, the JavaScript and PHP SDK error classes and which errors are safe to retry.

When a request fails, the API responds with a non-2xx HTTP status and a JSON body like this:

Error body
{
  "error": {
    "type": "invalid_request",
    "code": "invalid_parameter",
    "message": "Tipo de documento desconocido. Consulta GET /v1/document-types.",
    "param": "options.expect",
    "request_id": "req_01J9Z8Q3K4M5N6P7Q8R9S0T1V2"
  }
}
FieldDescription
typeError category. There are seven, see the table below.
codeStable, specific code. Write your logic against code, not message.
messageExplanation for people (in Spanish). Wording may change.
paramOptional. The parameter or header involved: file, options.expect, options.checks.max_age_days, Idempotency-Key…
request_idRequest identifier. Same value as the X-Request-Id header.

A failed analysis is not an HTTP error

If the file is accepted but the analysis cannot be completed, the response is an analysis with status: "failed" and error: { code, message } (for example processing_failed), not an HTTP error. Those analyses are not charged. For async analyses you get the analysis.failed webhook.

Types and HTTP statuses

HTTPtypeMeaning
400invalid_requestMalformed request.
401authenticationAPI key missing or invalid.
402insufficient_creditsNo credits left in live mode, or the monthly spend cap would be exceeded.
403permissionThe key cannot perform that action.
404not_foundThe resource or route does not exist.
409invalid_requestState conflict (idempotency in progress, analysis not finished).
413invalid_requestFile too large.
415invalid_requestUnsupported file or content type.
422invalid_requestParameters well-formed but invalid, or unreadable document.
429rate_limitedToo many requests per second, concurrent analyses or pages per minute.
500api_errorConstaia internal error.
501invalid_requestFeature not available yet.
503api_errorService temporarily unavailable or live mode unavailable.

Every code

HTTPcodeMeaningWhat to do
400invalid_jsonThe body is not valid JSON.Check serialisation and Content-Type: application/json.
400invalid_multipartThe multipart/form-data body could not be read.Let your HTTP client generate the boundary; don't set Content-Type by hand.
400invalid_optionsThe multipart options field is not valid JSON.Send options as a JSON string.
400missing_fileNo file: file, file_url or file_base64 is missing (or a batch item has none).Add the file.
400empty_fileThe file is empty.Make sure you read the file correctly before sending it.
400invalid_base64file_base64 is not valid base64.Use standard base64 (a data:…;base64, prefix is tolerated).
400invalid_file_urlfile_url is not a valid URL, is not https, or points to a disallowed address (private IP).Use a public https URL.
400batch_too_largeThe batch has more than 100 documents.Split into batches of up to 100.
400empty_batchThe batch has no documents.Add at least one.
400invalid_urlA webhook endpoint URL is not https.Use https.
400invalid_idempotency_keyIdempotency-Key is longer than 255 characters.Shorten the key.
401missing_api_keyAuthorization: Bearer <key> is missing.Add the header.
401invalid_api_keyKey malformed, unknown or revoked.Check the environment variable and the key in the dashboard.
402insufficient_creditsNot enough credits for this analysis (or, for a batch, for every document).Buy a pack or wait for the monthly renewal. See credits.
402monthly_cap_reachedThe analysis would exceed the account's monthly spend cap (monthly_credit_cap). Not charged.Raise the cap or wait until the 1st (UTC). Don't retry. See monthly spend cap.
402email_not_verifiedYou are trying to spend free credits in live mode and nobody in the account has verified their email.Verify your email from the link we sent you (or resend it from the dashboard).
403forbiddenYou are not allowed to perform this action.Check the key and the account. (In the dashboard, creating live keys without a verified email gives email_not_verified.)
404resource_missingThe resource doesn't exist, belongs to another account, was deleted or was created with keep_results: false.Check the id.
404route_not_foundThe route doesn't exist.Check method and URL (/v1/…).
409idempotency_in_progressThe original request with that Idempotency-Key is still running.Retry after a few seconds. See idempotency.
409analysis_not_completedYou asked for the export of an unfinished analysis.Wait for completed (webhook or GET /v1/analyses/{id}).
413file_too_largeThe file exceeds 20 MB.Compress or reduce the resolution.
415unsupported_file_typeThe content is not JPEG, PNG, WEBP, HEIC or PDF (detected from the bytes, not the extension).Convert the file.
415unsupported_content_typeThe request is neither multipart/form-data nor application/json.Use one of them.
422invalid_parameterA parameter is invalid: unknown type in expect, unknown option, out-of-range value. param tells you which.Fix the parameter. See checks.
422idempotency_key_reusedThat Idempotency-Key was already used with a different request.Use a new key per operation.
422too_many_pagesThe PDF has more than 30 pages (sync) or 200 (async or batches).Use async: true or split the PDF.
422unreadable_imageThe image could not be read.Ask for another photo.
422unreadable_pdfThe PDF could not be read (damaged or protected).Ask for another file.
422file_url_unreachablefile_url could not be downloaded (network error, non-2xx response or 15 s timeout). Over 20 MB you get 413 file_too_large.Make sure the URL is public and fast, or upload the file.
422processing_unavailableThe requested processing profile is not available.Use the other profile or don't send processing. See data residency.
429rate_limitedYou exceeded your key's requests per second.Wait for Retry-After and retry. See rate limits.
429concurrency_limitYour account already has its plan's maximum of concurrent synchronous analyses running (live mode only).Wait for them to finish, or use async: true or batches.
429pages_rate_limitedYou exceeded your plan's pages per minute (live mode only).Wait for Retry-After.
500internal_errorUnexpected Constaia error.Retry with the same Idempotency-Key. If it persists, contact support with the request_id.
501not_implementedThe feature doesn't exist yet (today, POST /v1/verification-links).Don't retry.
503live_mode_unavailableThe environment has no real AI providers configured; live keys never get simulated results.Use a ck_test_ key meanwhile. Don't retry it in a loop.
503billing_unavailablePack checkout is unavailable (dashboard only).Try again later.

X-Request-Id

Every response, successful or not, carries the X-Request-Id: req_… header. On errors it also comes in error.request_id. Log it: it is the first thing we will ask for if you contact support (hola@constaia.com).

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

SDK error classes

Every JavaScript SDK error extends ConstaiaError and exposes status, type, code, param, requestId, headers and raw. PHP SDK errors extend Constaia\Exception\ConstaiaException and expose getHttpStatus(), getType(), getErrorCode(), getParam() and getRequestId(). Python SDK errors extend constaia.ConstaiaError and expose .message, .status, .type, .code, .param and .request_id.

CaseJavaScript (@constaia/sdk)PHP (Constaia\Exception\…)Python (constaia)
400, 409, 413, 415, 422InvalidRequestErrorInvalidRequestExceptionInvalidRequestError
401AuthenticationErrorAuthenticationExceptionAuthenticationError
402InsufficientCreditsErrorInsufficientCreditsExceptionInsufficientCreditsError
403PermissionErrorPermissionExceptionPermissionDeniedError
404NotFoundErrorNotFoundExceptionNotFoundError
429RateLimitError (.retryAfter in seconds)RateLimitException (getRetryAfter())RateLimitError (.retry_after)
501InvalidRequestErrorApiExceptionInvalidRequestError
500, 503 and other 5xxAPIErrorApiExceptionAPIError
Network, DNS, TLSAPIConnectionErrorConnectionExceptionAPIConnectionError
TimeoutAPITimeoutError (extends APIConnectionError)ConnectionExceptionAPITimeoutError (extends APIConnectionError)
Webhook signatureWebhookVerificationErrorSignatureVerificationExceptionWebhookVerificationError

In a browser, the Constaia constructor throws an error with code secret_key_in_browser if you pass it a ck_ key: the key must stay on your server. See security.

What to retry

RetryableNot retryable (fix the request)
429 rate_limited (honour Retry-After)400, 401, 403, 404
409 idempotency_in_progress402 insufficient_credits (until you top up)
500, 502, 503, 504413, 415, 422
408 and network errors or timeouts409 analysis_not_completed (wait for it to finish, don't retry in a loop)
501 not_implemented

The JavaScript, PHP and Python SDKs already retry the left-hand cases for you (2 retries by default, maxRetries in JavaScript, max_retries in PHP and Python), honour Retry-After and reuse the same Idempotency-Key on every retry, so a retry never charges twice. Set it to 0 to disable retries and handle them yourself. If you make raw HTTP calls, send your own Idempotency-Key on every POST before retrying. See idempotency.

live_mode_unavailable

It is a 5xx and the SDKs retry it, but it does not fix itself within seconds. If you get it, work with a ck_test_ key and see status and SLA.

Handling examples

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: the problem is in the request or the file
    console.error(`Invalid request (${err.code}) at ${err.param ?? "-"}: ${err.message}`);
  } else if (err instanceof InsufficientCreditsError) {
    console.error("Out of credits: buy a pack in the dashboard.");
  } else if (err instanceof RateLimitError) {
    console.error(`Rate limited after retrying; try again in ${err.retryAfter ?? 1} s.`);
  } else if (err instanceof APIConnectionError) {
    console.error("Could not reach Constaia:", err.message);
  } else if (err instanceof ConstaiaError) {
    console.error(`Error ${err.status} ${err.code} (request ${err.requestId})`);
  } else {
    throw err;
  }
}

Next steps

On this page