# Límites de velocidad (por número, por cuenta, por clave)

`POST /v1/messages`

Tres límites, y cada uno cuenta por separado. **Por número** (`connection`), sólo `POST /v1/messages`: el ritmo que Meta le da a ese número — **Convivencia 20 por segundo, Cloud API 80 por segundo**, con una ráfaga de un segundo. Un número a fondo **no frena a los demás** de tu cuenta. **Por cuenta** (`account`), sólo `POST /v1/messages`, por minuto según el plan: prueba 600, Starter 1.200, Studio 2.400, Agencia 4.800, Escala 9.600; plan a medida sin tope; nunca menos de 600 por clave activa. **Por clave** (`key`): todo lo demás de `/v1`, **600 pedidos por minuto**. Un envío no gasta los 600 de la clave.

## Ejemplos

### cURL

```bash
curl -i -X POST https://api.waiaconnect.com/v1/messages \
  -H "Authorization: Bearer wc_live_YOUR_API_KEY" \
  -H "Idempotency-Key: 7c0e…-same-on-every-retry" \
  -H "Content-Type: application/json" \
  -d '{"connectionId":"conn_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX","to":"5493511234567","type":"text","text":{"body":"Hi"}}'
# 202 → X-RateLimit-Scope: connection  X-RateLimit-Limit: 20  X-RateLimit-Remaining: 19
# 429 → Retry-After: 1
# { "error": { "code": "RATE_LIMITED", "limit": "connection", "connectionId": "conn_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
#              "retryAfterSeconds": 1, "details": { "limitPerSecond": 20, "burst": 20 } } }
```

### Node.js

```javascript
// One limiter PER NUMBER (Coexistence 20/s, Cloud API 80/s); retry 429 with the SAME key.
const pausedUntil = new Map(); // connectionId → ms (pause only the number that hit its limit)
async function send(msg, idempotencyKey) {
  for (;;) {
    const wait = (pausedUntil.get(msg.connectionId) || 0) - Date.now();
    if (wait > 0) await new Promise(r => setTimeout(r, wait));
    const res = await fetch("https://api.waiaconnect.com/v1/messages", {
      method: "POST",
      headers: { "Authorization": "Bearer wc_live_YOUR_API_KEY", "Idempotency-Key": idempotencyKey, "Content-Type": "application/json" },
      body: JSON.stringify(msg)
    });
    if (res.status !== 429) return res.json();
    const { error } = await res.json();
    if (error.code !== "RATE_LIMITED") return { error }; // quota: do not retry
    const until = Date.now() + Number(res.headers.get("retry-after") || error.retryAfterSeconds) * 1000;
    if (error.limit === "connection") pausedUntil.set(msg.connectionId, until);
    else await new Promise(r => setTimeout(r, until - Date.now())); // account or key: everything waits
  }
}
```

### PHP

```php
<?php
// Retry a 429 with the SAME Idempotency-Key, after Retry-After.
function send(array $msg, string $idemKey): array {
  for ($i = 0; $i < 5; $i++) {
    $ch = curl_init("https://api.waiaconnect.com/v1/messages");
    curl_setopt_array($ch, [
      CURLOPT_RETURNTRANSFER => true, CURLOPT_POST => true, CURLOPT_HEADER => true,
      CURLOPT_HTTPHEADER => ["Authorization: Bearer wc_live_YOUR_API_KEY", "Idempotency-Key: $idemKey", "Content-Type: application/json"],
      CURLOPT_POSTFIELDS => json_encode($msg),
    ]);
    $raw = curl_exec($ch);
    $status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    $body = json_decode(substr($raw, curl_getinfo($ch, CURLINFO_HEADER_SIZE)), true);
    if ($status !== 429 || ($body["error"]["code"] ?? "") !== "RATE_LIMITED") return $body;
    sleep(max(1, (int)$body["error"]["retryAfterSeconds"])); // = Retry-After
  }
  return $body;
}
```

### Python

```python
import time, requests
# Retry a 429 with the SAME Idempotency-Key, after Retry-After.
def send(msg, idem_key):
    for _ in range(5):
        res = requests.post("https://api.waiaconnect.com/v1/messages", json=msg, headers={
            "Authorization": "Bearer wc_live_YOUR_API_KEY", "Idempotency-Key": idem_key})
        if res.status_code != 429 or res.json()["error"]["code"] != "RATE_LIMITED":
            return res.json()
        err = res.json()["error"]  # err["limit"]: connection | account | key
        time.sleep(int(res.headers.get("Retry-After", err["retryAfterSeconds"])))
    return res.json()
```

## Notas

- **El 429:** el `code` es el de siempre, `RATE_LIMITED`. Suma `error.limit` (`connection`, `account` o `key`: cuál te frenó), `error.connectionId` (sólo si fue el del número) y `error.retryAfterSeconds`, igual al header `Retry-After`. Para `account` y `key`, la cifra está en `details.limitPerMinute`; para `connection`, en `details.limitPerSecond` y `details.burst`.
- **Toda respuesta de `/v1`** trae `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset` (segundos hasta que se llena de nuevo) y `X-RateLimit-Scope`, del límite que más aprieta en ese momento.
- **Cómo armar tu cola:** un limitador por número (hasta 20/s en Convivencia, 80/s en Cloud API), todos debajo de la cifra por minuto de tu cuenta. Ante un 429, esperá `Retry-After` y reintentá con la **misma** `Idempotency-Key`: un 429 no crea nada, el reintento es seguro. Con `limit: "connection"` pausá sólo ese número; con `account` o `key`, todo.
- **Aceptado no es entregado:** después de un 202, la cola le pasa cada mensaje a Meta a su ritmo y no pierde ninguno. `GET /v1/usage/by-connection` cuenta mensajes, no pedidos: un 429 no aparece ahí.
- ¿Necesitás otra cifra de cuenta para un caso especial? Escribinos: se ajusta cuenta por cuenta.
