API: clientes, conversaciones y mensajes
Buscar y editar clientes, ver qué chats esperan a una persona, leer su historial, tomarlos o devolverlos al bot, escribirles por el WhatsApp del negocio (texto, fotos, archivos, notas de voz y ubicación) y bajar los archivos de los chats.
Revisado el 28 de octubre de 2026
El cliente
Un cliente nace la primera vez que le escribe al negocio por WhatsApp, o cuando alguien lo crea a mano en el panel. Lo identifica su teléfono.
{
"id": "c4d5e6f7-a8b9-4c0d-9e1f-2a3b4c5d6e7f",
"object": "customer",
"name": "María Pérez",
"phone": "584140000000",
"id_document": "V-00000000",
"address": "Av. Principal de Los Palos Grandes, casa 12",
"notes": "Prefiere que la llamen antes de llegar.",
"name_confirmed": true,
"bot_muted": false,
"created_at": "2026-07-02T21:14:09.330Z",
"updated_at": "2026-09-29T17:58:12.412Z"
}| Campo | Tipo | Qué es |
|---|---|---|
id | uuid | Id del cliente. |
object | texto | Siempre customer. |
name | texto | null | Su nombre, si lo dio o lo cargó el negocio. |
phone | texto | Solo dígitos con código de país: 584140000000. |
id_document | texto | null | Cédula o RIF, si lo dio. |
address | texto | null | Su dirección guardada. |
notes | texto | null | Notas internas del negocio sobre el cliente. El cliente no las ve. |
name_confirmed | booleano | true si el nombre lo dio el cliente o lo puso el negocio. false si name es solo el de su perfil de WhatsApp, que el bot no da por bueno (por eso a veces se lo pregunta). |
bot_muted | booleano | true si el cliente pidió que el bot no le responda: lo atiende solo una persona. Si el cliente vuelve a pedir que el bot le conteste, pasa a false solo. |
created_at | fecha | Cuándo se creó. |
updated_at | fecha | Último cambio. |
Dentro de un pedido, una cita, un pago, un viaje o un mensaje, el cliente viene resumido: { id, name, phone }.
Buscar clientes
Del más nuevo al más viejo, paginado. Filtros opcionales:
| Parámetro | Qué filtra |
|---|---|
phone | Teléfono exacto. Se quedan solo los dígitos, así que +58 414-000.00.00 y 584140000000 valen igual; tiene que llevar el código de país. |
q | Busca el texto dentro del nombre, el teléfono o el documento, sin distinguir mayúsculas. |
updated_after | Los que cambiaron en esa fecha o después, del más viejo al más nuevo (ver sincronización incremental). |
curl "https://api.atiendefacil.com/api/v1/customers?phone=584140000000" \
-H "Authorization: Bearer af_live_TU_CLAVE_AQUI"Un cliente.
Un cliente nace cuando escribe, al crear un pedido, una cita o un viaje con un customer_phone que no existía, o al darlo de alta con POST /customers (abajo). Para enterarte de los nuevos, de los cambios y de los borrados usa los webhooks customer.created, customer.updated (trae en changes qué cambió: name, phone, id_document, address, notes o bot_muted) y customer.deleted (trae el phone y el name que tenía; al borrar un cliente se borran también sus pedidos y citas).
Si tu sistema ya conoce a los clientes del negocio, no hace falta esperar a que el asistente les pida el nombre y la cédula: con el webhook customer.lookup, AtiendeFácil te pregunta por el teléfono de quien escribe y guarda lo que respondas antes de contestarle (ver Consultar un cliente en tu sistema).
Editar un cliente
Cambia los datos de un cliente, como la ficha del cliente en el panel. Manda solo lo que quieras cambiar; lo que no mandas se queda como está.
| Campo | Qué es |
|---|---|
name | Nombre, de 1 a 160 caracteres. Poner un nombre lo marca como confirmado (name_confirmed: true) y el bot deja de preguntárselo. Con null se borra. |
id_document | Cédula o RIF. Se valida igual que en el panel (cédula V o E de 6 a 8 números, o RIF con su dígito verificador) y se guarda formateado: v12345678 queda V-12345678. Si no es válido: 400 invalid_id_document, con el motivo en el mensaje. Si mandas el mismo que ya tenía, no se revisa. Con null se borra. |
address | Dirección, hasta 500 caracteres. null lo borra. |
notes | Notas internas, hasta 2.000 caracteres. null las borra. |
bot_muted | true: el bot deja de responderle a este cliente y lo atiende solo una persona. false: vuelve a responderle. |
curl -X PATCH https://api.atiendefacil.com/api/v1/customers/c4d5e6f7-a8b9-4c0d-9e1f-2a3b4c5d6e7f \
-H "Authorization: Bearer af_live_TU_CLAVE_AQUI" \
-H "Content-Type: application/json" \
-d '{ "name": "María Pérez", "id_document": "V00000000", "notes": "Cliente mayorista" }'- Responde
200con el cliente ya actualizado. - El teléfono no se cambia por la API. Cualquier campo que no esté en la tabla responde
400 invalid_body, igual que un valor demasiado largo. - Sale
customer.updatedsi de verdad cambió algo, y queda en la auditoría: «editó un cliente desde la API».
Dar de alta y borrar clientes
Da de alta un cliente por su teléfono, para cargar la cartera de tu sistema sin esperar a que cada uno escriba. Responde 201 con el cliente.
| Campo | Obligatorio | Qué es |
|---|---|---|
phone | Sí | Con o sin código de país: 04140000000 se guarda como 584140000000. Si no es un teléfono: 400 invalid_phone. |
name | No | Hasta 160 caracteres. Queda como nombre confirmado: el bot no se lo pregunta. |
id_document | No | Cédula o RIF, validado y formateado como en PATCH (V-00000000, J-00000000-0). Si no es válido: 400 invalid_id_document. |
address | No | Hasta 500 caracteres. |
notes | No | Notas internas, hasta 2000 caracteres. |
curl -X POST https://api.atiendefacil.com/api/v1/customers \
-H "Authorization: Bearer af_live_TU_CLAVE_AQUI" \
-H "Content-Type: application/json" \
-d '{ "phone": "04140000000", "name": "María Pérez", "id_document": "V00000000" }'- No duplica. Si ya hay un cliente con ese teléfono responde
200(no201) con el que existe. De lo que mandes solo le pone elname, si traes uno distinto; el documento, la dirección y las notas de un cliente que ya existe se cambian conPATCH /customers/{id}. - Darlo de alta no le escribe ni le abre una conversación: a un cliente solo se le puede mandar un mensaje cuando ya escribió él.
- Sale
customer.created(ocustomer.updatedsi ya existía y cambió el nombre), condata.sourcede tu clave. - Si lo que quieres es que el cliente quede identificado justo cuando escribe por primera vez, sin cargar toda la cartera, mira customer.lookup.
Borra el cliente y, con él, sus pedidos, sus citas y su conversación. No se puede deshacer. Responde { "id": "…", "object": "customer", "deleted": true } y sale customer.deleted. Si vuelve a escribir, nace como cliente nuevo.
Números bloqueados
A un número bloqueado el asistente no le contesta. Es la misma lista de Ajustes → Bloqueados del panel y la app. Un cambio en la lista se aplica en un minuto o menos.
| Petición | Qué hace |
|---|---|
GET /blocklist | Los bloqueados, del más reciente al más viejo (hasta 500, sin paginar). Cada uno: { id, object: "blocked_number", phone, reason, created_at }. |
POST /blocklist | Bloquea un número: phone y, opcional, reason (hasta 200 caracteres). Si ya estaba bloqueado, le cambia el motivo. Responde 201. |
DELETE /blocklist/{id} | Lo desbloquea. El id es el del bloqueo, no el del cliente. |
Para que el asistente deje de contestarle a un cliente sin bloquear el número, usa bot_muted en PATCH /customers/{id}.
Conversaciones
Una conversación es el chat de un cliente con el negocio. Por la API se ve en qué está cada una (si el bot la atiende, si espera a una persona, si alguien la tomó), se lee su historial y se puede tomar o devolver al bot, igual que desde Chats en el panel y la app (ver Chats).
{
"id": "e1f2a3b4-c5d6-4e7f-8a9b-0c1d2e3f4a5b",
"object": "conversation",
"state": "chatting",
"needs_agent": true,
"agent_reason": "¿Hacen tortas por encargo para 40 personas?",
"needs_agent_since": "2026-09-29T18:41:02.300Z",
"awaiting_delivery_quote": false,
"taken_over": false,
"taken_over_at": null,
"archived": false,
"customer": {
"id": "c4d5e6f7-a8b9-4c0d-9e1f-2a3b4c5d6e7f",
"name": "María Pérez",
"phone": "584140000000"
},
"last_message_at": "2026-09-29T18:41:01.870Z",
"created_at": "2026-07-02T21:14:09.402Z",
"updated_at": "2026-09-29T18:41:02.300Z"
}| Campo | Tipo | Qué es |
|---|---|---|
id | uuid | Id de la conversación. Es el conversation_id de pedidos y mensajes. |
object | texto | Siempre conversation. |
state | texto | En qué anda el bot con el cliente: chatting, taking_order, awaiting_payment, in_review, scheduling o closed. Es orientativo; para saber si hace falta alguien, mira los campos de abajo. |
needs_agent | booleano | El chat espera a una persona: el cliente pidió hablar con alguien o el bot no pudo seguir. Mientras tanto, el bot no le responde. En el panel sale como «Requiere agente». |
agent_reason | texto | null | Lo que preguntó el cliente o por qué se detuvo el bot. |
needs_agent_since | fecha | null | Desde cuándo espera. |
awaiting_delivery_quote | booleano | Espera que el negocio le ponga el costo del envío a su pedido (ver Cotizar el envío). No cuenta como needs_agent. |
taken_over | booleano | Alguien tomó el chat (desde el teléfono del negocio o por la API): el bot no responde en él hasta que se devuelva. |
taken_over_at | fecha | null | Cuándo se tomó. |
archived | booleano | El negocio lo archivó. |
customer | objeto | Resumen del cliente: { id, name, phone }. |
last_message_at | fecha | null | Cuándo fue el último mensaje. |
created_at | fecha | Cuándo empezó. |
updated_at | fecha | Último cambio. |
Listar y ver
Del último mensaje más reciente al más viejo, paginado. Filtros opcionales:
| Parámetro | Qué filtra |
|---|---|
needs_agent=true | Solo las que esperan a una persona. |
taken_over=true | Solo las tomadas a mano. |
customer_id | Las de un cliente. Si no es un uuid: 400 invalid_id. |
q | Busca por el nombre o el teléfono del cliente, sobre todas las conversaciones. El nombre, por cualquier trozo y sin distinguir mayúsculas; el teléfono se compara solo por sus dígitos, sin ceros a la izquierda, y hacen falta al menos 3 (q=0414-123 encuentra 58414123…). Hasta 80 caracteres. Se combina con los demás filtros y con page y limit. |
updated_after | Las que cambiaron desde esa fecha, de la más vieja a la más nueva (ver sincronización incremental). |
Una conversación.
El historial del chat, del mensaje más nuevo al más viejo, paginado con page y limit. Cada mensaje tiene la forma de El mensaje: los del cliente, los del bot y los del equipo.
# Los chats que esperan a una persona
curl "https://api.atiendefacil.com/api/v1/conversations?needs_agent=true" \
-H "Authorization: Bearer af_live_TU_CLAVE_AQUI"
# Los mensajes de uno, del más nuevo al más viejo
curl "https://api.atiendefacil.com/api/v1/conversations/e1f2a3b4-c5d6-4e7f-8a9b-0c1d2e3f4a5b/messages?limit=50" \
-H "Authorization: Bearer af_live_TU_CLAVE_AQUI"Tomar el chat y devolverlo al bot
Toma el chat: el bot deja de responder en esa conversación (en las demás sigue igual) hasta que se devuelva. No caduca sola. Es lo mismo que pasa cuando alguien le escribe al cliente desde el teléfono del negocio. Si ya estaba tomado, no cambia nada. Sale conversation.taken_over.
Se lo devuelve al bot: levanta a la vez la espera de una persona y la toma a mano, como el botón Reanudar del panel. Sale conversation.bot_resumed. Si la conversación espera la cotización del envío responde 409 awaiting_delivery_quote: eso se resuelve con POST /orders/{id}/delivery-quote.
# Tomar el chat: el bot se calla en esta conversación
curl -X POST https://api.atiendefacil.com/api/v1/conversations/e1f2a3b4-c5d6-4e7f-8a9b-0c1d2e3f4a5b/takeover \
-H "Authorization: Bearer af_live_TU_CLAVE_AQUI"
# Devolvérselo al bot
curl -X POST https://api.atiendefacil.com/api/v1/conversations/e1f2a3b4-c5d6-4e7f-8a9b-0c1d2e3f4a5b/resume \
-H "Authorization: Bearer af_live_TU_CLAVE_AQUI"Las dos responden 200 con la conversación actualizada.
La marca como leída: apaga el globo de mensajes sin leer en el panel y la app, como al abrir el chat. No cambia quién la atiende. Responde la conversación.
Cuándo espera a una persona
- Cuando el cliente pide hablar con alguien o el bot no puede seguir, la conversación pasa a
needs_agent: truey sale el webhookconversation.needs_agent, con el motivo endata.reason. - Se resuelve contestándole con
POST /messages(eso ya levanta la espera y saleconversation.bot_resumed: el bot atiende lo siguiente que escriba el cliente) o con/resume. Si prefieres que el bot no se meta mientras hablas con el cliente, tómalo antes con/takeover. - Si nadie la atiende, la espera caduca sola: cuando el cliente vuelve a escribir pasadas 12 horas, el bot retoma y sale
conversation.bot_resumed.
Enviar un mensaje
Le manda un texto a un cliente por el WhatsApp del negocio. Sale como mensaje del equipo, igual que si alguien lo escribiera desde el chat del panel, y queda en esa conversación a la vista de todos.
| Campo | Obligatorio | Qué es |
|---|---|---|
customer_id | Uno de los dos | El id del cliente. |
phone | Uno de los dos | Su teléfono (se quedan solo los dígitos). Si mandas los dos, manda customer_id. |
text | Sí | El mensaje, de 1 a 4.000 caracteres. Se le quitan los espacios de los extremos. |
curl -X POST https://api.atiendefacil.com/api/v1/messages \
-H "Authorization: Bearer af_live_TU_CLAVE_AQUI" \
-H "Content-Type: application/json" \
-d '{ "phone": "584140000000", "text": "¡Hola María! Tu pedido ya salió." }'Responde 201 con el mensaje:
{
"id": "f5a6b7c8-d9e0-4f1a-8b2c-3d4e5f6a7b8c",
"object": "message",
"conversation_id": "e1f2a3b4-c5d6-4e7f-8a9b-0c1d2e3f4a5b",
"direction": "outbound",
"sender": "agent",
"type": "text",
"text": "¡Hola María! Tu pedido ya salió.",
"location": null,
"file": null,
"has_media": false,
"customer": {
"id": "c4d5e6f7-a8b9-4c0d-9e1f-2a3b4c5d6e7f",
"name": "María Pérez",
"phone": "584140000000"
},
"created_at": "2026-09-29T18:20:31.004Z"
}Las reglas
- Ventana de 24 horas (solo canal oficial). Si el negocio está conectado por la API oficial de Meta, WhatsApp solo deja escribir libremente dentro de las 24 horas siguientes al último mensaje del cliente. Pasado eso, responde
409 outside_service_window. Conectado por código QR no hay ventana. - Solo texto. Las fotos, los archivos y las notas de voz van por POST /conversations/{id}/media y la ubicación por POST /conversations/{id}/location. Por esta ruta no salen plantillas de WhatsApp: para avisar a un cliente fuera de la ventana usa
POST /whatsapp/sendcontemplate(ver Empezar con la API). - 30 mensajes por minuto por clave. No es una herramienta de envíos masivos.
- Cada llamada manda un mensaje. Si la petición se corta sin respuesta, revisa el chat antes de repetirla o el cliente lo recibirá dos veces.
Errores
| Respuesta | Cuándo |
|---|---|
| 400 invalid_body | Falta text, está vacío o pasa de 4.000 caracteres, o no hay ni customer_id ni phone. |
| 404 no_conversation | Ese cliente nunca le escribió al negocio (o no existe). |
| 409 outside_service_window | Canal oficial y más de 24 horas desde el último mensaje del cliente. |
| 409 whatsapp_not_connected | El WhatsApp del negocio no está conectado; el mensaje no salió. |
| 402 meta_no_credit | Canal oficial y el saldo no cubre el mensaje (10.000 tokens cada uno); no salió. |
| 429 | Más de 30 mensajes en un minuto. |
Enviar fotos, archivos y audio
Manda una foto, un documento, una nota de voz o un vídeo en una conversación que ya existe, por el WhatsApp del negocio. Hace lo mismo que el botón de adjuntar del chat en el panel y la app: el archivo queda en el hilo, a la vista de todo el equipo.
| Campo | Obligatorio | Qué es |
|---|---|---|
kind | Sí | image, document, audio o video. |
base64 | Uno de los dos | El archivo en base64, con o sin el prefijo data:…;base64,. Hasta 25 MB de archivo. |
url | Uno de los dos | La dirección https:// de donde bajar el archivo, en vez de mandarlo en base64. Hasta 25 MB. Ver «Enviar desde una URL». |
mime_type | Con base64, sí | El tipo del archivo: image/jpeg, application/pdf, audio/webm… Con url es opcional: si falta, vale el que declare tu servidor al entregarlo. |
file_name | No | El nombre con el que lo ve el cliente (en un documento es lo que sale en el chat). Hasta 200 caracteres. |
caption | No | El texto que va al lado del archivo. Hasta 1.024 caracteres. |
# Una foto con texto al lado (base64 con o sin el prefijo data:)
curl -X POST https://api.atiendefacil.com/api/v1/conversations/e1f2a3b4-c5d6-4e7f-8a9b-0c1d2e3f4a5b/media \
-H "Authorization: Bearer af_live_TU_CLAVE_AQUI" \
-H "Content-Type: application/json" \
-d '{ "kind": "image", "mime_type": "image/jpeg", "file_name": "factura-1042.jpg",
"caption": "Aquí tu factura", "base64": "/9j/4AAQSkZJRgABAQ…" }'
# Una nota de voz grabada en el navegador (WebM/Opus): se convierte sola
curl -X POST https://api.atiendefacil.com/api/v1/conversations/e1f2a3b4-c5d6-4e7f-8a9b-0c1d2e3f4a5b/media \
-H "Authorization: Bearer af_live_TU_CLAVE_AQUI" \
-H "Content-Type: application/json" \
-d '{ "kind": "audio", "mime_type": "audio/webm;codecs=opus", "base64": "GkXfo59ChoEBQveBAULygQRC84EIQoKE…" }'Responde 201 con el mensaje ya guardado, con has_media: true:
{
"id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
"object": "message",
"conversation_id": "e1f2a3b4-c5d6-4e7f-8a9b-0c1d2e3f4a5b",
"direction": "outbound",
"sender": "agent",
"type": "image",
"text": "Aquí tu factura",
"location": null,
"file": { "name": "factura-1042.jpg", "mime_type": "image/jpeg" },
"has_media": true,
"customer": {
"id": "c4d5e6f7-a8b9-4c0d-9e1f-2a3b4c5d6e7f",
"name": "María Pérez",
"phone": "584140000000"
},
"created_at": "2026-09-29T18:22:05.118Z"
}Enviar desde una URL
Si el archivo ya está publicado en tu sistema, manda url y AtiendeFácil lo baja en el momento:
# Un PDF que ya está en tu sistema: se baja de la url y sale al chat
curl -X POST https://api.atiendefacil.com/api/v1/conversations/e1f2a3b4-c5d6-4e7f-8a9b-0c1d2e3f4a5b/media -H "Authorization: Bearer af_live_TU_CLAVE_AQUI" -H "Content-Type: application/json" -d '{ "kind": "document", "mime_type": "application/pdf", "file_name": "poliza-1042.pdf",
"caption": "Aquí tu póliza", "url": "https://mi-sistema.com/polizas/1042.pdf?firma=…" }'- Tiene que ser
https://, en un dominio público, y entregar el archivo directamente: no se siguen redirecciones ni se mandan cabeceras de acceso. - Se baja una sola vez, al recibir la petición, con 15 segundos de margen. Lo que sale al chat es una copia: puedes borrar el original después.
- Si la dirección solo vale con un inicio de sesión, usa un enlace firmado que caduque o manda el archivo en
base64.
Qué se hace con cada tipo
- Fotos (
image): en JPEG, PNG, WebP o GIF. Se dejan en JPEG o PNG y se achican si hace falta para que WhatsApp las acepte, como las que se mandan desde el panel. Enfilevuelve el tipo con el que salió. Otro formato de imagen mándalo comodocument. - Notas de voz (
audio): WhatsApp solo las muestra como nota de voz en Ogg/Opus, así que todo lo demás se convierte antes de mandarlo: la grabación del navegador (WebM/Opus), M4A/AAC, MP3, WAV… Si el audio no se puede convertir, responde415 audio_format_unsupportedy no sale nada. - Documentos y vídeos (
document,video): salen tal cual. El vídeo, en MP4.
Las reglas
- Las mismas que
POST /messages: solo en conversaciones que ya existen, ventana de 24 horas en el canal oficial, y 30 envíos por minuto por clave. - No toma el chat ni levanta la espera de una persona: si la conversación tenía
needs_agent: true, sigue así (igual que al mandar un adjunto desde el panel). Contesta con texto porPOST /messageso usa/resumepara levantarla. - Si WhatsApp no acepta el archivo, el mensaje no se guarda: no queda en el hilo nada que el cliente no haya recibido.
Errores
| Respuesta | Cuándo |
|---|---|
| 400 invalid_body | Falta kind o el archivo (base64 o url, uno de los dos); falta mime_type; el base64 o la url no son válidos o el archivo llegó vacío; o una imagen no es JPEG, PNG, WebP ni GIF. |
| 404 not_found | La conversación no existe o es de otro negocio. |
| 409 outside_service_window | Canal oficial y más de 24 horas desde el último mensaje del cliente. |
| 409 whatsapp_not_connected | El WhatsApp del negocio no está conectado; no salió nada. |
| 402 meta_no_credit | Canal oficial y el saldo no cubre el mensaje (10.000 tokens cada uno); no salió nada. |
| 413 file_too_large | El archivo pasa de 25 MB. |
| 415 audio_format_unsupported | El audio no se pudo convertir a nota de voz. |
| 415 format_unsupported | El cliente escribe por Instagram, que solo acepta documentos en PDF. |
| 422 file_not_fetched | No se pudo bajar el archivo de la url: no respondió a tiempo, respondió con un error o con una redirección. El mensaje dice cuál. |
| 502 media_not_sent | WhatsApp no aceptó el archivo. Puedes reintentar en un momento. |
| 429 | Más de 30 envíos en un minuto. |
Enviar una ubicación
Manda un punto del mapa en una conversación que ya existe, como el botón de ubicación del chat. La del negocio está en GET /me (location); puedes mandar esa u otra.
| Campo | Obligatorio | Qué es |
|---|---|---|
lat | Sí | Latitud, número entre -90 y 90. |
lng | Sí | Longitud, número entre -180 y 180. |
name | No | El nombre del sitio, hasta 200 caracteres. |
address | No | La dirección escrita, hasta 300 caracteres. |
# La ubicación del negocio (sale en GET /me → location)
curl -X POST https://api.atiendefacil.com/api/v1/conversations/e1f2a3b4-c5d6-4e7f-8a9b-0c1d2e3f4a5b/location \
-H "Authorization: Bearer af_live_TU_CLAVE_AQUI" \
-H "Content-Type: application/json" \
-d '{ "lat": 10.4806, "lng": -66.9036, "name": "Pizzería La Esquina", "address": "Av. Bolívar, local 3, Caracas" }'Responde 201 con el mensaje (type: "location", el punto en location y en text el nombre o, si no hay, la dirección). Mismas reglas que los adjuntos. Errores: 400 invalid_body (faltan las coordenadas o se salen de rango), 404 not_found, 409 outside_service_window, 409 whatsapp_not_connected, 402 meta_no_credit y 429.
Descargar archivos
El archivo de un mensaje: la foto, el documento o la nota de voz que mandó el cliente, o lo que mandó el negocio. Sirve para cualquier mensaje con has_media: true.
# Bajar el archivo de un mensaje (del cliente o del negocio) a disco
curl https://api.atiendefacil.com/api/v1/messages/a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d/media \
-H "Authorization: Bearer af_live_TU_CLAVE_AQUI" \
-o factura-1042.jpg- Responde
200con los bytes del archivo, no con JSON:Content-Typecon su tipo,Content-LengthyContent-Disposition: attachmentcon su nombre (si no tiene, uno armado con el id del mensaje y la extensión). - Nunca redirige: el archivo sale siempre de esta misma dirección, con tu clave en
Authorizationcomo el resto de la API. - Las notas de voz están en Ogg/Opus, que es como las manda WhatsApp.
404 media_not_found: el mensaje no tiene archivo, o ya se borró. Los archivos de los chats se borran a los 2 días, como en el panel (ver Chats); el mensaje sigue en el hilo, confile, pero conhas_media: false. Si te hace falta guardarlo, bájalo antes.404 not_found: el mensaje no existe o es de otro negocio.
El mensaje
Es lo que devuelven POST /messages, POST /conversations/{id}/media y /location, y lo que traen los webhooks message.received (cada mensaje que escribe un cliente) y message.sent (cada mensaje que le manda el negocio: el asistente, el equipo o tu integración).
| Campo | Tipo | Qué es |
|---|---|---|
id | uuid | Id del mensaje. |
object | texto | Siempre message. |
conversation_id | uuid | El chat al que pertenece. |
direction | texto | inbound (lo escribió el cliente) u outbound (salió del negocio). |
sender | texto | customer (el cliente), assistant (la IA), agent (alguien del equipo o tu integración) o system. |
type | texto | text, audio, image, location, video, document o sticker. |
text | texto | El texto. En una foto o un documento, lo que escribió al lado (puede ir vacío). |
location | objeto | null | { lat, lng } si compartió una ubicación. |
file | objeto | null | { name, mime_type } si el mensaje lleva un archivo. Se queda aunque el archivo ya se haya borrado. |
has_media | booleano | true si hay archivo que bajar con GET /messages/{id}/media. Pasa a false cuando el archivo se borra, a los 2 días. |
customer | objeto | Resumen del cliente. |
created_at | fecha | Cuándo se guardó. |
Para recibir lo que escriben los clientes al momento, usa el webhook message.received, y para lo que les contesta el negocio, message.sent (ver Webhooks). Para leer el historial de un chat, GET /conversations/{id}/messages (ver Conversaciones).
Preguntas frecuentes
¿El bot sigue respondiendo después de que escribo por la API?
Tu mensaje cuenta como una respuesta del equipo, igual que escribir desde el chat del panel: si el chat esperaba a una persona, deja de esperar y el bot atiende lo siguiente que escriba el cliente. Si quieres que el bot no se meta mientras hablas tú, toma el chat antes con POST /conversations/{id}/takeover y devuélveselo con /resume al terminar. Un cliente con bot_muted: true no recibe respuestas del bot mientras siga así.
¿Puedo escribirle a un cliente que hace una semana no escribe?
Conectado por QR, sí. Con la API oficial de Meta no: fuera de las 24 horas WhatsApp no lo permite y responde 409 outside_service_window.
