Ir al contenido

Etiquetas y Business

Estas rutas cubren dos cosas de WhatsApp que se suelen pedir para un número de negocio:

  • Las etiquetas de WhatsApp del número: crearlas, editarlas, eliminarlas y ponerlas o quitarlas de un chat o de un mensaje. Viven en la sincronización de la app de WhatsApp (el appstate) y el teléfono las ve. En WhatsApp Business se ven con su color; en un número normal son las Listas del teléfono.
  • El perfil comercial de un número: categoría, descripción, dirección, correo, sitios y horario, tal como los entrega WhatsApp.
Operación Ruta Scope
Listar las etiquetas GET /v1/instances/{id}/labels labels:read
Crear una etiqueta POST /v1/instances/{id}/labels labels:manage
Editar una etiqueta PATCH /v1/instances/{id}/labels/{label_id} labels:manage
Eliminar una etiqueta DELETE /v1/instances/{id}/labels/{label_id} labels:manage
Poner o quitar en un chat o un mensaje POST …/labels/{label_id}/associations labels:manage
Ver la sincronización GET /v1/instances/{id}/appstate chats:read
Pedir una sincronización completa POST /v1/instances/{id}/appstate/resync chats:manage
El perfil comercial GET /v1/instances/{id}/business/profile contacts:lookup
  • Los comandos de etiquetas piden una Idempotency-Key (de 1 a 200 caracteres) y se ejecutan con la instancia conectada; sin conexión vuelven a la cola.
  • El perfil comercial se consulta en el momento: sin conexión, 409 no_conectada. Gasta una consulta de la cuota diaria de la app (la misma de Contactos).
  • Funciona con un número de WhatsApp Business y con uno normal; ver los límites.

Los ejemplos de Go y JavaScript usan una función auxiliar, llamar, que se define en el primer paso: el SDK de Go no trae métodos de etiquetas, así que es HTTP directo con el token del cliente. El perfil comercial sí tiene método en el SDK.

GET /v1/instances/{id}/labels devuelve las etiquetas en el orden de su ID, de a limit (1 a 200; por omisión, 100), con next_cursor y has_more. Cada una trae label_id (el ID de WhatsApp, un texto), nombre, color (el índice de la paleta de WhatsApp, de 0 a 19), predefinida (el ID de una etiqueta predefinida de Business), orden, cuántos chats y mensajes la tienen y eliminada. Las eliminadas solo salen con incluir_eliminadas=true.

Ventana de terminal
curl "https://motor.ejemplo.com/v1/instances/INSTANCIA_ID/labels?limit=100" \
-H "Authorization: Bearer TU_API_KEY"
200 OK
{
"labels": [
{
"label_id": "19",
"nombre": "Pedido pendiente",
"color": 3,
"eliminada": false,
"chats": 4,
"mensajes": 0,
"actualizada_en": "2026-10-24T15:50:00Z"
}
],
"has_more": false
}
Operación Ruta Cuerpo Evento
Crear POST …/labels nombre (1 a 100 caracteres) y color (0 a 19; por omisión, 0) label.created
Editar PATCH …/labels/{label_id} nombre y/o color: lo que no mandas queda igual label.updated
Eliminar DELETE …/labels/{label_id} sin cuerpo label.deleted

Los tres son el comando label.edit. Al crear, el motor trae primero las etiquetas del teléfono y usa el ID siguiente al mayor, así que no pisa una que ya existe ni reutiliza el de una eliminada. Eliminar la quita también de los chats y los mensajes que la tenían.

Ventana de terminal
curl -X POST https://motor.ejemplo.com/v1/instances/INSTANCIA_ID/labels \
-H "Authorization: Bearer TU_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: etiqueta-pedido-pendiente" \
-d '{"nombre": "Pedido pendiente", "color": 3}'
curl -X PATCH https://motor.ejemplo.com/v1/instances/INSTANCIA_ID/labels/19 \
-H "Authorization: Bearer TU_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: etiqueta-19-color-9" \
-d '{"color": 9}'

POST …/labels/{label_id}/associations con destino (el chat: telefono, jid o peer_ref) y asociar (true la pone, false la quita) es el comando label.chat. Con objetivo (provider_id de un mensaje de ese chat) es label.message y la etiqueta va en el mensaje. Llega label.associated o label.dissociated.

Ventana de terminal
curl -X POST https://motor.ejemplo.com/v1/instances/INSTANCIA_ID/labels/19/associations \
-H "Authorization: Bearer TU_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: etiqueta-19-chat-cliente-1042" \
-d '{"destino": {"peer_ref": "4b7d9e01-0000-4000-8000-0000000000b1"}, "asociar": true}'

Las etiquetas viven en la colección regular del appstate (regular_low y regular_high llevan el estado de los chats: fijar, archivar, leído, silenciar, destacar, borrar). GET …/appstate dice, por colección, su estado (pendiente, al_dia, error o clave_faltante), su version, el último fullSync, el error con su proximo_intento_en y si el motor le pidió una copia al teléfono porque una clave no llegaba (recuperacion_en).

POST …/appstate/resync (comando appstate.resync, scope chats:manage) vuelve a traer completas las colecciones que indiques (regular, regular_low, regular_high; vacío, las tres). Repetirlo no duplica nada.

Ventana de terminal
curl https://motor.ejemplo.com/v1/instances/INSTANCIA_ID/appstate \
-H "Authorization: Bearer TU_API_KEY"
curl -X POST https://motor.ejemplo.com/v1/instances/INSTANCIA_ID/appstate/resync \
-H "Authorization: Bearer TU_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: resync-etiquetas-2026-10-24" \
-d '{"colecciones": ["regular"]}'

GET /v1/instances/{id}/business/profile con uno de telefono, jid o peer_ref (ninguno: el número de la instancia) devuelve es_negocio, propio, categorias, descripcion, direccion, correo, sitios, zona_horaria, horario (por día, con modo specific_hours, open_24h o appointment_only, y abre y cierra en minutos desde la medianoche), areas_servicio (un negocio sin dirección física), opciones y la cuota de la app. Los campos que el negocio no puso no vienen.

Un número que no es Business devuelve es_negocio: false y ningún campo: es la respuesta real del servidor, no un perfil vacío inventado. El catálogo y las colecciones están en Catálogo.

Ventana de terminal
curl "https://motor.ejemplo.com/v1/instances/INSTANCIA_ID/business/profile?telefono=%2BNUMERO_DEL_CONTACTO" \
-H "Authorization: Bearer TU_API_KEY"
200 OK
{
"jid": "CONTACTO@s.whatsapp.net",
"es_negocio": true,
"propio": false,
"categorias": [{ "id": "CATEGORIA_ID", "nombre": "Beauty, cosmetic & personal care" }],
"zona_horaria": "America/Lima",
"horario": [
{ "dia": "mon", "modo": "appointment_only" },
{ "dia": "tue", "modo": "specific_hours", "abre": "540", "cierra": "1080" }
],
"opciones": { "commerce_experience": "catalog" },
"consultado_en": "2026-10-24T16:00:00Z",
"cuota": { "cuota": 500, "usadas": 3, "maximo_por_consulta": 20, "renueva": "2026-10-25T00:00:00Z" }
}

Los comandos de etiquetas (label.edit, label.chat, label.message) y appstate.resync nacen aceptado y terminan completado o fallido. Un error pasajero (sin conexión, una clave del appstate que falta) vuelve a la cola y se reintenta a los 30 s, 1, 2 y 5 minutos. El efecto llega con su command_id y desde: motor; lo que cambia el teléfono, con desde: whatsapp.

Todos los eventos del appstate traen momento (el del cambio en WhatsApp), version (la de la colección), full_sync (llegó en una sincronización completa: el estado ya existía) y desde.

label.created, label.updated y label.deleted

Sección titulada «label.created, label.updated y label.deleted»

Traen la etiqueta completa. label.updated trae además anterior, con el nombre y el color de antes.

label.updated
{
"etiqueta": { "label_id": "19", "nombre": "Pedido pendiente", "color": 9, "eliminada": false, "chats": 4, "mensajes": 0, "actualizada_en": "2026-10-24T15:55:00Z" },
"anterior": { "label_id": "19", "nombre": "Pedido pendiente", "color": 3 },
"momento": "2026-10-24T15:55:00Z",
"version": 190,
"full_sync": false,
"desde": "motor",
"coleccion": "regular"
}

Una etiqueta se puso o se quitó de un chat (o de un mensaje: trae mensaje con su provider_id). etiqueta viene vacía si el motor todavía no conoce esa etiqueta.

label.associated
{
"label_id": "19",
"etiqueta": { "label_id": "19", "nombre": "Pedido pendiente", "color": 9 },
"chat": { "peer_ref": "…", "jid": "CONTACTO@lid", "clase": "usuario", "aliases": ["CONTACTO@s.whatsapp.net"] },
"momento": "2026-10-24T15:56:00Z",
"version": 191,
"full_sync": false,
"desde": "motor"
}

Cambió el estado de un chat en la app de WhatsApp. accion es fijar, desfijar, archivar, desarchivar, silenciar, reactivar, marcar_leido, marcar_no_leido, destacar, quitar_destacado, borrar_chat, poner_candado o quitar_candado; estado es el del chat después del cambio.

{
"accion": "archivar",
"chat": { "peer_ref": "…", "jid": "CONTACTO@lid", "clase": "usuario", "aliases": [] },
"momento": "2026-10-24T16:01:00Z",
"coleccion": "regular_low",
"version": 207,
"full_sync": false,
"desde": "whatsapp"
}

Una colección se sincronizó o falló. estado es al_dia, error o clave_faltante; error es un código acotado (clave_faltante, mac_invalido, tiempo_excedido, sin_sesion, rechazado, no_capturado, otro) y recuperacion: true marca la copia que mandó el teléfono a pedido del motor.

{ "coleccion": "regular", "estado": "al_dia", "version": 205, "full_sync": true, "recuperacion": false }
HTTP error_code Cuándo
403 — A tu key le falta el scope (labels:read, labels:manage, chats:read, chats:manage o contacts:lookup).
404 etiqueta_desconocida Esa etiqueta no existe en esta instancia (o se eliminó).
409 no_conectada El perfil comercial con la instancia sin conexión.
409 — La misma Idempotency-Key con otros datos.
422 — Una etiqueta nueva sin nombre o con más de 100 caracteres, un color fuera de 0 a 19, una colección que no existe, o en el perfil comercial un peer_ref que no es válido o un número inválido.
429 cuota_agotada El perfil comercial con la cuota de la app agotada (Retry-After).

Los error_code de un comando que falla: rechazado_por_whatsapp (WhatsApp rechazó el cambio) o, tras agotar los reintentos, sin_sesion, clave_faltante, no_capturado o appstate_fallido.

  • En un número normal, las etiquetas son las Listas (caso A39, 16/10/2026): el servidor no las rechaza y crear, renombrar, poner en un chat, quitar y eliminar se ven en el teléfono. Pero el cliente normal no muestra el color ni los mensajes etiquetados, así que eso no se puede verificar. Ver Etiquetas en números normales.
  • El cliente Business probado no etiqueta mensajes (caso A39, 20/10/2026, Business 2.26): pone las listas en un chat o en un número, no en un mensaje; mensajes queda en 0. Desde el motor, crear, editar el color, asociar, quitar y eliminar pasaron, con su evento cada uno. Colores confirmados en el teléfono: 4 es gris y 9, amarillento.
  • Un ID reutilizado por el teléfono (falla F16, provisional): una lista creada en el teléfono con el ID de una eliminada llegó una vez sin label.created. Desde el motor no pasa (nunca reutiliza un ID); del lado del teléfono no se volvió a reproducir (20/10/2026).
  • Una etiqueta eliminada sigue contando sus chats (caso A39, 16/10/2026): chats puede quedar en 1 aunque el chat ya no la muestre.
  • El perfil comercial (caso A71, 20 y 22/10/2026): trae lo mismo que muestra el teléfono, sin inventar los campos que el negocio no puso; el área de servicio llega cuando el negocio no tiene dirección física. Una cuenta que no es Business da es_negocio: false. Cada consulta gastó una de la cuota.