Conversaciones y atenciones
Referencia de las rutas que leen y gestionan las atenciones: listar conversaciones con filtros y sincronización incremental, consultar una conversación, recorrer el historial de mensajes por cursor, transferir, cerrar, etiquetar, anotar y abrir una atención en la cola sin hablar con el cliente. Con los objetos conversación y mensaje campo por campo, ejemplos en curl, PHP y Python y los errores de cada ruta.
Visión general
En ChatISP, cada conversación es una atención, con protocolo propio en el formato AAAAMMDD-NNNN. Una conversación cerrada nunca se reabre: cuando el cliente vuelve a escribir, nace otra conversación, con otro protocolo. El conjunto de las conversaciones de un mismo teléfono forma el contacto.
| Ruta | Método | Permiso de la clave | Resumen |
|---|---|---|---|
conversas/listar | GET o POST | Consultar conversaciones | Lista paginada con filtros por estado, departamento, agente, conexión, teléfono, etiqueta y fechas. |
conversas/consultar | GET o POST | Consultar conversaciones | Una conversación (por id, protocolo o teléfono) con etiquetas y los últimos mensajes. |
conversas/mensagens | GET o POST | Consultar conversaciones | Historial completo, paginado por cursor. |
conversas/transferir | POST | Gestionar conversaciones | A un departamento, a un agente o a ambos. |
conversas/encerrar | POST | Gestionar conversaciones | Cierra con motivo, con o sin el mensaje de cierre y la encuesta. |
conversas/etiquetas | POST | Gestionar conversaciones | Define, agrega o quita etiquetas. |
conversas/nota | POST | Gestionar conversaciones | Nota interna en la línea de tiempo (el equipo la ve, el cliente nunca). |
atendimentos/abrir | POST | Abrir atenciones | Coloca el teléfono en la cola de un departamento o con un agente, sin enviar nada al cliente. |
- Visión de toda la empresa. La clave ve todas las conversaciones, de todas las conexiones y departamentos. Las restricciones de visibilidad de los perfiles de agente no se aplican a la API.
- Las notas internas nunca salen. Los mensajes internos del equipo y las líneas del sistema quedan fuera de todas las respuestas y descargas.
- Rastro de la clave. Toda acción de gestión hecha por la API aparece en la línea de tiempo de la conversación con el nombre de la clave (por ejemplo, API: ERP), y el webhook correspondiente se dispara como si la acción se hubiera hecho en el panel.
- Identificar la conversación. Las rutas de una conversación aceptan
conversa_id(oid) oprotocolo. Sin ninguno de ellos: 400identificador_obrigatorio. Inexistente: 404nao_encontrado.
Los ids de departamento, agente, etiqueta y motivo de cierre vienen de las rutas de Catálogos (departamentos/listar, atendentes/listar, etiquetas/listar, motivos/listar).
Estados de la conversación
El campo estado indica quién está a cargo de la conversación ahora:
| Valor | En pantalla | Significado |
|---|---|---|
auto | Automatización | Conducida por un flujo de atención automático (menú, horario, etapas). Sale del flujo cuando este transfiere, activa la IA o cierra. |
ia | Con la IA | Atendida por la IA. Sale de ella cuando la IA transfiere al equipo o cuando un agente la asume. |
fila | En cola | Esperando a un agente, en la cola del departamento (departamento_id) o en la cola general (sin departamento). Es también el estado en que nace una conversación nueva antes de que el flujo o la IA la asuman. |
humano | En atención | Con un agente del equipo (atendente_id). |
encerrada | Cerrada | Atención terminada (por el equipo, por la IA, por inactividad, por la API) o aviso enviado a quien no tenía conversación abierta. No se vuelve a abrir. |
Cualquier estado distinto de encerrada cuenta como conversación abierta (filtro abertas=1 de conversas/listar). Una conversación marcada por el agente como Esperando la respuesta del cliente sigue en humano, con aguardando_cliente: true.
Para seguir los cambios de estado en tiempo real, sin consultar en bucle, use los webhooks conversa_criada, conversa_atribuida, conversa_transferida y conversa_encerrada. Vea Webhooks.
Objeto conversación
Formato usado por todas las rutas que devuelven conversaciones (listados, consulta, gestión, apertura y contactos):
{
"id": 18342,
"protocolo": "20261001-0042",
"conexao_id": 1,
"telefone": "5544999990000",
"nome": "Maria Souza",
"estado": "encerrada",
"aguardando_cliente": false,
"departamento_id": 2,
"departamento": "Soporte técnico",
"atendente_id": 7,
"atendente": "Carlos Lima",
"nao_lidas": 0,
"motivo_encerramento": "Resuelto",
"resumo": {
"titulo": "Sin internet después de un corte de energía",
"texto": "La clienta informó que no tenía internet desde la mañana. El agente identificó la ONU sin señal y programó una visita técnica para la tarde."
},
"criada_em": "2026-10-01T09:12:40-03:00",
"ultima_mensagem_em": "2026-10-01T09:41:03-03:00",
"encerrada_em": "2026-10-01T09:42:15-03:00",
"etiquetas": [
{ "id": 3, "nome": "Visita técnica", "cor": "#2563eb" }
]
}| Campo | Tipo | Descripción |
|---|---|---|
id | entero | Identificador de la conversación (el conversa_id de las demás rutas y de los webhooks). |
protocolo | string | Protocolo de la atención, el mismo que ven el equipo y el cliente. |
conexao_id | entero o null | Número de WhatsApp de la conversación. null cuando la conexión fue eliminada del sistema (el historial se conserva). |
telefone | string | Teléfono del cliente, solo dígitos, con el código del país. |
nome | string | Nombre del cliente: el guardado por el equipo o, sin él, el nombre del perfil de WhatsApp. Vacío cuando se desconoce. |
estado | string | auto, ia, fila, humano o encerrada. Vea Estados de la conversación. |
aguardando_cliente | booleano | true mientras el agente tenga la conversación marcada como esperando la respuesta del cliente (esa espera no cuenta en el tiempo de atención). Vuelve a false cuando el cliente responde o se cancela la espera. |
departamento_id, departamento | entero o null, string | Departamento actual (id y nombre). null y vacío en la cola general. |
atendente_id, atendente | entero o null, string | Agente responsable (id y nombre). En una conversación cerrada, quien la tenía al final. |
nao_lidas | entero | Mensajes del cliente que el responsable aún no abrió en el panel. |
motivo_encerramento | string | Nombre del motivo de cierre elegido. Vacío cuando la conversación está abierta o se cerró sin motivo. |
resumo | objeto o null | Resumen de la atención escrito por la IA después del cierre: titulo (asunto, hasta 70 caracteres) y texto (2 a 4 frases: lo que trajo el cliente, lo que se hizo y el desenlace). Se genera en segundo plano, segundos o minutos después del cierre, y solo cuando el cliente escribió algo en la conversación. null mientras no existe. |
criada_em | fecha y hora | Apertura de la conversación. |
ultima_mensagem_em | fecha y hora o null | Último mensaje intercambiado (del cliente o para él). |
encerrada_em | fecha y hora o null | Cierre; null si está abierta. |
etiquetas | lista | Etiquetas de la conversación, cada una con id, nome y cor, en el orden de la pantalla. Viene en conversas/consultar, en las rutas de gestión y en atendimentos/abrir; no viene en conversas/listar ni en contatos/consultar. |
Fechas en ISO 8601 con la zona horaria del servidor (la zona aparece en chave/info, campo servidor.fuso_horario).
Objeto mensaje
Formato usado en conversas/consultar, conversas/mensagens y en la respuesta de las rutas de envío:
{
"id": 402851,
"conversa_id": 18342,
"direcao": "recebida",
"autor": "cliente",
"autor_nome": "",
"tipo": "audio",
"conteudo": "",
"transcricao": "Buenas tardes, mi internet se cayó de nuevo después de la lluvia.",
"arquivo": {
"nome": "audio.ogg",
"mimetype": "audio/ogg",
"download": "mensagens/arquivo?id=402851"
},
"status": "entregue",
"erro": null,
"responde_a": null,
"editada": false,
"apagada": false,
"criada_em": "2026-10-01T09:13:02-03:00"
}| Campo | Tipo | Descripción |
|---|---|---|
id | entero | Identificador del mensaje. Crece con el tiempo: sirve de cursor en conversas/mensagens. |
conversa_id | entero | Conversación a la que pertenece el mensaje. |
direcao | string | recebida (del cliente) o enviada (del proveedor). |
autor | string | cliente, ia, atendente (escrito por alguien del equipo en el panel o en la aplicación) o sistema (enviado automáticamente: API, flujos, automatización de envíos, mensajes estándar, encuesta). |
autor_nome | string | Nombre del agente cuando autor es atendente; vacío en los demás casos. |
tipo | string | texto, imagem, audio, video, documento, localizacao o contato (tarjeta de contacto compartida). |
conteudo | string | Texto del mensaje. En archivos multimedia, el pie de foto (puede venir vacío). Vacío también cuando el mensaje fue eliminado. |
transcricao | string o null | Transcripción de un audio, cuando la transcripción de audios está activada y ya terminó. null en los demás casos. |
arquivo | objeto o null | Mensajes con archivo: nome (nombre original), mimetype y download, el valor de r que descarga el archivo por la ruta mensagens/arquivo. null en texto y en mensaje eliminado. |
status | string | pendente (registrado, esperando el canal), enviada (aceptado por WhatsApp), entregue (llegó al dispositivo; los recibidos del cliente también aparecen así), lida (el cliente lo leyó, cuando no desactivó la confirmación de lectura) o erro (el canal lo rechazó). |
erro | string o null | Motivo del fallo cuando status es erro. |
responde_a | entero o null | id del mensaje citado (respondido) por este. |
editada | booleano | El mensaje fue editado después de enviado; conteudo trae la versión actual. |
apagada | booleano | El mensaje fue eliminado para todos. El registro se mantiene (para que el historial siga coherente), sin contenido ni archivo. |
criada_em | fecha y hora | Momento del mensaje. |
Listar conversaciones
/api_publica.php?r=conversas/listar
Lista paginada de conversaciones, con filtros combinables (todos se suman con Y). Sin ningún filtro, devuelve todas las conversaciones, de las más recientes a las más antiguas.
Permiso: Consultar conversaciones.
| Parámetro | Tipo | Descripción |
|---|---|---|
estado | lista | Uno o más estados: ia, fila, humano, encerrada, auto. Separados por coma (estado=fila,humano), en formulario (estado[]=fila&estado[]=humano) o lista JSON. Valor desconocido: 400 parametro_invalido con aceitos. |
abertas | booleano | 1: solo conversaciones abiertas (cualquier estado menos encerrada). Acepta 1/0, true/false, sim/nao. |
departamento_id | entero | Solo de ese departamento. 0: solo las que están sin departamento (cola general). |
atendente_id | entero | Solo de ese agente. 0: solo las que están sin agente. |
conexao_id | entero | Solo de ese número de WhatsApp. En un número gemelo, trae las conversaciones de los dos canales. |
telefone | string | Solo de ese cliente, con y sin el noveno dígito. Misma regla de teléfono de las demás rutas (telefone_invalido si no tiene sentido). |
etiqueta_id | entero | Solo las que tienen esa etiqueta. |
criadas_desde, criadas_ate | fecha | Apertura dentro del período. Solo la fecha (2026-10-01) vale desde el inicio del día, en criadas_desde, hasta el fin del día, en criadas_ate. |
encerradas_desde | fecha | Cerradas a partir de ese momento. |
atualizadas_desde | fecha | Conversaciones abiertas, cerradas o con mensaje nuevo a partir de ese momento. Es el filtro de la sincronización incremental. |
ordem | string | recentes (predeterminado: última actividad, de la más nueva a la más antigua), antigas (última actividad, de la más antigua a la más nueva), criacao (orden de apertura) o criacao_desc (apertura, de la más nueva a la más antigua). La última actividad es el último mensaje o, sin mensajes, la apertura. |
pagina, limite | entero | Paginación: pagina a partir de 1; limite de 1 a 100, predeterminado 50. |
Las fechas aceptan ISO 8601 (2026-10-01T08:00:00-03:00), AAAA-MM-DD o AAAA-MM-DD HH:MM:SS; sin zona horaria, valen en la zona del servidor. Fecha que no tiene sentido: 400 data_invalida con campo.
# cola del departamento 2 ahora, primero las que esperan hace más tiempo curl -s "https://atendimento.seuprovedor.com.br/api_publica.php?r=conversas/listar&estado=fila&departamento_id=2&ordem=antigas" \ -H "Authorization: Bearer $CHATISP_CHAVE" # cerradas ayer con la etiqueta 3, página 2 curl -s "https://atendimento.seuprovedor.com.br/api_publica.php?r=conversas/listar&estado=encerrada&etiqueta_id=3&encerradas_desde=2026-09-30&criadas_ate=2026-09-30&pagina=2" \ -H "Authorization: Bearer $CHATISP_CHAVE"
<?php // recorre todas las páginas de las conversaciones abiertas $pagina = 1; do { $url = 'https://atendimento.seuprovedor.com.br/api_publica.php?' . http_build_query([ 'r' => 'conversas/listar', 'abertas' => 1, 'limite' => 100, 'pagina' => $pagina, ]); $ch = curl_init($url); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('CHATISP_CHAVE')], ]); $r = json_decode(curl_exec($ch), true); curl_close($ch); foreach ($r['conversas'] as $c) { echo $c['protocolo'], ' ', $c['estado'], ' ', $c['atendente'], PHP_EOL; } $pagina++; } while ($pagina <= $r['paginacao']['paginas']);
import os, requests r = requests.get("https://atendimento.seuprovedor.com.br/api_publica.php", params={ "r": "conversas/listar", "estado": "fila,humano", "atendente_id": 7, "limite": 100, }, headers={"Authorization": "Bearer " + os.environ["CHATISP_CHAVE"]}, timeout=30) dados = r.json() print(dados["paginacao"]["total"], "conversaciones con el agente 7") for c in dados["conversas"]: print(c["protocolo"], c["nome"], c["nao_lidas"])
{
"ok": true,
"conversas": [
{
"id": 18360,
"protocolo": "20261001-0055",
"conexao_id": 1,
"telefone": "5544988887777",
"nome": "João Pereira",
"estado": "fila",
"aguardando_cliente": false,
"departamento_id": 2,
"departamento": "Finanzas",
"atendente_id": null,
"atendente": "",
"nao_lidas": 2,
"motivo_encerramento": "",
"resumo": null,
"criada_em": "2026-10-01T10:31:08-03:00",
"ultima_mensagem_em": "2026-10-01T10:31:40-03:00",
"encerrada_em": null
}
],
"paginacao": { "pagina": 1, "limite": 50, "total": 1, "paginas": 1 }
}Errores específicos: parametro_invalido (estado, orden, límite o página fuera de lo aceptado, con campo), data_invalida, telefone_invalido.
Sincronización incremental
Para mantener una copia de las conversaciones en su CRM, BI o data warehouse sin descargar todo de nuevo en cada ronda, use atualizadas_desde. Trae las conversaciones que, a partir de la marca, fueron abiertas, cerradas o tuvieron un mensaje nuevo (del cliente o para él).
- En la primera carga, liste todo (sin filtro de fecha,
ordem=criacao) y guarde las conversaciones porid. - Antes de cada ronda, anote la hora actual del servidor (campo
servidor.agoradechave/info, o su reloj sincronizado). Esa será la marca de la próxima ronda. - Llame a
conversas/listarconatualizadas_desdeigual a la marca de la ronda anterior menos un pequeño margen (uno o dos minutos),ordem=antigasylimite=100, recorriendo las páginas. - Grabe cada conversación por
id(actualice si ya existe, inserte si es nueva). El margen hace que algunas conversaciones vengan repetidas; grabar poridlo vuelve inofensivo. - Para los mensajes de cada conversación modificada, use
conversas/mensagenscondepois_deigual al últimoidde mensaje que ya tiene.
atualizadas_desde mira la apertura, el cierre y el último mensaje. Los cambios que no involucran mensajes (transferencia, cambio de etiquetas, el resumen de la IA que llega después del cierre) no traen la conversación de vuelta. Para ellos, use los webhooks conversa_transferida y conversa_atribuida, y consulte de nuevo la conversación cerrada algunos minutos después para obtener el resumo.
import os, requests from datetime import datetime, timedelta URL = "https://atendimento.seuprovedor.com.br/api_publica.php" CAB = {"Authorization": "Bearer " + os.environ["CHATISP_CHAVE"]} def rodada(marco_anterior): agora = requests.get(URL, params={"r": "chave/info"}, headers=CAB, timeout=30).json()["servidor"]["agora"] desde = (datetime.fromisoformat(marco_anterior) - timedelta(minutes=2)).isoformat() pagina = 1 while True: r = requests.get(URL, headers=CAB, timeout=30, params={ "r": "conversas/listar", "atualizadas_desde": desde, "ordem": "antigas", "limite": 100, "pagina": pagina}).json() for c in r["conversas"]: gravar_conversa(c) # upsert por c["id"] en su base de datos if pagina >= r["paginacao"]["paginas"]: break pagina += 1 return agora # marca de la próxima ronda
Consultar conversación
/api_publica.php?r=conversas/consultar
Devuelve una conversación completa (con etiquetas) y sus últimos mensajes, en orden cronológico. Sirve para mostrar en el CRM el avance de un protocolo, verificar si el cliente respondió a un aviso o buscar el contexto después de un webhook.
Permiso: Consultar conversaciones.
| Parámetro | Tipo | Obligatorio | Descripción |
|---|---|---|---|
id o conversa_id | entero | Uno de los tres | Identificador de la conversación. |
protocolo | string | Uno de los tres | Protocolo, por ejemplo 20261001-0042. |
telefone | string | Uno de los tres | Devuelve la conversación más reciente de ese teléfono, en cualquier número de WhatsApp y en cualquier estado (incluso cerrada), con y sin el noveno dígito. |
mensagens | entero | No | Cuántos mensajes traer, de los más recientes: de 0 a 100, predeterminado 20. Use 0 para solo la conversación. Los valores fuera del rango se ajustan al límite más cercano. |
Con más de un identificador, vale el primero en el orden conversa_id/id, protocolo, telefone. Para más de 100 mensajes, o para paginar el historial, use conversas/mensagens.
# por protocolo, con los 3 últimos mensajes curl -s "https://atendimento.seuprovedor.com.br/api_publica.php?r=conversas/consultar&protocolo=20261001-0042&mensagens=3" \ -H "Authorization: Bearer $CHATISP_CHAVE" # la conversación más reciente de un teléfono, sin mensajes curl -s "https://atendimento.seuprovedor.com.br/api_publica.php?r=conversas/consultar&telefone=44999990000&mensagens=0" \ -H "Authorization: Bearer $CHATISP_CHAVE"
<?php $url = 'https://atendimento.seuprovedor.com.br/api_publica.php?' . http_build_query([ 'r' => 'conversas/consultar', 'protocolo' => '20261001-0042', 'mensagens' => 3, ]); $ch = curl_init($url); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('CHATISP_CHAVE')], ]); $r = json_decode(curl_exec($ch), true); curl_close($ch); echo $r['conversa']['estado'], ' con ', $r['conversa']['atendente'] ?: 'nadie', PHP_EOL; foreach ($r['mensagens'] as $m) { echo $m['criada_em'], ' [', $m['autor'], '] ', $m['conteudo'] ?: ($m['transcricao'] ?? ''), PHP_EOL; }
import os, requests r = requests.get("https://atendimento.seuprovedor.com.br/api_publica.php", params={ "r": "conversas/consultar", "protocolo": "20261001-0042", "mensagens": 3, }, headers={"Authorization": "Bearer " + os.environ["CHATISP_CHAVE"]}, timeout=30) if r.status_code == 404: print("El protocolo no existe") else: for m in r.json()["mensagens"]: print(m["criada_em"], m["autor"], m["conteudo"])
{
"ok": true,
"conversa": {
"id": 18342,
"protocolo": "20261001-0042",
"conexao_id": 1,
"telefone": "5544999990000",
"nome": "Maria Souza",
"estado": "humano",
"aguardando_cliente": false,
"departamento_id": 1,
"departamento": "Soporte técnico",
"atendente_id": 7,
"atendente": "Carlos Lima",
"nao_lidas": 0,
"motivo_encerramento": "",
"resumo": null,
"criada_em": "2026-10-01T09:12:40-03:00",
"ultima_mensagem_em": "2026-10-01T09:20:03-03:00",
"encerrada_em": null,
"etiquetas": []
},
"mensagens": [
{
"id": 402850, "conversa_id": 18342, "direcao": "recebida", "autor": "cliente", "autor_nome": "",
"tipo": "texto", "conteudo": "Mi internet está caída desde temprano", "transcricao": null, "arquivo": null,
"status": "entregue", "erro": null, "responde_a": null, "editada": false, "apagada": false,
"criada_em": "2026-10-01T09:12:40-03:00"
},
{
"id": 402852, "conversa_id": 18342, "direcao": "enviada", "autor": "ia", "autor_nome": "",
"tipo": "texto", "conteudo": "¡Hola, Maria! Voy a verificar su conexión ahora mismo.", "transcricao": null, "arquivo": null,
"status": "lida", "erro": null, "responde_a": null, "editada": false, "apagada": false,
"criada_em": "2026-10-01T09:12:52-03:00"
},
{
"id": 402866, "conversa_id": 18342, "direcao": "enviada", "autor": "atendente", "autor_nome": "Carlos Lima",
"tipo": "texto", "conteudo": "Maria, identifiqué la falla. Un técnico irá a su domicilio hoy por la tarde.", "transcricao": null, "arquivo": null,
"status": "entregue", "erro": null, "responde_a": 402850, "editada": false, "apagada": false,
"criada_em": "2026-10-01T09:20:03-03:00"
}
]
}Errores específicos: identificador_obrigatorio (ninguno de los tres identificadores), telefone_invalido, nao_encontrado (404: id o protocolo inexistente, o teléfono sin ninguna conversación).
Historial de mensajes
/api_publica.php?r=conversas/mensagens
Recorre el historial completo de una conversación, en páginas, por cursor de id de mensaje. El cursor no salta ni repite mensajes cuando llegan mensajes nuevos en medio de la lectura, a diferencia de una paginación numerada. Los mensajes vienen siempre en orden cronológico (del más antiguo al más nuevo) y sin las notas internas.
Permiso: Consultar conversaciones.
| Parámetro | Tipo | Obligatorio | Descripción |
|---|---|---|---|
conversa_id (o id) o protocolo | Sí | La conversación. | |
limite | entero | No | Mensajes por página, de 1 a 100, predeterminado 50. |
antes_de | entero | No | Retroceder: trae los mensajes anteriores a ese id (los más cercanos a él). |
depois_de | entero | No | Buscar lo que llegó: trae los mensajes posteriores a ese id, a partir del más antiguo. Tiene prioridad sobre antes_de. |
| Campo de la respuesta | Descripción |
|---|---|
conversa_id | La conversación leída. |
mensagens | Lista de mensajes, en orden cronológico. |
tem_mais | true cuando hay más mensajes en la dirección pedida: más antiguos (sin cursor o con antes_de) o más nuevos más allá de esta página (con depois_de). |
cursor.antes_de | id del primer mensaje de la página: páselo en antes_de para la página anterior. |
cursor.depois_de | id del último mensaje de la página: páselo en depois_de para buscar lo que llegue después. En una página vacía con depois_de, repite el valor enviado, para que usted continúe desde el mismo punto. |
Dos formas de usarlo
- Leer todo de atrás hacia adelante (exportar el historial): primera llamada sin cursor (los más recientes); mientras
tem_maisseatrue, llame de nuevo conantes_deigual acursor.antes_de. - Seguir lo que llega (reflejar la conversación en su sistema): guarde el
cursor.depois_dey, la próxima vez, llame condepois_de; mientrastem_maisseatrue, continúe con el nuevocursor.depois_de.
# los 50 más recientes curl -s "https://atendimento.seuprovedor.com.br/api_publica.php?r=conversas/mensagens&conversa_id=18342" \ -H "Authorization: Bearer $CHATISP_CHAVE" # la página anterior curl -s "https://atendimento.seuprovedor.com.br/api_publica.php?r=conversas/mensagens&conversa_id=18342&antes_de=402850" \ -H "Authorization: Bearer $CHATISP_CHAVE" # lo que llegó después del mensaje 402866 curl -s "https://atendimento.seuprovedor.com.br/api_publica.php?r=conversas/mensagens&protocolo=20261001-0042&depois_de=402866" \ -H "Authorization: Bearer $CHATISP_CHAVE"
<?php // exporta el historial completo, del más antiguo al más nuevo function chatisp(array $params): array { $ch = curl_init('https://atendimento.seuprovedor.com.br/api_publica.php?' . http_build_query($params)); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('CHATISP_CHAVE')], ]); $r = json_decode(curl_exec($ch), true); curl_close($ch); return $r; } $historico = []; $params = ['r' => 'conversas/mensagens', 'conversa_id' => 18342, 'limite' => 100]; do { $r = chatisp($params); $historico = array_merge($r['mensagens'], $historico); $params['antes_de'] = $r['cursor']['antes_de']; } while ($r['tem_mais']);
import os, requests URL = "https://atendimento.seuprovedor.com.br/api_publica.php" CAB = {"Authorization": "Bearer " + os.environ["CHATISP_CHAVE"]} def novas_mensagens(conversa_id, ultimo_id): """Busca todo lo que llegó después de ultimo_id y devuelve el nuevo cursor.""" while True: r = requests.get(URL, headers=CAB, timeout=30, params={ "r": "conversas/mensagens", "conversa_id": conversa_id, "depois_de": ultimo_id, "limite": 100}).json() for m in r["mensagens"]: print(m["autor"], m["conteudo"]) ultimo_id = r["cursor"]["depois_de"] if not r["tem_mais"]: return ultimo_id
{
"ok": true,
"conversa_id": 18342,
"mensagens": [
{ "id": 402852, "conversa_id": 18342, "direcao": "enviada", "autor": "ia", "...": "..." },
{ "id": 402866, "conversa_id": 18342, "direcao": "enviada", "autor": "atendente", "...": "..." }
],
"tem_mais": true,
"cursor": { "antes_de": 402852, "depois_de": 402866 }
}Errores específicos: identificador_obrigatorio, nao_encontrado, parametro_invalido (limite fuera de 1 a 100, con maximo; cursor que no es un número entero).
Transferir conversación
/api_publica.php?r=conversas/transferir
Lleva una conversación abierta a un departamento, a un agente o a ambos, exactamente como el botón Transferir del panel. Ejemplos: el ERP detecta que el cliente está en mora y envía la conversación a Finanzas; el CRM dirige a los clientes corporativos a su ejecutivo de cuenta.
Permiso: Gestionar conversaciones.
| Parámetro | Tipo | Obligatorio | Descripción |
|---|---|---|---|
conversa_id (o id) o protocolo | Sí | La conversación. Debe estar abierta. | |
departamento_id | entero | Al menos uno de los dos | Departamento de destino, activo. |
atendente_id | entero | Al menos uno de los dos | Agente de destino, activo. |
motivo | string | No | Texto libre, hasta 300 caracteres, que aparece en la línea de tiempo junto con el nombre de la clave (por ejemplo, API: ERP (cliente en mora)). |
Qué sucede
| Usted envía | Resultado |
|---|---|
Solo departamento_id | La conversación vuelve a la cola de ese departamento, sin agente. La distribución automática del departamento intenta entregarla en el momento; si nadie puede asumirla, el equipo recibe el aviso de nueva conversación en la cola. |
Solo atendente_id | La conversación va directo al agente (humano), en el departamento en que ya estaba. El agente recibe el aviso por la aplicación y, si tiene las alertas por WhatsApp activadas, por WhatsApp. |
| Ambos | La conversación va al agente y pasa a figurar en ese departamento. |
- En todos los casos la conversación sale de la automatización: si estaba con la IA o en un flujo, estos dejan de responder.
- Se deshace el uso compartido de la conversación con otros agentes; quien la recibe decide con quién compartirla.
- La API no verifica si el agente forma parte del departamento: la combinación es responsabilidad de la integración.
- El cliente no recibe ningún mensaje de la transferencia. La única excepción posible es el Aviso de tiempo de espera, cuando está activado en Configuración y la conversación entra en la cola con todos los agentes ocupados.
- Se dispara el webhook
conversa_transferida(yconversa_atribuidacuando la distribución automática entrega la conversación a alguien).
curl -s -X POST "https://atendimento.seuprovedor.com.br/api_publica.php?r=conversas/transferir" \
-H "Authorization: Bearer $CHATISP_CHAVE" \
-H "Content-Type: application/json" \
-d '{"protocolo": "20261001-0042", "departamento_id": 2, "motivo": "cliente en mora"}'<?php $ch = curl_init('https://atendimento.seuprovedor.com.br/api_publica.php?r=conversas/transferir'); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('CHATISP_CHAVE'), 'Content-Type: application/json'], CURLOPT_POSTFIELDS => json_encode(['conversa_id' => 18342, 'atendente_id' => 7, 'motivo' => 'cliente corporativo']), ]); $r = json_decode(curl_exec($ch), true); curl_close($ch); echo $r['conversa']['estado'], ' con ', $r['conversa']['atendente'], PHP_EOL;
import os, requests r = requests.post("https://atendimento.seuprovedor.com.br/api_publica.php", params={"r": "conversas/transferir"}, headers={"Authorization": "Bearer " + os.environ["CHATISP_CHAVE"]}, json={"conversa_id": 18342, "departamento_id": 2, "atendente_id": 9}, timeout=30) print(r.status_code, r.json()["conversa"]["estado"])
La respuesta trae la conversación ya actualizada, con etiquetas:
{
"ok": true,
"conversa": {
"id": 18342,
"protocolo": "20261001-0042",
"estado": "fila",
"departamento_id": 2,
"departamento": "Finanzas",
"atendente_id": null,
"atendente": "",
"...": "demás campos del objeto conversación",
"etiquetas": []
}
}Si la distribución automática entrega la conversación en el momento, el estado ya viene como humano, con el agente elegido por ella.
Errores específicos: parametro_obrigatorio (sin departamento ni agente; campo igual a departamento_id|atendente_id), departamento_invalido, atendente_invalido (inexistente, inactivo o eliminado), conversa_encerrada (409), identificador_obrigatorio, nao_encontrado.
Cerrar conversación
/api_publica.php?r=conversas/encerrar
Cierra una conversación abierta, como el botón Cerrar del panel. Ejemplo: el sistema de órdenes de servicio cierra la OS y cierra la atención de WhatsApp con el motivo correcto.
Permiso: Gestionar conversaciones.
| Parámetro | Tipo | Obligatorio | Descripción |
|---|---|---|---|
conversa_id (o id) o protocolo | Sí | La conversación. Debe estar abierta. | |
motivo_id | entero | No | Motivo de cierre activo (los motivos se registran en Catálogos y se listan con motivos/listar). Sin él, la conversación queda sin motivo y la línea de tiempo registra el nombre de la clave. |
enviar_mensagem | booleano | No | Predeterminado true. Con false, no se envía nada al cliente en el cierre. |
Qué sucede con el cliente
Con enviar_mensagem verdadero (predeterminado), el cierre por la API hace lo mismo que por el panel:
- Mensaje de atención cerrada: se envía el texto configurado en Configuración (con
{protocolo}y{nome}completados), siempre que haya habido atención real: el cliente participó en la conversación, o la IA o un agente habló con él. Con el campo vacío en Configuración, no se envía nada. - Encuesta de satisfacción: si Enviar la encuesta al cerrar conversaciones atendidas está activado (vea Encuesta de satisfacción), la pregunta se envía cuando la IA o un agente respondió al cliente en la conversación. La nota que el cliente responda entra en los informes.
Con enviar_mensagem=false, no sale ninguno de los dos. Úselo, por ejemplo, al cerrar en lote conversaciones antiguas o de prueba.
Qué sucede internamente
- El estado pasa a
encerrada, con el motivo; la conversación sale del flujo y de la IA; una espera de respuesta abierta se cierra. - Si el cliente escribió algo, la IA escribe el
resumode la atención en segundo plano (consulte la conversación de nuevo algunos minutos después). - Las órdenes de servicio abiertas a partir de esa atención tienen el resumen rehecho cubriendo la conversación completa.
- Se dispara el webhook
conversa_encerrada, conmotivoigual al nombre del motivo elegido (o a la identificación de la clave, sin motivo).
# cierra con motivo, enviando el mensaje de cierre y la encuesta curl -s -X POST "https://atendimento.seuprovedor.com.br/api_publica.php" \ -H "Authorization: Bearer $CHATISP_CHAVE" \ -d "r=conversas/encerrar" -d "protocolo=20261001-0042" -d "motivo_id=1" # cierra en silencio curl -s -X POST "https://atendimento.seuprovedor.com.br/api_publica.php" \ -H "Authorization: Bearer $CHATISP_CHAVE" \ -d "r=conversas/encerrar" -d "conversa_id=18342" -d "enviar_mensagem=0"
<?php $ch = curl_init('https://atendimento.seuprovedor.com.br/api_publica.php?r=conversas/encerrar'); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('CHATISP_CHAVE'), 'Content-Type: application/json'], CURLOPT_POSTFIELDS => json_encode(['protocolo' => '20261001-0042', 'motivo_id' => 1]), ]); $r = json_decode(curl_exec($ch), true); $status = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); if ($status === 409) { // conversa_encerrada: ya estaba cerrada; trátelo como éxito }
import os, requests r = requests.post("https://atendimento.seuprovedor.com.br/api_publica.php", params={"r": "conversas/encerrar"}, headers={"Authorization": "Bearer " + os.environ["CHATISP_CHAVE"]}, json={"conversa_id": 18342, "motivo_id": 1, "enviar_mensagem": False}, timeout=30) c = r.json()["conversa"] print(c["estado"], c["motivo_encerramento"], c["encerrada_em"])
{
"ok": true,
"conversa": {
"id": 18342,
"protocolo": "20261001-0042",
"estado": "encerrada",
"motivo_encerramento": "Resuelto",
"resumo": null,
"encerrada_em": "2026-10-01T11:05:12-03:00",
"...": "demás campos del objeto conversación"
}
}Errores específicos: motivo_invalido (inexistente o inactivo), conversa_encerrada (409: ya estaba cerrada; en reprocesamientos, trátelo como éxito), parametro_invalido (enviar_mensagem con un valor que no es booleano), identificador_obrigatorio, nao_encontrado.
Etiquetas de la conversación
/api_publica.php?r=conversas/etiquetas
Cambia las etiquetas de una conversación (abierta o cerrada). Las etiquetas se registran en Catálogos y se listan con etiquetas/listar. Ejemplos: marcar VIP cuando el ERP identifica un plan empresarial; marcar En mora para que Finanzas filtre en el kanban.
Permiso: Gestionar conversaciones.
| Parámetro | Tipo | Descripción |
|---|---|---|
conversa_id (o id) o protocolo | La conversación (obligatorio). | |
etiquetas | lista de ids | Reemplaza todas las etiquetas por la lista. Una lista vacía ([] en JSON o etiquetas= en formulario) las quita todas. |
adicionar | lista de ids | Agrega esas etiquetas, sin tocar las demás. |
remover | lista de ids | Quita esas etiquetas, sin tocar las demás. |
Envíe al menos uno de los tres. Pueden venir juntos: la API parte de etiquetas (o de las actuales, sin él), suma adicionar y quita remover. Las listas aceptan JSON ([1, 3]), formulario (adicionar[]=1&adicionar[]=3) o texto separado por comas (adicionar=1,3). Los ids repetidos se ignoran.
# agrega las etiquetas 1 y 3 y quita la 2 curl -s -X POST "https://atendimento.seuprovedor.com.br/api_publica.php?r=conversas/etiquetas" \ -H "Authorization: Bearer $CHATISP_CHAVE" \ -H "Content-Type: application/json" \ -d '{"conversa_id": 18342, "adicionar": [1, 3], "remover": [2]}' # quita todas curl -s -X POST "https://atendimento.seuprovedor.com.br/api_publica.php" \ -H "Authorization: Bearer $CHATISP_CHAVE" \ -d "r=conversas/etiquetas" -d "conversa_id=18342" -d "etiquetas="
<?php $ch = curl_init('https://atendimento.seuprovedor.com.br/api_publica.php?r=conversas/etiquetas'); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('CHATISP_CHAVE'), 'Content-Type: application/json'], CURLOPT_POSTFIELDS => json_encode(['protocolo' => '20261001-0042', 'etiquetas' => [1]]), ]); $r = json_decode(curl_exec($ch), true); curl_close($ch); echo implode(', ', array_column($r['conversa']['etiquetas'], 'nome')), PHP_EOL;
import os, requests r = requests.post("https://atendimento.seuprovedor.com.br/api_publica.php", params={"r": "conversas/etiquetas"}, headers={"Authorization": "Bearer " + os.environ["CHATISP_CHAVE"]}, json={"conversa_id": 18342, "adicionar": [3]}, timeout=30) print([e["nome"] for e in r.json()["conversa"]["etiquetas"]])
{
"ok": true,
"conversa": {
"id": 18342,
"protocolo": "20261001-0042",
"...": "demás campos del objeto conversación",
"etiquetas": [
{ "id": 1, "nome": "VIP", "cor": "#f59e0b" },
{ "id": 3, "nome": "Visita técnica", "cor": "#2563eb" }
]
}
}{"ok": false, "error": "etiqueta_invalida", "campo": "adicionar"}Errores específicos: parametro_obrigatorio (ninguno de los tres; campo igual a etiquetas|adicionar|remover), etiqueta_invalida (id inexistente; no se modifica nada), parametro_invalido, identificador_obrigatorio, nao_encontrado.
Nota interna
/api_publica.php?r=conversas/nota
Graba una nota en la línea de tiempo de la conversación, visible solo para el equipo. Ejemplos: el NOC avisa que hay un corte de fibra en la zona del cliente; el ERP registra que el pago se acreditó durante la atención.
Permiso: Gestionar conversaciones.
| Parámetro | Tipo | Obligatorio | Descripción |
|---|---|---|---|
conversa_id (o id) o protocolo | Sí | La conversación (abierta o cerrada). | |
texto | string | Sí | De 1 a 900 caracteres. |
- La nota aparece en el chat como línea del sistema, con el nombre de la clave al frente: API (ERP): Pago acreditado a las 10:12.
- Nunca se envía al cliente, no entra en el historial que lee la IA y no aparece en los informes.
- Tampoco vuelve por las rutas de lectura de la API (que nunca devuelven contenido interno): guarde el
nota_idsi necesita hacer referencia a ella.
curl -s -X POST "https://atendimento.seuprovedor.com.br/api_publica.php?r=conversas/nota" \
-H "Authorization: Bearer $CHATISP_CHAVE" \
-H "Content-Type: application/json" \
-d '{"protocolo": "20261001-0042", "texto": "Corte de fibra en el barrio Jardim Alvorada, previsión 14:00."}'<?php $ch = curl_init('https://atendimento.seuprovedor.com.br/api_publica.php?r=conversas/nota'); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('CHATISP_CHAVE'), 'Content-Type: application/json'], CURLOPT_POSTFIELDS => json_encode(['conversa_id' => 18342, 'texto' => 'Pago acreditado a las 10:12.']), ]); $r = json_decode(curl_exec($ch), true); curl_close($ch);
import os, requests r = requests.post("https://atendimento.seuprovedor.com.br/api_publica.php", params={"r": "conversas/nota"}, headers={"Authorization": "Bearer " + os.environ["CHATISP_CHAVE"]}, json={"conversa_id": 18342, "texto": "Pago acreditado a las 10:12."}, timeout=30) print(r.json())
{"ok": true, "nota_id": 402990, "conversa_id": 18342}Errores específicos: texto_invalido (vacío o de más de 900 caracteres), identificador_obrigatorio, nao_encontrado.
Abrir atención
/api_publica.php?r=atendimentos/abrir
Coloca un teléfono en la cola de atención (o directo con un agente) sin enviar ningún mensaje al cliente. Es el camino para cuando la solicitud nace fuera de WhatsApp: formulario de contacto del sitio web, portal del suscriptor, alarma del sistema de monitoreo, devolución de llamada programada. El equipo ve la atención en el panel y habla con el cliente desde allí.
Permiso: Abrir atenciones.
| Parámetro | Tipo | Obligatorio | Descripción |
|---|---|---|---|
telefone | string | Sí | Teléfono del cliente (misma regla de las rutas de envío). |
conexao_id | entero | Condicional | Número de WhatsApp por el que el equipo atenderá. Obligatorio cuando hay más de un número conectado. Se omite cuando la clave tiene Número de origen; si viene en la llamada, vale el de la llamada. |
departamento_id | entero | No | Departamento de destino, activo. Sin él, vale el departamento en que ya está la conversación (en una conversación nueva, el Departamento de las conversaciones nuevas de Configuración, o la cola general). |
atendente_id | entero | No | Entrega la conversación directo a ese agente, activo. |
nota | string | No | Hasta 900 caracteres. Se convierte en una nota interna en la conversación (API (Portal): El cliente pidió que lo contacten sobre un cambio de domicilio.), para que el equipo sepa de qué se trata antes de hablar con el cliente. |
nome | string | No | Nombre del cliente para una conversación nueva (hasta 190 caracteres). Prevalece el nombre guardado por el equipo para el contacto. |
Cómo se comporta la llamada
- ChatISP busca una conversación abierta de ese teléfono en el número de WhatsApp (por los dos canales de un número gemelo, con y sin el noveno dígito). Si no la hay, crea una nueva, en la cola, con protocolo propio (se dispara el webhook
conversa_criada). - La
nota, si se envió, se graba en la línea de tiempo, junto con el registro de la clave. - Con
atendente_id: la conversación va directo a ese agente (estadohumano), sea cual sea el estado en que estaba, y también aldepartamento_id, si se envió. Sale de la IA o del flujo, si estaba en ellos. El agente recibe el aviso. - Sin
atendente_id, con la conversación en la cola: va a la cola deldepartamento_idpedido (o sigue en la del departamento actual), y la distribución automática intenta entregarla en el momento. Si hay un agente disponible, ya sale comohumano; si no, queda enfilay el equipo recibe el aviso. - Sin
atendente_id, con la conversación con la IA, en un flujo o ya con un agente: no cambia nada, ni el departamento. La API no toma una conversación que ya está siendo conducida.
No se envía nada al cliente. Solo recibe algo cuando el equipo escriba. La única excepción posible es el Aviso de tiempo de espera, si está activado en Configuración y la conversación queda en la cola con todos los agentes ocupados. Para abrir y enviar un mensaje enseguida, llame a mensagens/enviar justo después con el conversa_id devuelto: como la conversación ya está abierta, no se trata como aviso y sigue abierta.
curl -s -X POST "https://atendimento.seuprovedor.com.br/api_publica.php?r=atendimentos/abrir" \
-H "Authorization: Bearer $CHATISP_CHAVE" \
-H "Content-Type: application/json" \
-d '{
"telefone": "5544988887777",
"nome": "João Pereira",
"departamento_id": 2,
"nota": "Pidió por el portal renegociar la factura de septiembre."
}'<?php $ch = curl_init('https://atendimento.seuprovedor.com.br/api_publica.php'); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('CHATISP_CHAVE')], CURLOPT_POSTFIELDS => http_build_query([ 'r' => 'atendimentos/abrir', 'telefone' => '5544988887777', 'nome' => 'João Pereira', 'departamento_id' => 2, 'nota' => 'Pidió por el portal renegociar la factura de septiembre.', ]), ]); $r = json_decode(curl_exec($ch), true); curl_close($ch); echo $r['ja_existia'] ? 'Ya había una atención: ' : 'Atención abierta: ', $r['protocolo'], PHP_EOL;
import os, requests r = requests.post("https://atendimento.seuprovedor.com.br/api_publica.php", params={"r": "atendimentos/abrir"}, headers={"Authorization": "Bearer " + os.environ["CHATISP_CHAVE"]}, json={ "telefone": "5544988887777", "atendente_id": 7, "nota": "Devolución de llamada programada por el NOC.", }, timeout=30) corpo = r.json() print(corpo["protocolo"], corpo["estado"], corpo["ja_existia"])
{
"ok": true,
"conversa_id": 18361,
"protocolo": "20261001-0056",
"estado": "fila",
"ja_existia": false,
"conversa": {
"id": 18361,
"protocolo": "20261001-0056",
"conexao_id": 1,
"telefone": "5544988887777",
"nome": "João Pereira",
"estado": "fila",
"departamento_id": 2,
"departamento": "Finanzas",
"...": "demás campos del objeto conversación",
"etiquetas": []
}
}| Campo | Descripción |
|---|---|
conversa_id, protocolo | Identificación de la atención. |
estado | Estado después de la llamada: fila (esperando agente), humano (con el agente pedido o entregada por la distribución automática) o, en una conversación que ya estaba siendo conducida, el estado en que estaba (ia, auto, humano). |
ja_existia | true cuando el teléfono ya tenía una conversación abierta en ese número (en cualquier estado); false cuando esta llamada creó la atención. |
conversa | El objeto conversación completo, con etiquetas. |
Llamada idempotente: llamar de nuevo para el mismo teléfono mientras la atención está abierta no crea un duplicado; devuelve la misma conversación con ja_existia: true (y graba la nueva nota, si viene). Esto hace seguro repetir la llamada después de un error de red.
Errores específicos: telefone_invalido, conexao_invalida, sem_conexao, conexao_id_obrigatorio, conexao_desconectada, departamento_invalido, atendente_invalido, texto_invalido (nota de más de 900 caracteres, con campo igual a nota).
Errores por ruta
Además de los errores generales (rota, autenticacao, chave_invalida, plano, cota_excedida, permissao, json_invalido, parametro_invalido), descritos en Códigos de error:
error | HTTP | Rutas | Causa |
|---|---|---|---|
metodo | 405 | transferir, encerrar, etiquetas, nota, abrir | Llamada con GET en una ruta de escritura (esperado: POST). |
identificador_obrigatorio | 400 | todas menos listar y abrir | Faltó conversa_id/id o protocolo (o telefone, en consultar). |
nao_encontrado | 404 | todas menos listar y abrir | La conversación no existe. |
parametro_obrigatorio | 400 | transferir, etiquetas | Faltó el destino o la lista; campo indica cuál. |
data_invalida | 400 | listar | Fecha que no se pudo interpretar; campo indica cuál. |
telefone_invalido | 400 | listar, consultar, abrir | Teléfono que no tiene sentido. |
conversa_encerrada | 409 | transferir, encerrar | La conversación ya está cerrada. |
departamento_invalido | 400 | transferir, abrir | Departamento inexistente o inactivo. |
atendente_invalido | 400 | transferir, abrir | Agente inexistente, inactivo o eliminado. |
motivo_invalido | 400 | encerrar | Motivo inexistente o inactivo. |
etiqueta_invalida | 400 | etiquetas | Etiqueta inexistente; campo indica en qué lista. |
texto_invalido | 400 | nota, abrir | Texto de la nota vacío o de más de 900 caracteres. |
conexao_invalida, sem_conexao, conexao_id_obrigatorio | 400 | abrir | Elección del número de WhatsApp (vea A quién va el mensaje). |
conexao_desconectada | 409 | abrir | El número de WhatsApp elegido está desconectado. |