Ir al contenido

Catálogo

Con estas dos rutas tu app lee el catálogo de productos de un número de WhatsApp Business:

  • GET /v1/instances/{id}/business/catalog: una página de productos, con su cursor;
  • GET /v1/instances/{id}/business/collections: las colecciones con sus productos.

Sin jid ni peer_ref se lee el catálogo del número de la instancia; con otro negocio, lo que este número puede ver de él (lo decide WhatsApp). Es solo lectura: el motor no crea ni edita productos. El perfil comercial (categoría, horario, dirección) está en Etiquetas y Business.

Operación Ruta Scope
Una página del catálogo GET /v1/instances/{id}/business/catalog catalog:read
Las colecciones GET /v1/instances/{id}/business/collections catalog:read
  • Una instancia conectado: se consulta en el momento y sin conexión responde 409 no_conectada.
  • La vía con que el motor pide el catálogo la elige el operador en Ajustes (Catálogo comercial). La de fábrica, graphql, necesita el token público de WhatsApp Web cargado en comercio.token_graphql: sin él, 409 via_catalogo_sin_token, sin consultar ni gastar cuota.
  • Cada consulta que sale a WhatsApp gasta una de la cuota comercial diaria de la app (comercio.consultas_dia; de fábrica, 500), que es aparte de la cuota de Contactos. Cuenta todo intento que pudo salir, también si falla.

El SDK de Go trae Catalogo y Colecciones.

Parámetro Qué es
jid El negocio: un JID de usuario @s.whatsapp.net, o un @lid con su teléfono ya comprobado por esta instancia.
peer_ref O el peer_ref de un contacto de esta instancia. No va junto con jid.
limit Productos por página, de 1 a 50 (por omisión, 10). Con un cursor, vale el de la primera página.
cursor El next_cursor de la página anterior.

La respuesta trae los productos de esta página y:

Campo Qué es
has_more true si WhatsApp informó una continuación; null si no la informó (desconocido, nunca un false inventado).
next_cursor El cursor de la página siguiente (del motor: opaco, firmado y atado a tu app, la instancia, el número vinculado, el negocio y el límite).
continuacion informada o no_informada.
paginacion Siempre proveedor: la continuación es la de WhatsApp.
completitud Siempre pagina: el motor no recorre el catálogo entero.
total Siempre null: WhatsApp no informa un total.
origen whatsapp (se consultó ahora) o cache (la misma lectura de hace poco, sin volver a WhatsApp ni gastar cuota).
fresca_hasta Hasta cuándo una consulta igual se responde con esta lectura.
vence_en Hasta cuándo sirve el next_cursor.
consulta_id La consulta que leyó esto: la de su evento query.completed.
negocio_jid, propio El negocio consultado y si es el número de la instancia.
cuota La cuota comercial de la app: cuota, usadas, renueva y su desglose (catalogo, colecciones).

Cada producto trae id (opaco y del negocio, no un identificador global), nombre, descripcion, codigo_vendedor (el retailer ID del negocio), url, precio_proveedor y moneda, imagenes (con solicitada_url, de 100 px, y original_url: URLs de WhatsApp, temporales, que el motor no descarga), revision (el estado de revisión, tal cual), oculto (null si no vino), y escala_precio y disponibilidad, siempre null.

Ventana de terminal
# El catálogo propio:
curl "https://motor.ejemplo.com/v1/instances/INSTANCIA_ID/business/catalog?limit=20" \
-H "Authorization: Bearer TU_API_KEY"
# El de otro negocio, y la página siguiente con su cursor:
curl "https://motor.ejemplo.com/v1/instances/INSTANCIA_ID/business/catalog?jid=NEGOCIO@s.whatsapp.net&limit=20" \
-H "Authorization: Bearer TU_API_KEY"
curl "https://motor.ejemplo.com/v1/instances/INSTANCIA_ID/business/catalog?cursor=NEXT_CURSOR" \
-H "Authorization: Bearer TU_API_KEY"
200 OK
{
"negocio_jid": "NEGOCIO@s.whatsapp.net",
"propio": false,
"productos": [
{
"id": "PRODUCTO_ID",
"nombre": "Crema hidratante 50 ml",
"descripcion": "Para piel seca",
"codigo_vendedor": "SKU-0042",
"precio_proveedor": "25900",
"moneda": "PEN",
"escala_precio": null,
"disponibilidad": null,
"oculto": null,
"revision": "APPROVED",
"imagenes": [
{ "solicitada_url": "https://mmg.whatsapp.net/…", "original_url": "https://mmg.whatsapp.net/…" }
]
}
],
"limit": 20,
"has_more": null,
"next_cursor": null,
"continuacion": "no_informada",
"paginacion": "proveedor",
"completitud": "pagina",
"total": null,
"origen": "whatsapp",
"consulta_id": "3f9a0c11-0000-4000-8000-0000000000d4",
"consultado_en": "2026-10-24T16:10:00Z",
"fresca_hasta": "2026-10-24T16:11:00Z",
"vence_en": "2026-10-24T16:25:00Z",
"cuota": {
"cuota": 500,
"usadas": 16,
"renueva": "2026-10-25T00:00:00Z",
"desglose": [{ "tipo": "catalogo", "cantidad": 14 }, { "tipo": "colecciones", "cantidad": 2 }]
}
}

GET …/business/collections pide a WhatsApp una vista con collection_limit colecciones (de 1 a 20; por omisión, 10) y item_limit productos por colección (de 1 a 50; por omisión, 10). Los dos se piden por separado y su producto no puede pasar de 200. Un producto puede estar en varias colecciones.

Esa vista se guarda, y next_cursor la pagina localmente (paginacion: snapshot_local, de a limit colecciones, de 1 a 20; por omisión, 10), sin volver a WhatsApp. has_more dice si hay más colecciones en la vista guardada, no en WhatsApp.

Ventana de terminal
curl "https://motor.ejemplo.com/v1/instances/INSTANCIA_ID/business/collections?collection_limit=10&item_limit=20&limit=5" \
-H "Authorization: Bearer TU_API_KEY"
200 OK (resumido)
{
"negocio_jid": "TU_NUMERO@s.whatsapp.net",
"propio": true,
"colecciones": [
{
"id": "COLECCION_ID",
"nombre": "Cuidado facial",
"revision": "APPROVED",
"puede_apelar": null,
"alcanza_limite": false,
"completitud_productos": "desconocida",
"productos": [{ "id": "PRODUCTO_ID", "nombre": "Crema hidratante 50 ml", "precio_proveedor": "25900", "moneda": "PEN", … }]
}
],
"collection_limit": 10,
"item_limit": 20,
"limit": 5,
"has_more": false,
"next_cursor": null,
"total_vista": 1,
"paginacion": "snapshot_local",
"continuacion_remota": false,
"proveedor_has_more": null,
"paging_recibido": false,
"posible_truncamiento": false,
"completitud": "desconocida",
"origen": "whatsapp",
"consulta_id": "3f9a0c11-0000-4000-8000-0000000000d5",
"cuota": { "cuota": 500, "usadas": 17, "renueva": "2026-10-25T00:00:00Z", "desglose": [ … ] }
}
  • Frescura: una consulta igual del mismo número dentro de comercio.segundos_cache (de fábrica, 60 s; 30 s para una lista vacía) devuelve la misma lectura con origen: cache, sin gastar cuota. Un error nunca se guarda.
  • Vida de un cursor: comercio.minutos_cursor (de fábrica, 15 minutos); WhatsApp puede vencer el suyo antes. Un cursor sobrevive a un reinicio del motor. Vencido, 409 cursor_vencido; de otra vinculación, otro negocio u otro límite, 409 cursor_incompatible; alterado, 422 cursor_invalido; de otra instancia o app, 404. En los tres primeros casos, vuelve a pedir la primera página.
  • Cupo por instancia: el motor guarda hasta comercio.vistas_instancia resultados (de fábrica, 100) y 10 MB por instancia. Con el cupo lleno, 429 vistas_agotadas: nunca borra un cursor vigente.
  • Ritmo: una consulta a la vez por instancia, con 2 s entre dos. Si WhatsApp limita, la instancia espera 60 s antes de la próxima, y el doble si se repite (hasta 15 minutos): 429 limitado_por_whatsapp con Retry-After.
  • Espera: sin respuesta de WhatsApp en comercio.segundos_timeout (de fábrica, 15 s), 504 consulta_agotada, nunca una lista vacía.

Todos estos valores son Ajustes del operador.

Estas rutas no crean comandos: responden en el momento. Cada consulta que sale a WhatsApp deja en el ledger de la instancia un evento sin datos del negocio (ni productos ni el JID consultado), con el mismo consulta_id que la respuesta. Una respuesta desde la caché no deja evento.

La consulta terminó bien. cantidad es cuántos productos (o colecciones) leyó, vence_en hasta cuándo sirve su cursor y, en el catálogo, continuacion dice si WhatsApp informó otra página.

{
"consulta_id": "3f9a0c11-0000-4000-8000-0000000000d4",
"operacion": "catalogo",
"resultado": "ok",
"cantidad": 5,
"vence_en": "2026-10-24T16:25:00Z"
}

La consulta falló. codigo es el mismo error_code de la respuesta HTTP; via es la vía que se pidió y server_code y server_origin, el código numérico de WhatsApp y por dónde llegó, si lo hubo. La respuesta HTTP del error trae el consulta_id en errors.

{
"consulta_id": "3f9a0c11-0000-4000-8000-0000000000d6",
"operacion": "colecciones",
"codigo": "catalogo_no_disponible",
"via": "graphql",
"server_code": 2498055,
"server_origin": "graphql"
}

El error_code va en type como urn:motor-whatsapp:error:<código>; el SDK de Go lo devuelve en Codigo().

HTTP error_code Cuándo
403 — A tu key le falta catalog:read.
403 catalogo_no_autorizado WhatsApp no deja a este número ver ese catálogo (no dice por qué).
403 credencial_no_vigente Tu credencial dejó de autorizar la consulta mientras esperaba.
404 catalogo_no_disponible WhatsApp no encontró un catálogo para ese número (sin catálogo, no Business u otra causa).
409 no_conectada La instancia no está conectada (o se desconectó durante la consulta).
409 via_catalogo_sin_token La vía graphql sin el token en Ajustes. No se consultó.
409 doc_id_catalogo_invalido, variables_catalogo_invalidas Los ajustes de la consulta del catálogo no son válidos. No se consultó.
409 cursor_vencido, cursor_incompatible Vuelve a pedir la primera página.
409 observador_cambio La instancia cambió de número vinculado durante la consulta.
422 cursor_invalido El cursor fue alterado: úsalo tal cual lo dio el motor.
422 — jid y peer_ref juntos, un peer_ref que no es válido, o collection_limit × item_limit mayor que 200.
429 cuota_agotada La app usó su cuota comercial de hoy (vuelve a cero a las 00:00 UTC). No se consultó.
429 limitado_por_whatsapp WhatsApp limitó las consultas del número: la instancia está en enfriamiento.
429 vistas_agotadas La instancia llegó a su cupo de resultados guardados.
501 catalogo_no_soportado El proveedor no consulta catálogos.
502 consulta_proveedor_rechazada WhatsApp rechazó la consulta.
502 respuesta_proveedor_invalida La respuesta no tiene el formato esperado: no se entrega como una lista vacía.
502 respuesta_proveedor_demasiado_grande La respuesta supera los límites del motor: pide menos con limit.
502 continuacion_sin_avance WhatsApp devolvió una continuación ya usada: vuelve a la primera página.
503 proveedor_temporalmente_no_disponible WhatsApp no está disponible para la consulta; reintenta más tarde.
503 registro_no_disponible La consulta terminó pero el motor no pudo registrar su resultado; reintenta.
504 consulta_agotada WhatsApp no contestó a tiempo: no hay resultado, tampoco uno vacío.
  • Solo responde la vía graphql (falla F30, corregida el 20/10/2026): WhatsApp ya no contesta el pedido viejo (iq) y por mex da 400. Con graphql y el token cargado, el catálogo y las colecciones, propios y ajenos, responden. Por eso es la vía de fábrica.
  • Lo oculto no aparece (caso B11, 20/10/2026): un producto oculto no llega marcado como oculto: desaparece de la respuesta, también para el dueño.
  • Gratis y sin precio no se distinguen (caso B13, 20/10/2026): los dos llegan sin precio_proveedor ni moneda. El precio con valor llegó ×1000 ("25900" = S/ 25,90).
  • Sin cursor del servidor en catálogos chicos (casos B8 y B15, 20/10/2026): con 4 o 5 productos visibles y limit de 1 o 2, WhatsApp devolvió todos de una vez, sin continuación. El recorrido con el cursor de WhatsApp y el cursor alterado, vencido o tras un reinicio no se pudieron probar en real (hace falta un catálogo de más de 20 productos).
  • Un número sin catálogo (caso B10 y falla F31, 20/10/2026): el catálogo de un número normal dio 404 catalogo_no_disponible (código 2498052) y sus colecciones, también 404 (código 2498055), desde la corrección de la ronda 6.
  • Un número normal puede leer catálogos (caso B7, 20/10/2026): vio lo mismo que el dueño, sin lo oculto. Bloquear al negocio desde el observador no cambió la respuesta (B12); el caso de un negocio que bloquea al observador no se probó.
  • Imágenes (caso B14, 20/10/2026): la solicitada_url mide 100 px de lado mayor, la original de 1080 a 1600 px, y las URLs vencieron a los ~4,5 días.
  • Corte y límites (casos B16 y B17, 20/10/2026): con la red cortada, 503 en 17 ms con su query.failed, nunca una lista vacía. En unas 35 consultas del día no hubo ningún 429.