# Errores, límites y buenas prácticas

## Formato de los errores

Todo error vuelve así:

```json
{
  "error": "validation_failed",
  "message": "Algún campo es incorrecto. `details` dice cuál y por qué.",
  "details": { "name": ["Required"] },
  "request_id": "01a3c0de-7a11-7b22-8c33-9d44e55f6677"
}
```

- `error` es el código estable: su sistema decide qué hacer a partir de él.
- `message` explica, en el idioma del encabezado `Accept-Language` (portugués, inglés o español; inglés por omisión).
- `request_id` identifica la llamada: cítelo al hablar con soporte. Puede enviar el suyo en `X-Request-Id`.

| HTTP | Código | Qué hacer |
|---|---|---|
| 401 | `unauthorized` | Falta la clave. Envíe Authorization: Bearer <clave>. |
| 401 | `api_key_invalid` | Clave inválida o revocada. Revísela o cree otra en Configuración › API. |
| 401 | `api_key_expired` | La clave venció. Cree otra en Configuración › API. |
| 403 | `api_key_address_not_allowed` | Esta clave no acepta llamadas desde esta dirección IP. Agregue la dirección a la clave. |
| 403 | `insufficient_scope` | La clave no tiene el alcance de esta llamada. `details.required` indica cuáles sirven. |
| 403 | `not_available_to_api_keys` | Esta llamada no forma parte de la API pública. |
| 403 | `not_available_to_test_keys` | Esta llamada no tiene modo de prueba: use una clave de producción. |
| 403 | `permission_denied` | La clave no puede hacer esto. |
| 403 | `integration_template_only` | Texto y archivos piden el alcance messages:send_text; sin él, solo plantillas aprobadas. |
| 404 | `not_found` | No encontrado. |
| 404 | `campaign_not_found` | Campaña no encontrada en esta empresa. |
| 404 | `contact_not_found` | Contacto no encontrado en esta empresa. |
| 422 | `validation_failed` | Algún campo es incorrecto. `details` dice cuál y por qué. |
| 409 | `campaign_not_editable` | La campaña ya no es borrador; solo un borrador se puede editar o enviar. |
| 409 | `template_not_approved` | Meta aún no aprobó la plantilla. Espere la aprobación o elija otra. |
| 409 | `campaign_audience_empty` | Nadie del público puede recibir la campaña. Vea los motivos en la vista previa. |
| 409 | `contact_exists` | Ya existe un contacto con este teléfono. `details.contactId` dice cuál. |
| 409 | `window_closed` | La ventana de 24 horas está cerrada: solo una plantilla aprobada puede iniciar la conversación. |
| 409 | `idempotency_key_reused` | Esta Idempotency-Key ya se usó con otra solicitud. Use una clave nueva por solicitud. |
| 409 | `idempotency_in_progress` | La primera solicitud con esta Idempotency-Key aún está en curso. Reintente en un momento. |
| 429 | `rate_limited` | Límite de 600 llamadas por minuto. Espere los segundos de Retry-After. |
| 500 | `internal_error` | Error de nuestro lado. Ya fuimos avisados; reintente y cite el request_id si persiste. |

## Límite de llamadas

Cada clave puede hacer 600 llamadas por minuto. Toda respuesta dice dónde está:

```http
RateLimit-Limit: 600
RateLimit-Remaining: 587
RateLimit-Reset: 41
```

Pasado el límite, la respuesta es `429 rate_limited` con `Retry-After` (segundos). Espere ese tiempo y reintente. El envío de una campaña no cuenta: quien envía es DoneTalk, al ritmo que Meta permite para el número.

## Reintentar sin duplicar

Envíe `Idempotency-Key` (hasta 200 letras, números, `_ . : -`) en creaciones y envíos. Si la red falla y envía la misma llamada otra vez con la misma clave dentro de 24 horas, recibe la primera respuesta, con `Idempotent-Replayed: true`, y nada se hace dos veces. La misma clave con otra solicitud devuelve `409 idempotency_key_reused`.

```http
Idempotency-Key: pedido-88213-campana
```

## Paginación

- **Campañas:** `limit` y `cursor`; la respuesta trae `nextCursor` (null en la última página).
- **Contactos:** `limit` y `before` (el `lastActivityAt` del último de la página).
- **Destinatarios de una campaña:** `limit` y `offset`, o `format=csv` para todo.

## Versiones

La v1 solo gana cosas nuevas: más campos y llamadas, nunca un cambio en lo que existe. Ignore los campos que no conoce. Si algún día algo debe cambiar, nace la v2 y la v1 sigue funcionando por 12 meses, con aviso.
