Cuotas y límites
El motor limita tres cosas: cuánto tiene cada app (instancias, multimedia guardada), qué tan rápido sale lo que pide (el ritmo de envío de cada número) y cuánto le pregunta a WhatsApp (las consultas del día). Además, cada recurso tiene sus tamaños máximos, que valida el contrato.
Los valores de esta página son los de fábrica. El operador los cambia en Ajustes del panel, así que en tu motor pueden ser otros. Cuando un límite se puede fijar por app o por instancia, la tabla lo dice.
Por app
Sección titulada «Por app»| Límite | De fábrica | Clave en Ajustes | Al pasarlo |
|---|---|---|---|
| Instancias | 3 | limites.cupo_instancias (0 a 1000) |
POST /v1/instances responde 429. El operador puede darle a la app un cupo propio. |
| Consultas de identidad por día | 500 | limites.consultas_identidad_dia |
429 cuota_agotada con Retry-After hasta las 00:00 UTC. |
| Consultas comerciales por día | 500 | comercio.consultas_dia |
429 cuota_agotada con Retry-After hasta las 00:00 UTC. |
| Multimedia guardada | 10 240 MB | multimedia.cuota_app_mb |
429 cuota_excedida al subir. |
| Streams abiertos a la vez | 5 por proceso del motor | streams.conexiones_por_app |
429 al abrir otro. |
Las dos cuotas diarias son de la app: las gastan todas sus instancias juntas y vuelven a cero a las 00:00 UTC.
- La de identidad la gasta cada número de
POST /v1/instances/{id}/contacts/check(«¿está en WhatsApp?»), cada perfil de la info de contactos, cada foto de un contacto y cada perfil comercial. Una consulta con un número inválido o sin cuota se rechaza entera, antes de salir y sin gastar nada. Lo usado hoy, con su desglose por tipo, está enGET /v1/instances/{id}/contacts/quota(scopecontacts:lookup). - La comercial la gastan el catálogo y las colecciones. Cuenta cada intento que pudo llegar a WhatsApp, también los que fallan. Cada respuesta comercial trae su cuota y su desglose.
Por instancia
Sección titulada «Por instancia»| Límite | De fábrica | Dónde se cambia | Al pasarlo |
|---|---|---|---|
| Mensajes por segundo | 1 | limites.mensajes_por_segundo (1 a 20), o config.mensajes_por_segundo de la instancia |
El envío se guarda y espera su turno: 429 con el comando y Retry-After. |
| Ráfaga | 2 envíos seguidos | limites.rafaga (1 a 20), o config.rafaga de la instancia |
Ídem. |
| Envíos en cola | 200 | limites.envios_pendientes |
429 sin guardar nada. |
| Multimedia guardada | 2048 MB | multimedia.cuota_instancia_mb |
429 cuota_excedida al subir. |
| Resultados comerciales guardados | 100 (y 10 MB) | comercio.vistas_instancia, comercio.mb_instancia |
429 vistas_agotadas, con Retry-After: 60. |
| Espera entre consultas comerciales | Una a la vez, 2 s entre dos | comercio.segundos_entre_consultas |
La consulta espera. |
El ritmo de una instancia se cambia con PATCH /v1/instances/{id} (scope instances:manage),
en config.mensajes_por_segundo y config.rafaga; un valor fuera de 1 a 20 es un 422. Sin
valor propio, rige el de Ajustes.
Ritmo y cola no son lo mismo. El ritmo no rechaza: el mensaje queda guardado y sale cuando
le toca. La cola sí rechaza: con 200 envíos pendientes en la instancia (aceptado, en_cola o
ejecutando), uno nuevo recibe 429 y no se guarda.
Si WhatsApp limita las consultas comerciales de un número, la instancia se enfría 60 s antes
de la próxima, y el enfriamiento se duplica si se repite, hasta 15 min
(comercio.segundos_enfriamiento, comercio.minutos_enfriamiento_maximo).
Del motor
Sección titulada «Del motor»Estos no se ven en tus respuestas, pero explican esperas:
| Límite | De fábrica | Clave en Ajustes |
|---|---|---|
| Operaciones simultáneas con WhatsApp, en todo el motor | 4 | limites.operaciones_remotas |
| De esas, las que puede ocupar un mismo número | 2 | operacion.operaciones_por_instancia |
| Conversiones de multimedia a la vez | 2 | multimedia.conversiones_simultaneas |
Un comando de control (un grupo, el perfil, un canal) espera una plaza libre antes de salir: con muchos a la vez, tarda más, pero no falla por eso.
Por recurso
Sección titulada «Por recurso»Archivos y cuerpos
Sección titulada «Archivos y cuerpos»| Qué | Máximo | Al pasarlo |
|---|---|---|
| Un archivo (subir, importar por URL o descargar) | 100 MB (multimedia.tamano_maximo_mb, hasta 2000) |
413 demasiado_grande. |
| Un medio pedido en base64 | 5120 KB (multimedia.base64_maximo_kb) |
422 demasiado_grande_para_base64: descárgalo en binario. |
| Un cuerpo JSON | 1 MiB | 413. |
| Una foto de perfil o de grupo | JPEG de hasta 2 MB, de 192 a 2048 píxeles por lado | 422 foto_invalida. |
Mensajes
Sección titulada «Mensajes»| Campo | Máximo |
|---|---|
texto |
4096 caracteres. |
menciones |
50. |
contactos |
10 por mensaje. |
pregunta de una encuesta |
255 caracteres. |
opciones de una encuesta |
De 2 a 12, distintas, de hasta 100 caracteres. |
emoji de una reacción |
32 caracteres. |
correlation_id |
200 caracteres. |
Idempotency-Key |
De 1 a 200 caracteres. |
segundos del temporizador |
0, 86400, 604800 o 7776000. |
| Destinatarios de un status | 256 (estados.max_destinatarios). |
Los límites de los interactivos (botones, listas, carruseles) están en Interactivos. WhatsApp tiene sus propias ventanas: editar durante 20 minutos y borrar para todos durante 2 días y 12 horas.
Instancias, suscripciones y replays
Sección titulada «Instancias, suscripciones y replays»| Campo | Máximo |
|---|---|
nombre de una instancia |
120 caracteres. |
owner_ref |
200 caracteres. |
url de una suscripción |
2000 caracteres. |
tipos de una suscripción |
50. |
instancias de una suscripción |
200. |
| Un replay | Un rango de hasta 10 000 seq de una instancia, o hasta 500 event_ids. |
Paginación y consultas
Sección titulada «Paginación y consultas»| Ruta | Parámetro | Rango | Por omisión |
|---|---|---|---|
GET /v1/instances/{id}/events |
limit |
1 a 500 | 100 |
GET /v1/instances/{id}/messages, /chats, /contacts, /groups, /newsletters, /status |
limit |
1 a 200 | 50 |
GET /v1/instances/{id}/labels |
limit |
1 a 200 | 100 |
GET /v1/instances/{id}/history |
limit |
1 a 100 | 20 |
GET /v1/instances/{id}/interactive-responses |
limite |
1 a 200 | 50 |
GET /v1/instances/{id}/newsletters/{canal_ref}/messages |
cantidad |
1 a 100 | 20 |
GET /v1/instances/{id}/business/catalog |
limit |
1 a 50 | 10 |
GET /v1/instances/{id}/business/collections |
collection_limit · item_limit · limit |
1 a 20 · 1 a 50 · 1 a 20 | 10 cada uno |
GET /v1/deliveries |
por_pagina |
1 a 200 | 50 |
GET /v1/instances |
por_pagina |
10, 20, 50 o 100 | 20 |
- En
GET /v1/instances, unpor_paginaque no es 10, 20, 50 ni 100 no da error: se usa 20. - En las colecciones,
collection_limit × item_limitno puede pasar de 200 (422). - Un filtro
typesacepta hasta 20 tipos separados por comas (422 si son más). - Los números de una consulta de identidad son hasta 20 por pedido (
limites.numeros_por_consulta, hasta 50); más es un 422. - Las listas con cursor (
next_cursor) se recorren pasando el cursor tal cual. Un cursor del catálogo vence a los 15 min (comercio.minutos_cursor).
Retenciones
Sección titulada «Retenciones»| Qué | Cuánto | Clave en Ajustes |
|---|---|---|
| Entregas de webhooks ya entregadas | 30 días (lo pendiente y lo fallido no vence) | retencion.dias_entregados |
| Reanudación de un stream | 30 días | streams.dias_reanudacion |
| Multimedia | 30 días (lo que espera un envío no se borra) | multimedia.dias_retencion |
| Permiso temporal de descarga | 15 min | multimedia.minutos_permiso |
| Un status recibido | 24 h como máximo | estados.horas_retencion |
Los eventos del ledger no se borran: cuando vence el contenido, se redacta y el evento queda.
La respuesta 429
Sección titulada «La respuesta 429»Un 429 dice qué se pasó en detail y, si es tipado, en type. Cuando el motor sabe cuánto
esperar, manda Retry-After en segundos.
| Origen | Cuerpo | Retry-After |
¿Se guardó algo? |
|---|---|---|---|
| Ritmo de envío de la instancia | El comando, con command_id, status: en_cola y next_try |
Hasta next_try |
Sí: sale solo, no lo repitas. |
| Cola de envíos llena | problem+json | No | No. |
| Cupo de instancias de la app | problem+json | No | No. |
| Streams abiertos de la app | problem+json | No | No. |
cuota_agotada |
problem+json tipado | Hasta las 00:00 UTC | No se consultó nada. |
limitado_por_whatsapp |
problem+json tipado | Lo que dure el enfriamiento (60 s en las consultas de contactos) | No se consultó nada. |
vistas_agotadas |
problem+json tipado | 60 | No. |
cuota_excedida (multimedia) |
problem+json tipado | No | No. |
# El 429 de ritmo trae el comando: guárdalo y no repitas el pedido.curl -i -X POST https://motor.ejemplo.com/v1/instances/INSTANCIA_ID/messages \ -H "Authorization: Bearer TU_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: aviso-1043" \ -d '{"destino": {"telefono": "+NUMERO_DEL_CONTACTO"}, "tipo": "text", "texto": "Tu pedido salió."}'# HTTP/1.1 429 Too Many Requests# Retry-After: 2# {"command_id": "…", "status": "en_cola", "next_try": "…", …}// EnviarTexto trata el 429 de ritmo como un éxito: el comando quedó guardado.cmd, err := c.EnviarTexto(ctx, instanciaID, "aviso-1043", motor.Texto{ Destino: motor.Destino{Telefono: "+NUMERO_DEL_CONTACTO"}, Texto: "Tu pedido salió.",})var e *motor.Errorswitch {case err == nil: fmt.Println(cmd.ID, cmd.Estado, cmd.NextTry) // en_cola y cuándo salecase errors.As(err, &e) && e.Status == http.StatusTooManyRequests: // Cola llena o cuota: no se guardó. e.Codigo() y e.RetryAfter dicen cuál y cuánto.}const res = await fetch(`https://motor.ejemplo.com/v1/instances/${instanciaId}/messages`, { method: 'POST', headers: { Authorization: `Bearer ${apiKey}`, 'Content-Type': 'application/json', 'Idempotency-Key': 'aviso-1043', }, body: JSON.stringify({ destino: { telefono: '+NUMERO_DEL_CONTACTO' }, tipo: 'text', texto: 'Tu pedido salió.' }),});const cuerpo = await res.json();if (res.status === 429 && cuerpo.command_id) { // Ritmo: guardado; sale en cuerpo.next_try.} else if (res.status === 429) { const espera = Number(res.headers.get('retry-after') ?? 0); // 0: no lo dijo // Cola llena o cuota agotada: no se guardó nada.}Los demás códigos de error están en Errores.