# Errores

> Cada código de error que devuelve Ikiform, con su estado HTTP, su causa y su solución.

Los envíos de formularios y la API REST comparten un mismo formato de error. Las peticiones JSON lo reciben con el estado HTTP correspondiente; los envíos de formularios del navegador reciben una página de error alojada con el mismo mensaje y estado.

## El formato de error

`Error`:

```json
{
  "ok": false,
  "error": {
    "code": "rate_limited",
    "message": "Too many requests. Please slow down."
  },
  "request_id": "req_01k7v2c9t3e8m5q4x6z0a1b2c3"
}
```

- `error.code` es estable; usa ese valor para decidir. `error.message` es inglés sencillo pensado para personas y puede cambiar.
- `error.fields` aparece en los errores de validación, indexado por id de campo, cada uno con una lista de mensajes. Los errores sobre el cuerpo completo usan la clave `_form`.
- `error.challenge_url` aparece en `challenge_required`.
- `request_id` coincide con la cabecera `X-Request-Id` que lleva cada respuesta.
- Las respuestas con límite de frecuencia llevan `Retry-After` en segundos. Ambas cabeceras se pueden leer desde el código del navegador.

## Códigos

| Estado | Código | Dónde |
| --- | --- | --- |
| 400 | [`invalid_body`](#invalid-body) | Envío |
| 400 | [`invalid_request`](#invalid-request) | API |
| 401 | [`unauthorized`](#unauthorized) | API |
| 402 | [`plan_required`](#plan-required) | Envío |
| 403 | [`forbidden`](#forbidden) | Ambos |
| 403 | [`origin_not_allowed`](#origin-not-allowed) | Envío |
| 403 | [`form_paused`](#form-paused) | Envío |
| 403 | [`captcha_failed`](#captcha-failed) | Envío |
| 403 | [`challenge_required`](#challenge-required) | Envío |
| 404 | [`not_found`](#not-found) | Ambos |
| 405 | [`method_not_allowed`](#method-not-allowed) | API |
| 409 | [`idempotency_in_progress`](#idempotency-in-progress) | Envío |
| 413 | [`payload_too_large`](#payload-too-large) | Envío |
| 415 | [`unsupported_media_type`](#unsupported-media-type) | Envío |
| 415 | [`unsupported_charset`](#unsupported-charset) | Envío |
| 422 | [`validation_failed`](#validation-failed) | Envío |
| 422 | [`files_not_supported`](#files-not-supported) | Envío |
| 422 | [`too_deep`](#too-deep) | Envío |
| 422 | [`idempotency_mismatch`](#idempotency-mismatch) | Envío |
| 429 | [`rate_limited`](#rate-limited) | Ambos |
| 429 | [`quota_exceeded`](#quota-exceeded) | Envío |
| 500 | [`server_error`](#server-error) | Ambos |

### 400 invalid_body

|   |   |
| --- | --- |
| Mensaje | `The request body could not be read.` |
| Causa | JSON o multipart mal formado, un array o una cadena JSON en lugar de un objeto, o una clave de idempotencia de más de 255 caracteres. |
| Solución | Envía un único objeto JSON, o deja que el navegador o `FormData` construyan el cuerpo. |

### 400 invalid_request

|   |   |
| --- | --- |
| Mensaje | `The request is invalid.` |
| Causa | `limit`, `status`, `form_id` o `cursor` no es válido. El mensaje indica el parámetro. |
| Solución | Usa `limit` de 1 a 100, `status` `received` o `spam`, un id `frm_` real y los cursores exactamente como se devolvieron. |

### 401 unauthorized

|   |   |
| --- | --- |
| Mensaje | `A valid API key is required.` |
| Causa | No hay cabecera `Authorization`, no es un token Bearer, o la clave es desconocida, está revocada o caducada. |
| Solución | Envía `Authorization: Bearer ik_sk_...` con una clave secreta activa. |

### 402 plan_required

|   |   |
| --- | --- |
| Mensaje | `This form isn't accepting submissions right now.` |
| Causa | El espacio de trabajo no tiene un plan activo. Esto solo puede pasar cuando se active la facturación. |
| Solución | Elige un plan en el panel, en **Settings**. |

### 403 forbidden

|   |   |
| --- | --- |
| Mensaje | `You do not have access to this resource.` |
| Causa | La clave de API está mal formada, pertenece a otro espacio de trabajo, es publicable en la API REST o le falta el permiso necesario. |
| Solución | Usa una clave secreta de este espacio de trabajo con el permiso que indica el mensaje. |

### 403 origin_not_allowed

|   |   |
| --- | --- |
| Mensaje | `This form does not accept submissions from this site.` |
| Causa | El envío vino de un sitio fuera de los dominios permitidos del formulario y el formulario rechaza las discrepancias, o se usó una clave publicable desde un dominio que no está en la clave. |
| Solución | Añade el sitio a **Allowed domains** o a los dominios de la clave. Para envíos desde servidor, usa una clave secreta. |

### 403 form_paused

|   |   |
| --- | --- |
| Mensaje | `This form is not accepting submissions right now.` |
| Causa | El formulario está en pausa en el panel. |
| Solución | Reanuda el formulario. |

### 403 captcha_failed

|   |   |
| --- | --- |
| Mensaje | `The captcha check failed. Please try again.` |
| Causa | El token de Turnstile falta, caducó, ya se usó o no pasó la verificación. |
| Solución | Obtén un token nuevo en cada envío. Con tus propias claves, comprueba que la clave de sitio corresponde al secreto. |

### 403 challenge_required

|   |   |
| --- | --- |
| Mensaje | `Complete the human check, then send the form again.` |
| Causa | El formulario usa la verificación humana de Ikiform y llegó un envío JSON sin token. |
| Solución | Llama a `challenge(formId)` del SDK y pasa el token, o usa `error.challenge_url`. Los envíos desde servidor pueden enviar una clave secreta en su lugar. |

### 404 not_found

|   |   |
| --- | --- |
| Mensaje | `Not found.` |
| Causa | El formulario no existe, se eliminó o se desactivó, o el id o la ruta son incorrectos. También se devuelve ante un `GET` a `/f/<formId>`. |
| Solución | Copia el endpoint desde el resumen del formulario y envía a él en lugar de abrirlo en una pestaña. |

### 405 method_not_allowed

|   |   |
| --- | --- |
| Mensaje | `This method is not allowed here.` |
| Causa | Un preflight del navegador en la API REST, que es solo de servidor a servidor. |
| Solución | Llama a `/api/v1` desde tu backend. |

### 409 idempotency_in_progress

|   |   |
| --- | --- |
| Mensaje | `A request with this idempotency key is still being processed.` |
| Causa | Todavía se está procesando una petición con esta clave de idempotencia. |
| Solución | Espera un momento y reintenta con la misma clave; recibirás la primera respuesta. |

### 413 payload_too_large

|   |   |
| --- | --- |
| Mensaje | `The submission is too large.` |
| Causa | El cuerpo, un nombre de campo, un valor o el número de campos supera su límite. El mensaje indica cuál. |
| Solución | Consulta [Límites](https://ikiform.com/es/docs/limits.md#submissions). Reparte el texto largo entre varios campos, o guarda los archivos en otro lugar y envía un enlace. |

### 415 unsupported_media_type

|   |   |
| --- | --- |
| Mensaje | `Send the form as urlencoded, multipart or JSON.` |
| Causa | El `Content-Type` no es urlencoded, multipart ni JSON, o falta. |
| Solución | Define `Content-Type: application/json` cuando envíes JSON. |

### 415 unsupported_charset

|   |   |
| --- | --- |
| Mensaje | `Only UTF-8 is supported.` |
| Causa | El cuerpo no es UTF-8, o declara otro charset. |
| Solución | Codifica en UTF-8. Añade `accept-charset="utf-8"` a los formularios HTML antiguos. |

### 422 validation_failed

|   |   |
| --- | --- |
| Mensaje | `Some fields are invalid.` |
| Causa | Hay campos que no pasaron la validación. `error.fields` relaciona los ids de campo con los mensajes. |
| Solución | Muestra los mensajes junto a los campos. Consulta [Campos](https://ikiform.com/es/docs/fields.md#errors). |

### 422 files_not_supported

|   |   |
| --- | --- |
| Mensaje | `File uploads are not supported yet.` |
| Causa | El envío contiene un archivo. La subida de archivos aún no existe. |
| Solución | Quita los inputs de archivo, o sube los archivos a otro lugar y envía el enlace. |

### 422 too_deep

|   |   |
| --- | --- |
| Mensaje | `The JSON body is nested too deeply.` |
| Causa | El cuerpo JSON tiene más de 3 niveles de anidamiento, contando el objeto principal. |
| Solución | Aplana los datos, por ejemplo `{ "address_city": "London" }`. |

### 422 idempotency_mismatch

|   |   |
| --- | --- |
| Mensaje | `This idempotency key was already used with a different payload.` |
| Causa | Esta clave de idempotencia ya se usó en este formulario en las últimas 24 horas con campos distintos. |
| Solución | Usa una clave nueva para datos nuevos. Mantén la misma clave solo para reintentos idénticos. |

### 429 rate_limited

|   |   |
| --- | --- |
| Mensaje | `Too many requests. Please slow down.` |
| Causa | Se superó un límite de frecuencia. `Retry-After` indica cuántos segundos esperar. |
| Solución | Espera y reintenta. Si les pasa a visitantes reales, cambia el [límite de frecuencia](https://ikiform.com/es/docs/rate-limits.md) del formulario. |

### 429 quota_exceeded

|   |   |
| --- | --- |
| Mensaje | `This form isn't accepting submissions right now.` |
| Causa | El espacio de trabajo recibió este mes el doble de su límite mensual de envíos. |
| Solución | Mejora tu plan o espera al mes siguiente. Consulta [Uso mensual](https://ikiform.com/es/docs/limits.md#monthly-usage). |

### 500 server_error

|   |   |
| --- | --- |
| Mensaje | `Something went wrong on our side.` |
| Causa | Algo falló del lado de Ikiform. |
| Solución | Reintenta con backoff y una clave de idempotencia. Si sigue pasando, indica el `request_id`. |

## Lo que no son errores

- El spam recibe el mismo `201` o la misma redirección que un envío real. Búscalo en el filtro de spam del panel.
- Un `_redirect` no permitido se ignora y el navegador llega a la página de agradecimiento alojada.
- Un `_replyto` que no es una única dirección de email válida se ignora.

## Reintentos

- Reintenta un `429` después de `Retry-After`, y los `500`, `409` o errores de red con backoff. Envía una [clave de idempotencia](https://ikiform.com/es/docs/frameworks.md#idempotency) para que un reintento nunca cree un duplicado.
- No reintentes otros errores `4xx` sin cambios. Corrige antes la petición.
