# Errores y límites

> Códigos de respuesta de la API de Ghosty Studio, qué significa cada uno y cómo reintentar; tamaños y topes.

URL: https://www.ghosty.studio/docs/api/errores-y-limites

## Códigos

| Código | Cuerpo | Qué hacer |
|---|---|---|
| `202` | `{ turnId, key, estado, enCola, reemplazoAnterior, repetido, inyectado }` | El turno fue **aceptado**, no terminado. Escucha los [eventos](/docs/api/eventos). `enCola` > 0 = hay turnos delante. `repetido` = ya habías mandado ese `turnId`, no se duplicó. `inyectado` = tu mensaje **entró al turno que ya corría**: no hay turno nuevo, sigue escuchando el mismo `turnId`. |
| `400` | `{ error }` | Cuerpo inválido: falta `content`, `id`/`optionId`, modelo vacío… No reintentes sin cambiarlo. |
| `401` | texto | Sin token o caducado. Refresca y reintenta **una** vez. |
| `402` | `{ error: "quota_exhausted" \| "trial_expired" \| "own_key_required", message }` | Bolsa agotada, trial vencido o modelo que exige llave propia. El `message` es para mostrar. |
| `403` | texto | Falta un scope. No reintentes: pide el scope en el siguiente login. |
| `404` | texto o `{ error: "not_found" }` | El agente no es tuyo, o el recurso no existe. Un agente ajeno responde 404 y no 403, a propósito. |
| `405` | `{ error: "method_not_allowed" }` | Método incorrecto. |
| `409` | `{ error: "agente_no_acp", motor }` | El agente no habla el protocolo de conversaciones de esta API (motor sin ACP). |
| `413` | `{ error: "audio demasiado grande" }` | Audio o archivo por encima del tope. |
| `503` | `{ error: "reiniciando" }` + `Retry-After: 10` | Deploy en curso. Reintenta con el **mismo `turnId`** tras el `Retry-After`: no duplica. |

## Idempotencia

`POST …/messages` acepta un `turnId` tuyo (UUID). Si repites la petición con el mismo `turnId` —por un timeout, un 503, una red mala— el servidor contesta `repetido: true` y no crea un segundo turno. Úsalo siempre.

Un reintento correcto respeta `Retry-After` y reutiliza el `turnId`:

```typescript
async function sendTurn(url: string, body: { content: string; turnId: string }, token: string) {
  for (let intento = 0; intento < 3; intento++) {
    const res = await fetch(url, {
      method: "POST",
      headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json" },
      body: JSON.stringify(body), // mismo turnId en cada intento: el servidor no duplica
    });
    if (res.status !== 503) return res;
    const wait = Number(res.headers.get("Retry-After") ?? 10) * 1000;
    await new Promise((r) => setTimeout(r, wait));
  }
  throw new Error("la plataforma siguió reiniciando tras 3 intentos");
}
```

## Concurrencia

Un agente corre **un turno a la vez por conversación**. Un mensaje nuevo en una conversación con turno en curso **entra a ese turno** (`inyectado: true`): el agente lo lee en cuanto termina la herramienta que está corriendo y contesta en la misma respuesta, sin tirar el trabajo hecho. Es la forma de corregirlo a mitad de trabajo. Si el turno ya iba cerrando y no alcanzó a entrar, se **detiene el anterior y se reemplaza** (`reemplazoAnterior: true`). Sólo quien pidió el turno puede reemplazarlo. Conversaciones distintas del mismo agente corren en paralelo hasta el tope del plan; las que esperan máquina cuentan en `enCola`.

## Tamaños

| Qué | Tope |
|---|---|
| Adjuntos por mensaje | 8 |
| Audio para `POST /api/v2/me/stt` | según plan; excederlo da `413` |
| URL firmada de un archivo | 6 horas; vuelve a pedirla con `GET /api/v2/me/files/:id` |
| Código de autorización OAuth2 | 60 s |
| Access token | 1 h |
| Refresh token | 90 días, rota en cada uso |
| Permiso sin contestar | 10 min → denegado |

## Ritmo

No hay un límite de peticiones por segundo publicado en beta; el tope real es la bolsa de tokens del plan y las máquinas encendidas a la vez. Si ves `429`, respeta `Retry-After`.
