---
seo_title: Códigos de error de la API: 400, 401, 402, 403, 413 | EnConvert
meta_desc: Códigos de estado HTTP que devuelve la API de EnConvert: 401 clave API inválida, 402 límite mensual de ops, 413 archivo demasiado grande, con mensajes y soluciones.
keywords: api 402 pago requerido límite de ops alcanzado, error 401 unauthorized clave api inválida, error 413 payload too large al subir archivo, códigos de error api enconvert, diferencia entre error 402 y 403 api, error 422 campo desconocido validación api, error 429 demasiadas solicitudes retry-after, formato de respuesta de error api conversión de archivos, token jwt expirado error 401 api
---

# Códigos de error de la API de EnConvert

Esta referencia enumera todos los códigos de estado HTTP y mensajes de error que devuelve la API de EnConvert, desde `200 OK` para conversiones síncronas y `202 Accepted` para trabajos asíncronos y por lotes, hasta las respuestas de error documentadas a continuación. Cada sección de error enumera las cadenas de mensaje exactas, la condición que activa cada una, y cómo corregir la solicitud. Los cuerpos de error no tienen todos la misma forma: existen seis, y la sección [Formato de respuesta de error](#error-response-format) muestra cada una.

Un trabajo que falla *después* de haber sido aceptado no es un error HTTP. El `202` se mantiene y el fallo aparece en la carga de estado del trabajo cuando lo consultas, tal como se describe en [Trabajos síncronos y asíncronos](/es/docs/concepts/sync-and-async.md).

---

## Códigos de estado HTTP

| Código | Estado | Descripción |
|------|--------|-------------|
| `200` | OK | Conversión completada correctamente (modo síncrono). |
| `202` | Accepted | El trabajo por lotes o asíncrono fue aceptado para procesamiento en segundo plano. |
| `400` | Bad Request | Parámetros inválidos, campos obligatorios faltantes, cuerpo de solicitud malformado o contenido de archivo inválido. |
| `401` | Unauthorized | Clave de API o token JWT faltante, inválido o expirado. |
| `402` | Payment Required | Se agotó la cuota mensual de ops, no hay periodo de facturación activo, se alcanzó el tope de watchers, se alcanzó el límite de almacenamiento, o un endpoint V2 está desactivado en tu plan. |
| `403` | Forbidden | Restricción de tipo de clave, de dominio o de lista de endpoints permitidos, una restricción de función V1 (async, webhooks, salida ZIP, autenticación básica, lotes), o acceso a un recurso de otro proyecto. |
| `404` | Not Found | El recurso solicitado (trabajo, lote, operación, archivo, watcher o widget) no existe, o la ruta no corresponde a ningún endpoint. |
| `405` | Method Not Allowed | La ruta existe, pero no para el método HTTP que usaste. |
| `409` | Conflict | Un `job_id` proporcionado por el cliente ya está en uso, o se pidió un reintento de webhook sobre un trabajo de ingest que no ha finalizado. |
| `410` | Gone | Un artefacto V2 o un archivo comprimido de lote superó la ventana de retención de archivos de tu plan y ya no está en el almacenamiento. |
| `413` | Payload Too Large | El archivo subido supera el límite de tamaño de tu plan de suscripción. |
| `415` | Unsupported Media Type | La URL de destino devolvió contenido que este conversor no puede renderizar (p. ej., JSON a `url-to-pdf`). |
| `422` | Unprocessable Entity | El cuerpo de la solicitud no pasó la validación de esquema (incluidos campos desconocidos en endpoints V2), o falló una precondición de renderizado, como un `wait_for_selector` que nunca apareció. |
| `429` | Too Many Requests | Se superó un límite de tasa de solicitudes de ventana corta. Este no es el código de cuota; agotar la asignación mensual responde `402`. |
| `500` | Internal Server Error | Error inesperado durante la conversión (falló nuestro motor). |
| `502` | Bad Gateway | No se pudo alcanzar el sitio de destino, falló un proveedor externo (upstream), o el destino sirvió un desafío anti-bot sin contenido de página (`/v2/perceive`, salvo que se defina `allow_degraded`). |
| `503` | Service Unavailable | Un conversor no está disponible, el grupo de renderizado o la compuerta de admisión de conversiones está a plena capacidad, o una dependencia externa está caída. |
| `504` | Gateway Timeout | El sitio de destino tardó demasiado en responder o en terminar de cargar, o la solicitud superó el presupuesto de 300 segundos del gateway. |

---

## Formato de respuesta de error {: #error-response-format }

Existen seis formas de cuerpo. Cuál recibes depende de dónde ocurrió el fallo, no solo del código de estado, así que comprueba el tipo de `detail` antes de leerlo.

**1. `detail` de tipo cadena.** El caso común, y la única forma que ven la mayoría de las integraciones.

```json
{
    "detail": "Authentication required"
}
```

**2. `detail` de tipo objeto.** El `413` por tamaño de archivo del plan. El objeto estructurado es el valor de `detail`, así que lee `body.detail.max_size`, no `body.max_size`. Consulta [413 Payload Too Large](#413-payload-too-large).

```json
{
    "detail": {
        "error": "File too large",
        "file_size": 10485760,
        "max_size": 5242880,
        "tier": "free",
        "key_type": "private"
    }
}
```

**3. `detail` de tipo array más `errors`.** La validación de esquema (`422`) devuelve ambos: `detail` es la salida sin procesar del validador y `errors` es un array paralelo de cadenas legibles por personas. Consulta [422 Unprocessable Entity](#422-unprocessable-entity).

**4. Envoltorio de conversión tipificado.** `{"error", "code", "detail"}`, con un `code` legible por máquina. Solo lo emiten los tres endpoints V1 de conversión de URL. Consulta [Errores de conversión del navegador](#browser-conversion-errors-415-422-502-504).

**5. Excepción no controlada.** Un `500` que no proviene de un conversor no tiene `detail` en absoluto:

```json
{
    "error": "Internal server error",
    "event_id": "a1b2c3d4"
}
```

**6. Tiempo de espera de solicitud del gateway.** El propio presupuesto de 300 segundos del gateway produce un `504` sin `detail` y sin `code`:

```json
{
    "error": "Request timeout"
}
```

---

## 400 Bad Request

Se devuelve cuando la solicitud contiene parámetros inválidos, campos faltantes o datos malformados.

### Validación de entrada

| Mensaje | Condición |
|---------|-----------|
| `'url' must be provided` | Falta el campo `url` o está vacío en los endpoints basados en URL. |
| `Invalid file format '{ext}' for {endpoint}. Allowed: {list}` | La extensión del archivo subido no coincide con los formatos aceptados por el endpoint. |
| `File content does not match the '{endpoint}' input type.` | La extensión se aceptó, pero los bytes mágicos del archivo corresponden a otro formato. |
| `Invalid pdf_options: {error}` | JSON malformado en el campo de formulario `pdf_options`. |

### Validación de lote y modo

| Mensaje | Condición |
|---------|-----------|
| `Public keys only support a single URL input` | Una clave pública o del panel intentó enviar varias URLs. |
| `output_format=True requires multiple URLs` | Se solicitó agrupación en ZIP con una sola URL. |
| `direct_download not supported for multiple URLs` | `direct_download=true` con un array de URLs. |
| `direct_download only works in sync mode` | `direct_download=true` combinado con `async_mode=true`. |

### Validación de autenticación, cookies y encabezados

| Mensaje | Condición |
|---------|-----------|
| `'auth' must be an object with 'username' and 'password'` | El parámetro `auth` tiene una estructura incorrecta. |
| `'cookies' must be an array of cookie objects` | `cookies` no es un array. |
| `'cookies' array must not exceed 50 entries` | Se proporcionaron más de 50 cookies. |
| `Cookie at index {i} must be an object` | La entrada de cookie no es un diccionario. |
| `Cookie at index {i} must have 'name' and 'value'` | A la cookie le faltan campos obligatorios. |
| `Cookie at index {i} must have 'domain' or 'url'` | A la cookie le faltan tanto `domain` como `url`. |
| `'headers' must be an object of header name/value pairs` | `headers` no es un diccionario. |
| `'headers' must not exceed 20 entries` | Se proporcionaron más de 20 encabezados personalizados. |
| `Header '{name}' cannot be overridden` | Intento de configurar un encabezado bloqueado. El conjunto bloqueado es `host`, `content-length`, `transfer-encoding`, `connection`, `upgrade`, `te`, `trailer`. |
| `Header '{name}' value must be a string` | El valor del encabezado no es una cadena de texto. |
| `Cannot use both 'auth' and an 'Authorization' header. Use 'auth' for HTTP Basic Auth or 'headers' for Bearer/custom auth, not both.` | Se proporcionaron tanto un objeto `auth` como un encabezado personalizado `Authorization`. |

### Seguridad de URL (SSRF)

Todos los endpoints basados en URL examinan la `url` de destino antes de obtenerla. Estos mensajes se devuelven como `400` cuando la URL no es una dirección pública `http(s)`.

| Mensaje | Condición |
|---------|-----------|
| `Only http:// and https:// URLs are supported.` | La URL usa un esquema distinto de `http` o `https`. |
| `URLs with embedded credentials are not allowed. Use the 'auth' field for HTTP Basic Auth.` | La URL incrusta un nombre de usuario/contraseña (`https://user:pass@host/`). |
| `URL has no hostname.` | No se pudo analizar la URL para obtener un host. |
| `This hostname is not allowed.` | El host es `localhost` o un nombre de host de metadatos de la nube. |
| `URLs resolving to private or internal addresses are not allowed.` | La URL es, o se resuelve en, una IP privada, de bucle local (loopback), local de enlace (link-local), reservada o de cualquier otro modo no pública. |
| `Non-standard IP address notation is not allowed.` | El host usa una notación de IP octal, hexadecimal o de entero empaquetado que podría resolverse de forma ambigua. |
| `Could not resolve hostname '{hostname}'.` | Falló la resolución DNS del host. |
| `This URL is blocked by the site's threat policy.` | El host de destino está en la lista de denegación de la política de amenazas, que se comprueba junto con el filtro SSRF. |

### Validación de opciones de renderizado

| Mensaje | Condición |
|---------|-----------|
| `'wait_for_selector' must be a string` | `wait_for_selector` no era una cadena. |
| `'wait_for_selector' is too long (max 1000 chars)` | El selector supera los 1000 caracteres. |
| `'wait_for_selector_timeout' must be a positive integer (ms)` | El tiempo de espera falta, es cero, negativo o no es un entero. |
| `'wait_for_selector_timeout' must not exceed 60000 ms` | El tiempo de espera supera el techo de 60 segundos. |
| `'block_ads' must be a boolean` / `'block_media' must be a boolean` | La bandera de bloqueo no era un booleano. |

### Errores de sitemap y rastreo

| Mensaje | Condición |
|---------|-----------|
| `No URLs found in sitemap: {url}` | El sitemap se analizó pero no contiene URLs. |
| `Timeout fetching sitemap: {url}` | La obtención del sitemap superó el tiempo de espera de 30 segundos. |
| `Could not fetch sitemap: {url} returned {status}` | La URL del sitemap devolvió un estado HTTP distinto de 200. |
| `Invalid XML in sitemap: {url}` | No se pudo analizar el XML del sitemap. |
| `Unrecognized sitemap format at {url}: root element is <{tag}>` | El elemento raíz del sitemap no es `<urlset>` ni `<sitemapindex>`. |
| `No pages discovered on {base_url}` | El rastreo completo finalizó pero no encontró ninguna página. |

### Errores de contenido de conversión

| Mensaje | Condición |
|---------|-----------|
| `Invalid JSON: {error}` | El archivo JSON contiene una sintaxis JSON inválida. |
| `Invalid YAML: {error}` | El archivo YAML contiene una sintaxis YAML inválida. |
| `Invalid TOML: {error}` | El archivo TOML contiene una sintaxis TOML inválida. |
| `Invalid HTML encoding (expected UTF-8)` | El archivo HTML no está codificado en UTF-8. |
| `Invalid Markdown encoding (expected UTF-8)` | El archivo Markdown no está codificado en UTF-8. |
| `JSON must be an array of objects for CSV conversion` | La entrada de json-to-csv no es un array. |
| `JSON array is empty` | La entrada de json-to-csv es un array vacío. |
| `CSV file is empty or has no valid rows` | El archivo CSV no tiene filas de datos. |
| `XML structure cannot be converted to CSV` | El XML no es tabular (xml-to-csv). |
| `Turnstile verification failed` | El desafío antibots de Cloudflare Turnstile falló. |
| `Turnstile token required` | Solicitud del widget sin un token de Turnstile. |

---

## 401 Unauthorized

Se devuelve cuando falta la autenticación o es inválida.

| Mensaje | Condición |
|---------|-----------|
| `Authentication required` | No se proporcionó clave de API ni token JWT en la solicitud. |
| `Invalid API Key format` | La clave de API es demasiado corta o no empieza con `sk_` o `pk_`. |
| `Invalid API Key` | El hash de la clave de API no se encontró en la base de datos. |
| `API Key revoked` | La clave de API fue desactivada desde el panel. |
| `Token has expired` | El token de acceso JWT expiró (vida útil de 1 hora). |
| `Invalid token` | El JWT está malformado, alterado o es inválido de alguna otra forma. |
| `Refresh token has expired` | El token de actualización expiró (vida útil de 7 días). |
| `Invalid refresh token` | El token de actualización está malformado o es inválido. |
| `Invalid token type` | El token se decodificó correctamente, pero no es del tipo esperado (refresh). |
| `No refresh token` | Se llamó al endpoint de actualización del widget sin la cookie refresh_token. |
| `Refresh token not found` | El token de actualización presentado no está almacenado en ninguna sesión. |
| `User not found or invalid` | El token se decodificó, pero su sujeto ya no corresponde a una cuenta utilizable. |
| `Project not found` | No se pudo analizar el id de proyecto de la clave o del token al comprobar la cuota de ops. |

---

## 402 Payment Required

Se devuelve cuando se supera un límite de uso. La división entre `402` y `403` no es simétrica y suele confundir. **Toda condición de cuota responde `402`**, igual que un endpoint V2 desactivado en tu plan. **Las restricciones de funciones V1 responden `403`** (async, webhooks, salida ZIP, autenticación básica, lotes). La limitación de tasa es un mecanismo aparte que responde [`429`](#429-too-many-requests), nunca `402`.

| Mensaje | Condición |
|---------|-----------|
| `Monthly operations limit reached ({used}/{limit}). Upgrade your plan to continue.` | El contador mensual unificado de ops alcanzó la asignación del plan. Plan Founding: 500 ops. Cada endpoint consume este único contador. En cualquier plan de pago con excedente activado, las solicitudes continúan a $0.02/op en lugar de fallar. |
| `This request needs {units} operations but only {remaining} of your {limit} monthly operations remain. Upgrade your plan to continue.` | La solicitud por lotes superaría la cuota mensual de ops restante. Todo el lote se rechaza de antemano. |
| `No active billing period found for this project. Contact support to restore your subscription.` | El proyecto no tiene periodo de uso y no fue posible aprovisionar uno a partir de su suscripción. La compuerta falla en cerrado en lugar de conceder una operación gratuita. |
| `Storage limit reached. Delete files or upgrade your storage plan to continue.` | El uso de almacenamiento del proyecto alcanzó la asignación de almacenamiento del plan. |
| `Active watcher limit reached ({active_count}/{limit}). Delete an existing watcher or upgrade your plan to add more.` | El proyecto ya tiene el número máximo de watchers activos de su plan. Los watchers no consumen ops; este es un tope sobre cuántos existen a la vez. |
| `{Feature} is not available on your current plan. Upgrade to a V2-inclusive plan to access this endpoint.` | Un endpoint V2 está deshabilitado para el plan. |

Quedarse sin créditos mensuales de IA no produce un `402`. La extracción de esquema recurre al resultado heurístico y CSS, y la solicitud sigue teniendo éxito. Las asignaciones, los precios y qué cuenta como una operación están en [Límites de tasa y cuotas](/es/docs/reference/rate-limits.md).

---

## 403 Forbidden

Se devuelve cuando se deniega el acceso debido a restricciones de tipo de clave, dominio, función V1 del plan o endpoint. Las restricciones de endpoints V2 son la excepción: responden [`402`](#402-payment-required), no `403`.

### Restricciones de clave de API y token

| Mensaje | Condición |
|---------|-----------|
| `Private API keys cannot be used from browsers` | Se usó una clave privada (`sk_...`) en una solicitud con un encabezado `Origin` de navegador. Usa en su lugar una clave pública con JWT. |
| `Domain {origin} not authorized` | El origen de la solicitud no coincide con ningún dominio de la lista de dominios permitidos de la clave de API. |
| `Public API keys can only be used to generate JWT tokens or fetch widget branding. Please exchange your public key for a JWT token at /v1/auth/token, then use the token for API calls.` | Se usó una clave pública en una ruta distinta de `/auth/token` o `/auth/branding`. Intercámbiala primero por un JWT. |
| `Endpoint '{path}' not allowed for this API key` | La lista `allowed_endpoints` de la clave de API no incluye la ruta solicitada. |
| `Endpoint '{path}' not allowed for this token` | La lista `allowed_endpoints` del token JWT no incluye la ruta solicitada. |
| `Token issued for different origin` | El origen de la solicitud no coincide con el origen registrado en el JWT (evita el robo de tokens). |
| `Parent origin does not match token` | El encabezado `X-Parent-Origin` no coincide con lo validado al emitir el token. |

### Restricciones de funciones del plan

| Mensaje | Condición |
|---------|-----------|
| `Async processing is not available on your current plan. Please upgrade to access this feature.` | `async_mode=true` en un plan sin acceso asíncrono. |
| `Webhook callbacks is not available on your current plan. Please upgrade to access this feature.` | Se proporcionó `callback_url` en un plan sin acceso a webhooks. |
| `ZIP output bundling is not available on your current plan. Please upgrade to access this feature.` | `output_format=true` en un plan sin acceso a salida ZIP. |
| `Basic authentication, cookies & custom headers is not available on your current plan. Please upgrade to access this feature.` | Se usó `auth`, `cookies` o `headers` en un plan sin acceso a autenticación básica. |
| `Batch processing is not available on your current plan. Please upgrade to access this feature.` | Se enviaron varias URLs en un plan con batch_limit de 0. |
| `Batch size {N} exceeds your plan's limit of {M} URLs per batch.` | El número de URLs supera el límite de tamaño de lote del plan. |
| `Website crawling is not available on your current plan. Please upgrade to access this feature.` | Se usó el endpoint de captura de sitios web en un plan con crawl_mode "none" (plan Founding). |
| `Full website crawling requires a Studio plan or higher. Your plan supports sitemap-based crawling only.` | Se solicitó `crawl_mode=full` en un plan Indie que solo admite rastreo basado en sitemap. |

### Restricciones de widget

| Mensaje | Condición |
|---------|-----------|
| `Widget API key has been revoked` | La clave de API interna vinculada al widget fue desactivada. |
| `Domain {origin} is not authorized for this widget` | El dominio de inserción del widget no está en la lista de dominios permitidos del widget. |
| `Refresh token does not match widget` | El ID de proyecto del token de actualización no coincide con el proyecto del widget. |
| `Batch status requires a private API key` | Una clave pública o del panel intentó acceder a `GET /v1/convert/batch/{batch_id}`. |
| `Access denied` | Intento de acceder a un recurso (estado de trabajo, archivo) que pertenece a otro proyecto. |

Otros dos mensajes `403` no tratan ni de claves ni de planes: `Account suspended`, que se devuelve en todas las solicitudes cuando la cuenta asociada a la clave o al token está suspendida, y `robots.txt disallows fetching this URL (request sent respect_robots=true).`, que devuelve perceive cuando pediste cumplimiento de robots y el destino no permite esa ruta.

---

## 404 Not Found

| Mensaje | Condición |
|---------|-----------|
| `Job not found` | El ID del trabajo de conversión no se encontró en la base de datos (consulta de estado). |
| `Batch not found` | El ID de lote no tiene filas de actividad coincidentes para este proyecto. |
| `File not found` | El archivo solicitado no existe en el almacenamiento (endpoint de descarga). |
| `Widget not found` | El ID de widget no se encontró o el widget fue desactivado. |
| `Operation not found`, `Ingest job not found`, `Watcher not found` | Un id de recurso V2 que no existe, o que pertenece a otro proyecto. La existencia nunca se filtra entre proyectos. |
| `Not Found` | La ruta no corresponde a ningún endpoint de la API. Revisa la ruta y el prefijo de versión. |

---

## 409 Conflict

| Mensaje | Condición |
|---------|-----------|
| `job_id already in use` | Un `job_id` proporcionado por el cliente ya está reclamado por otro proyecto. Elige un id distinto, o deja que la API genere uno. |
| `A completion webhook is only delivered for completed jobs.` | Se solicitó un reintento de webhook para un trabajo de ingest que no ha alcanzado `completed`. |

---

## 410 Gone

El artefacto existió, pero superó la ventana de retención de archivos de tu plan y ya no está en el almacenamiento. La retención es por plan; consulta [Límites de tasa y cuotas](/es/docs/reference/rate-limits.md).

| Mensaje | Condición |
|---------|-----------|
| `The artifact is no longer in storage (it may have passed your plan's file-retention window). Re-run the perceive request to regenerate it.` | Descarga de artefacto en `GET /v2/perceive/{operation_id}`. |
| `The batch archive is no longer in storage (it may have passed your plan's file-retention window).` | Descarga de ZIP en `GET /v2/perceive/batch/{job_id}`. |

Trata el `410` como definitivo para ese objeto. Volver a ejecutar la solicitud produce un artefacto nuevo; reintentar la descarga, no.

---

## 413 Payload Too Large

Se devuelve cuando el archivo subido supera el tamaño máximo de archivo del plan.

<div class="alert alert-warning">
<strong>Cuerpo anidado:</strong> el objeto estructurado es el valor de <code>detail</code>, no un objeto de nivel superior. Lee <code>body.detail.max_size</code>.
</div>

```json
{
    "detail": {
        "error": "File too large",
        "file_size": 10485760,
        "max_size": 5242880,
        "tier": "free",
        "key_type": "private"
    }
}
```

| Campo | Descripción |
|-------|-------------|
| `error` | Siempre `"File too large"`. |
| `file_size` | El tamaño del archivo subido en bytes. |
| `max_size` | El tamaño máximo de archivo permitido para tu plan, en bytes. |
| `tier` | El slug de tu plan de suscripción (por ejemplo, `"free"`, `"starter"`, `"pro"`), con `"free"` como valor de reserva cuando no se resuelve ningún plan. Los slugs son identificadores estables de la API; los nombres comerciales son Founding (`free`), Indie (`starter`), Studio (`pro`) y Production (`business`). |
| `key_type` | El tipo de clave de API usada: `"private"`, `"public"` o `"unknown"`. |

El límite se comprueba contra el número exacto de bytes de la parte subida, antes de que empiece cualquier trabajo de conversión. Un archivo cuyo tamaño sea exactamente `max_size` se acepta; solo se rechaza uno mayor. El encabezado `Content-Length` es un mecanismo de reserva para puntos de llamada antiguos que no entregan su objeto de subida a la comprobación.

`POST /v2/ingest/files` no usa esta forma. Responde `413` con un `detail` de cadena simple: `File '{filename}' exceeds the {max_size}-byte limit.`

Los techos por plan están en [Límites de tasa y cuotas](/es/docs/reference/rate-limits.md), y las rutas de subida a las que esto se aplica están en [Ingesta de archivos](/es/docs/guides/file-ingestion.md).

---

## Errores de conversión del navegador (415 / 422 / 502 / 504) {: #browser-conversion-errors-415-422-502-504 }

Las conversiones de URL distinguen un fallo en el **sitio de destino o en la entrada** (un `4xx`, `502` o `504` sobre el que puedes actuar) de un fallo en **nuestro motor** (un `500`). Los tres endpoints V1 de conversión de URL (`url-to-pdf`, `url-to-screenshot`, `url-to-markdown`) devuelven estos fallos tipificados con un `code` legible por máquina junto a `detail`:

```json
{
    "error": "Gateway Timeout",
    "code": "upstream_timeout",
    "detail": "The target site took too long to respond while rendering PDF for https://example.com."
}
```

| Código | Campo `code` | Condición |
|------|--------------|-----------|
| `415` | `unsupported_content_type` | El destino devolvió contenido que el conversor no puede renderizar, por ejemplo `application/json` enviado a `url-to-pdf` o `url-to-screenshot`. Usa `url-to-markdown` para JSON. |
| `422` | `selector_not_found` | Un `wait_for_selector` proporcionado por el llamante nunca apareció dentro de `wait_for_selector_timeout`. |
| `502` | `upstream_unreachable` | No se pudo alcanzar el sitio de destino (fallo de DNS o de conexión). |
| `502` | `empty_render` | La navegación finalizó pero la página no produjo contenido capturable. |
| `504` | `upstream_timeout` | El sitio de destino tardó demasiado en responder o en terminar de cargar. |

Esos cinco son todo el vocabulario. Ninguna otra familia de endpoints emite un `code`, V2 incluido: un fallo de V2 vuelve como una cadena `detail` simple. La clase base del envoltorio define un sexto slug, `conversion_error`, pero nada lo lanza, así que nunca te llega. Ramifica sobre los cinco anteriores y trata cualquier otro valor como desconocido.

<div class="alert alert-info">
Un `500` ahora significa que falló nuestro motor, así que reintentar una solicitud idéntica difícilmente ayudará. Un `502`/`504` significa que el <em>destino</em> se comportó mal: reintenta, o comprueba la URL.
</div>

Un `504` también puede llegar en dos formas sin tipificar: `{"error": "Request timeout"}` cuando la solicitud supera el presupuesto de 300 segundos del gateway, y un `detail` de cadena simple con el mensaje de tiempo de espera cuando una conversión de documento (LibreOffice) se agota. Ninguna de las dos lleva un `code`.

---

## 422 Unprocessable Entity

Los fallos de validación de esquema devuelven dos arrays paralelos. `detail` es la salida sin procesar del validador, que es lo que reasignas a los campos del formulario. `errors` es una cadena legible por personas por cada problema, que es lo que muestras al usuario.

```json
{
    "detail": [
        {
            "loc": ["body", "max_pages"],
            "msg": "Input should be a valid integer, unable to parse string as an integer",
            "type": "int_parsing"
        }
    ],
    "errors": [
        "body.max_pages: Input should be a valid integer, unable to parse string as an integer (you sent 'ten')"
    ]
}
```

Vale la pena tratar tres valores de `type` por su nombre:

| `type` | Significado |
|--------|-------------|
| `extra_forbidden` | Campo desconocido. Los esquemas de solicitud V2 rechazan las claves desconocidas en lugar de ignorarlas, así que un parámetro mal escrito es un `422` que nombra el campo, y no una opción descartada en silencio. |
| `missing` | No se envió un campo obligatorio. |
| `json_invalid` | El cuerpo de la solicitud no era JSON válido. |

<div class="alert alert-warning">
<strong>Una excepción:</strong> la validación por elemento en <code>POST /v2/perceive/batch</code> devuelve un <code>422</code> cuyo <code>detail</code> es una lista simple de objetos <code>{"loc", "msg"}</code>, sin clave <code>type</code> y sin array <code>errors</code> de nivel superior. Los analizadores que asumen que <code>errors</code> siempre está presente fallarán ahí.
</div>

Un `422` con el código `selector_not_found` es otra cosa: una precondición de renderizado que falló, tratada en [Errores de conversión del navegador](#browser-conversion-errors-415-422-502-504).

---

## 429 Too Many Requests

La limitación de tasa es un control de equidad de ventana corta e independiente de la cuota mensual de ops. Agotar la cuota responde [`402`](#402-payment-required); solo el limitador de tasa responde `429`.

| Mensaje | Condición |
|---------|-----------|
| `Rate limit exceeded. Please slow down and retry shortly.` | Se superó una ventana de tasa de solicitudes del proyecto. Los cubos son por proyecto y están separados por tipo de clave, así que el tráfico público y el privado no comparten uno. |

Un `429` incluye cuatro encabezados:

| Encabezado | Significado |
|--------|-------------|
| `RateLimit-Limit` | Solicitudes permitidas en la ventana que se superó. |
| `RateLimit-Remaining` | Solicitudes restantes en esa ventana, `0` en un rechazo. |
| `RateLimit-Reset` | Segundos hasta que la ventana se reinicia. |
| `Retry-After` | El mismo valor que `RateLimit-Reset`. Espera ese tiempo antes de reintentar. |

Estos encabezados aparecen solo en el `429`. Las respuestas correctas no llevan encabezados de límite de tasa ni de ops restantes, así que no puedes leer tu presupuesto restante en una respuesta; revisa el uso en el panel. Consulta [Límites de tasa y cuotas](/es/docs/reference/rate-limits.md).

---

## 500 Internal Server Error

| Mensaje | Condición |
|---------|-----------|
| `Conversion failed: {error}` | Un error inesperado durante una conversión de **archivo subido**. Las conversiones de URL no usan este mensaje: aparecen como el envoltorio tipificado anterior, o como el cuerpo genérico siguiente. |
| `Perception failed. Reference operation_id '{id}' when contacting support.` | Un fallo inesperado dentro de una ejecución de `/v2/perceive`. Los demás endpoints V2 tienen equivalentes, como `Distillation failed. Reference operation_id ...` y `Could not start ingest job. Reference job_id ...`. Cita el id cuando contactes con soporte. |

Todo lo que falla fuera de un conversor nunca te llega como texto. Vuelve sin ningún `detail`:

```json
{
    "error": "Internal server error",
    "event_id": "a1b2c3d4"
}
```

Un caso sorprende a mucha gente: un cuerpo JSON malformado enviado a un endpoint V1 de URL (`url-to-pdf`, `url-to-screenshot`, `url-to-markdown`, `website-to-pdf`, `website-to-screenshot`) devuelve este `500` en lugar de un `422`, porque esos endpoints leen el cuerpo sin procesar. El mismo cuerpo malformado en un endpoint V2 devuelve un `422` con `type: json_invalid`.

Si encuentras errores 500 persistentes, es probable que el problema esté en el archivo o la URL de entrada. Prueba con una entrada diferente para aislar el problema.

---

## 503 Service Unavailable

| Mensaje | Condición |
|---------|-----------|
| `Converter not available: {endpoint}` | El conversor solicitado no está registrado o no se está ejecutando. |
| `Converter not available` | El conversor basado en URL para el endpoint solicitado no está disponible. |
| `The conversion service is at capacity. Please retry shortly.` | El grupo de renderizado del navegador no tiene ninguna ranura libre. Se envía con `Retry-After: 30`. |
| `Server is at capacity. Please retry shortly.` | La compuerta de admisión de conversiones de CPU está llena: hay demasiadas conversiones de archivos, o demasiados bytes, en curso. Se envía con `Retry-After: 10`. |
| `Search is temporarily unavailable. Please try again later.` | El proveedor de búsqueda externo (upstream) detrás de lookup no está disponible o está mal configurado. |
| `Turnstile verification unavailable` | El servicio de verificación de Cloudflare Turnstile no está disponible. |

Existen dos compuertas de capacidad distintas y piden esperas diferentes, así que lee `Retry-After` en lugar de suponer una. Por lo demás, estos errores son transitorios: reintenta después de una breve espera.

---

## Errores de los endpoints V2

Los [endpoints de inteligencia web V2](/es/docs/concepts/v1-and-v2.md) reutilizan los códigos de estado anteriores, con algunas condiciones específicas de V2 que vale la pena mencionar.

### Cuota y plan (402 / 403)

Las operaciones V2 se miden contra la misma cuota mensual unificada de ops que las conversiones V1: una op por unidad de trabajo. Cualquier plan de pago con excedente activado ($0.02/op) permite superar la asignación; si no, el tope es estricto.

| Código | Condición |
|------|-----------|
| `402` | Se agotó la cuota mensual de ops. Todos los endpoints V2 ([perceive](/es/docs/endpoints/perceive.md), [discover](/es/docs/coming-soon/discover.md), [lookup](/es/docs/coming-soon/lookup.md), [distill](/es/docs/coming-soon/distill.md), [ingest](/es/docs/endpoints/ingest.md)) facturan este único contador junto con las conversiones V1. |
| `402` | Se alcanzó el límite de watchers activos (`max_watchers`) ([watch](/es/docs/coming-soon/watch.md)). Los watchers son un tope separado y nunca consumen ops. |
| `402` | El endpoint está desactivado para el plan. Las restricciones V2 responden `402`, a diferencia de las restricciones de funciones V1, que responden `403`. |
| `403` | El endpoint no está en la lista `allowed_endpoints` de la clave de API. |

Dos comportamientos de V2 deliberadamente no son errores. Agotar el saldo de créditos de IA no hace fallar la solicitud: la extracción de esquema recurre al resultado heurístico y CSS. Y una ejecución de [distill](/es/docs/coming-soon/distill.md) con varias URLs que cruza el límite de ops a mitad de camino tampoco devuelve `402`: se detiene ahí, devuelve las URLs que terminó, y añade una advertencia que indica cuántas se omitieron.

### Validación (422)

Todos los esquemas de solicitud V2 rechazan las claves desconocidas, así que un parámetro mal escrito es un `422` que nombra el campo. La forma del cuerpo se describe en [422 Unprocessable Entity](#422-unprocessable-entity).

| Endpoint | Condición |
|----------|-----------|
| [perceive](/es/docs/endpoints/perceive.md) | Se envió `proxy_url`, `geolocation` o `action_chain`; reservado para una versión posterior. |
| [distill](/es/docs/coming-soon/distill.md) | No se proporcionó ni `schema` ni `prompt` (enviar ambos está bien, gana `schema`); no se proporcionó ninguno o se proporcionaron ambos de `urls` y `discover_from`; un campo CSS inválido, un tipo de campo no admitido, o una expresión regular que arriesga un backtracking catastrófico. |
| [watch](/es/docs/coming-soon/watch.md) | `frequency_minutes` por debajo del mínimo horario de 60 minutos; un cuerpo `PATCH` vacío. |
| [ingest](/es/docs/endpoints/ingest.md) | El `mode` no coincide con la fuente (modo `urls` sin `urls`, o `sitemap`/`crawl` sin una `url` semilla). |

### Proveedor de búsqueda (502 / 503)

El endpoint [lookup](/es/docs/coming-soon/lookup.md) depende de un proveedor de búsqueda externo (upstream). El texto de error sin procesar del proveedor nunca llega al cliente.

| Código | Mensaje | Condición |
|------|---------|-----------|
| `502` | `The search provider returned an error. Please try again.` | El proveedor devolvió una respuesta de error o un fallo de transporte no reintentable. |
| `503` | `Search is temporarily unavailable. Please try again later.` | El proveedor está mal configurado (falta la clave) o no está disponible temporalmente. Reintenta más tarde. |

### No encontrado (404)

`GET` y `DELETE` sobre un `operation_id`, `job_id` o `watcher_id` de V2 que no existe, o que pertenece a otro proyecto, devuelven `404`. La existencia nunca se filtra entre proyectos.

### Aceptado (202)

[Ingest](/es/docs/endpoints/ingest.md) siempre es asíncrono: `POST /v2/ingest` responde `202` con un `job_id` que consultas mediante polling. Los lotes de perceive con más de 10 URLs responden `202` con estado `queued`. Un lote de 10 o menos normalmente responde en línea, pero si supera la ventana en línea degrada a `202` con estado `processing` y una advertencia, así que gestiona el `202` con cualquier tamaño de lote.

Un `202` también significa que los fallos posteriores no son errores HTTP. Consulta el trabajo y lee su carga de estado, tal como se describe en [Trabajos síncronos y asíncronos](/es/docs/concepts/sync-and-async.md).

---

## Solución de problemas

### Problemas de autenticación

- **¿Recibes un 401?** Verifica que tu clave de API sea válida y esté activa en el panel. Si usas JWT, asegúrate de que el token no haya expirado (vida útil de 1 hora).
- **¿Recibes un 403 sobre uso desde el navegador?** Estás usando una clave privada (`sk_...`) desde código del lado del cliente. Cambia a una clave pública con JWT para solicitudes desde el navegador.
- **¿Recibes un 403 sobre dominio?** Agrega tu dominio a la lista de dominios permitidos de la clave de API en el panel.

### Problemas de conversión

- **¿Recibes un 400 sobre formato de archivo?** Asegúrate de que la extensión del archivo subido coincida con el endpoint (por ejemplo, `.json` para json-to-xml, `.docx` para doc-to-pdf).
- **¿Recibes un 413?** Tu archivo supera el límite de tamaño del plan. Lee `detail.max_size` de la respuesta, luego revisa el tamaño máximo de archivo de tu plan o actualízalo.
- **¿Recibes un 402?** Alcanzaste tu cuota mensual de ops, el tope de watchers o el límite de almacenamiento, o el proyecto no tiene periodo de facturación activo. Revisa el uso en el panel y consulta [Límites de tasa y cuotas](/es/docs/reference/rate-limits.md).

### Problemas de acceso a funciones

- **¿Recibes un 403 sobre funciones del plan?** La función V1 que intentas usar (async, lotes, webhooks, salida ZIP, autenticación básica) requiere un nivel de plan superior. Consulta la [tabla de habilitación de funciones](/es/docs/reference/rate-limits.md#403-una-funcion-de-lotes-o-de-v1-que-tu-plan-no-tiene).
- **¿Recibes un 402 en un endpoint V2 que no tiene que ver con la cuota?** Las restricciones de endpoints V2 responden `402`, no `403`. El mensaje dice `... is not available on your current plan. Upgrade to a V2-inclusive plan to access this endpoint.`

## Preguntas frecuentes

### ¿Por qué la API devuelve 402 Payment Required en una conversión de archivo?

Un `402` significa que se agotó un límite de uso: tu cuota mensual unificada de ops (500 ops en el plan Founding), el tope de watchers activos, o la asignación de almacenamiento de tu proyecto. También cubre dos casos ajenos a la cuota: un proyecto sin periodo de facturación activo, y un endpoint V2 desactivado en tu plan. Las solicitudes por lotes que superarían la cuota mensual de ops restante se rechazan de antemano con un `402` para todo el lote. Las conversiones V1 y las operaciones V2 consumen la misma cuota; cualquier plan de pago con excedente activado ($0.02/op) permite superarla. La limitación de tasa es un mecanismo distinto y responde `429`.

### ¿Cómo soluciono un error 401 Unauthorized de la API de conversión?

Verifica que la clave de API esté presente, comience con `sk_` o `pk_`, y siga activa en el panel; las claves revocadas devuelven `API Key revoked`. Si te autenticas con un JWT, ten en cuenta que los tokens de acceso expiran después de 1 hora (`Token has expired`) y los tokens de actualización después de 7 días.

### ¿Por qué recibo 413 Payload Too Large al subir un archivo?

El archivo subido supera el tamaño máximo de archivo de tu plan, medido a partir del número exacto de bytes de la parte subida antes de que empiece cualquier trabajo de conversión. Un archivo justo en el límite se acepta. El cuerpo del `413` anida un objeto estructurado bajo `detail`, con `file_size`, `max_size` (ambos en bytes), `tier` y `key_type`, así que léelo como `detail.max_size` y no como un campo de nivel superior.

### ¿Puedo usar una clave de API privada desde JavaScript en el navegador?

No: una clave privada (`sk_...`) usada en una solicitud con un encabezado `Origin` de navegador devuelve `403 Private API keys cannot be used from browsers`. Intercambia una clave pública por un JWT en `/v1/auth/token` y usa ese token para las llamadas a la API desde el navegador.

### ¿Es permanente un error 503 Service Unavailable de la API?

No, los errores `503` como `Converter not available: {endpoint}` o `Turnstile verification unavailable` suelen ser transitorios. Reintenta la solicitud después de una breve espera.
