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.

RutaMétodoPermiso de la claveResumen
conversas/listarGET o POSTConsultar conversacionesLista paginada con filtros por estado, departamento, agente, conexión, teléfono, etiqueta y fechas.
conversas/consultarGET o POSTConsultar conversacionesUna conversación (por id, protocolo o teléfono) con etiquetas y los últimos mensajes.
conversas/mensagensGET o POSTConsultar conversacionesHistorial completo, paginado por cursor.
conversas/transferirPOSTGestionar conversacionesA un departamento, a un agente o a ambos.
conversas/encerrarPOSTGestionar conversacionesCierra con motivo, con o sin el mensaje de cierre y la encuesta.
conversas/etiquetasPOSTGestionar conversacionesDefine, agrega o quita etiquetas.
conversas/notaPOSTGestionar conversacionesNota interna en la línea de tiempo (el equipo la ve, el cliente nunca).
atendimentos/abrirPOSTAbrir atencionesColoca 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 (o id) o protocolo. Sin ninguno de ellos: 400 identificador_obrigatorio. Inexistente: 404 nao_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:

ValorEn pantallaSignificado
autoAutomatizaciónConducida por un flujo de atención automático (menú, horario, etapas). Sale del flujo cuando este transfiere, activa la IA o cierra.
iaCon la IAAtendida por la IA. Sale de ella cuando la IA transfiere al equipo o cuando un agente la asume.
filaEn colaEsperando 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.
humanoEn atenciónCon un agente del equipo (atendente_id).
encerradaCerradaAtenció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):

conversación
{
  "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" }
  ]
}
CampoTipoDescripción
identeroIdentificador de la conversación (el conversa_id de las demás rutas y de los webhooks).
protocolostringProtocolo de la atención, el mismo que ven el equipo y el cliente.
conexao_identero o nullNúmero de WhatsApp de la conversación. null cuando la conexión fue eliminada del sistema (el historial se conserva).
telefonestringTeléfono del cliente, solo dígitos, con el código del país.
nomestringNombre del cliente: el guardado por el equipo o, sin él, el nombre del perfil de WhatsApp. Vacío cuando se desconoce.
estadostringauto, ia, fila, humano o encerrada. Vea Estados de la conversación.
aguardando_clientebooleanotrue 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, departamentoentero o null, stringDepartamento actual (id y nombre). null y vacío en la cola general.
atendente_id, atendenteentero o null, stringAgente responsable (id y nombre). En una conversación cerrada, quien la tenía al final.
nao_lidasenteroMensajes del cliente que el responsable aún no abrió en el panel.
motivo_encerramentostringNombre del motivo de cierre elegido. Vacío cuando la conversación está abierta o se cerró sin motivo.
resumoobjeto o nullResumen 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_emfecha y horaApertura de la conversación.
ultima_mensagem_emfecha y hora o nullÚltimo mensaje intercambiado (del cliente o para él).
encerrada_emfecha y hora o nullCierre; null si está abierta.
etiquetaslistaEtiquetas 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:

mensaje
{
  "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"
}
CampoTipoDescripción
identeroIdentificador del mensaje. Crece con el tiempo: sirve de cursor en conversas/mensagens.
conversa_identeroConversación a la que pertenece el mensaje.
direcaostringrecebida (del cliente) o enviada (del proveedor).
autorstringcliente, 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_nomestringNombre del agente cuando autor es atendente; vacío en los demás casos.
tipostringtexto, imagem, audio, video, documento, localizacao o contato (tarjeta de contacto compartida).
conteudostringTexto del mensaje. En archivos multimedia, el pie de foto (puede venir vacío). Vacío también cuando el mensaje fue eliminado.
transcricaostring o nullTranscripción de un audio, cuando la transcripción de audios está activada y ya terminó. null en los demás casos.
arquivoobjeto o nullMensajes 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.
statusstringpendente (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ó).
errostring o nullMotivo del fallo cuando status es erro.
responde_aentero o nullid del mensaje citado (respondido) por este.
editadabooleanoEl mensaje fue editado después de enviado; conteudo trae la versión actual.
apagadabooleanoEl mensaje fue eliminado para todos. El registro se mantiene (para que el historial siga coherente), sin contenido ni archivo.
criada_emfecha y horaMomento del mensaje.

Listar conversaciones

GET POST /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ámetroTipoDescripción
estadolistaUno 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.
abertasbooleano1: solo conversaciones abiertas (cualquier estado menos encerrada). Acepta 1/0, true/false, sim/nao.
departamento_identeroSolo de ese departamento. 0: solo las que están sin departamento (cola general).
atendente_identeroSolo de ese agente. 0: solo las que están sin agente.
conexao_identeroSolo de ese número de WhatsApp. En un número gemelo, trae las conversaciones de los dos canales.
telefonestringSolo 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_identeroSolo las que tienen esa etiqueta.
criadas_desde, criadas_atefechaApertura 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_desdefechaCerradas a partir de ese momento.
atualizadas_desdefechaConversaciones abiertas, cerradas o con mensaje nuevo a partir de ese momento. Es el filtro de la sincronización incremental.
ordemstringrecentes (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, limiteenteroPaginació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.

terminal
# 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
<?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']);
Python
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"])
respuesta JSON (HTTP 200)
{
  "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).

  1. En la primera carga, liste todo (sin filtro de fecha, ordem=criacao) y guarde las conversaciones por id.
  2. Antes de cada ronda, anote la hora actual del servidor (campo servidor.agora de chave/info, o su reloj sincronizado). Esa será la marca de la próxima ronda.
  3. Llame a conversas/listar con atualizadas_desde igual a la marca de la ronda anterior menos un pequeño margen (uno o dos minutos), ordem=antigas y limite=100, recorriendo las páginas.
  4. Grabe cada conversación por id (actualice si ya existe, inserte si es nueva). El margen hace que algunas conversaciones vengan repetidas; grabar por id lo vuelve inofensivo.
  5. Para los mensajes de cada conversación modificada, use conversas/mensagens con depois_de igual al último id de 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.

ronda incremental (Python)
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

GET POST /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ámetroTipoObligatorioDescripción
id o conversa_identeroUno de los tresIdentificador de la conversación.
protocolostringUno de los tresProtocolo, por ejemplo 20261001-0042.
telefonestringUno de los tresDevuelve 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.
mensagensenteroNoCuá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.

terminal
# 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
<?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;
}
Python
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"])
respuesta JSON (HTTP 200)
{
  "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

GET POST /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ámetroTipoObligatorioDescripción
conversa_id (o id) o protocoloSíLa conversación.
limiteenteroNoMensajes por página, de 1 a 100, predeterminado 50.
antes_deenteroNoRetroceder: trae los mensajes anteriores a ese id (los más cercanos a él).
depois_deenteroNoBuscar lo que llegó: trae los mensajes posteriores a ese id, a partir del más antiguo. Tiene prioridad sobre antes_de.
Campo de la respuestaDescripción
conversa_idLa conversación leída.
mensagensLista de mensajes, en orden cronológico.
tem_maistrue 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_deid del primer mensaje de la página: páselo en antes_de para la página anterior.
cursor.depois_deid 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_mais sea true, llame de nuevo con antes_de igual a cursor.antes_de.
  • Seguir lo que llega (reflejar la conversación en su sistema): guarde el cursor.depois_de y, la próxima vez, llame con depois_de; mientras tem_mais sea true, continúe con el nuevo cursor.depois_de.
terminal
# 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
<?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']);
Python
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
respuesta JSON (HTTP 200, limite=2)
{
  "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

POST /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ámetroTipoObligatorioDescripción
conversa_id (o id) o protocoloSíLa conversación. Debe estar abierta.
departamento_identeroAl menos uno de los dosDepartamento de destino, activo.
atendente_identeroAl menos uno de los dosAgente de destino, activo.
motivostringNoTexto 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íaResultado
Solo departamento_idLa 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_idLa 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.
AmbosLa 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 (y conversa_atribuida cuando la distribución automática entrega la conversación a alguien).
terminal
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
<?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;
Python
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:

respuesta JSON (HTTP 200, resumida)
{
  "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

POST /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ámetroTipoObligatorioDescripción
conversa_id (o id) o protocoloSíLa conversación. Debe estar abierta.
motivo_identeroNoMotivo 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_mensagembooleanoNoPredeterminado 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:

  1. 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.
  2. 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 resumo de 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, con motivo igual al nombre del motivo elegido (o a la identificación de la clave, sin motivo).
terminal
# 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
<?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
}
Python
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"])
respuesta JSON (HTTP 200, resumida)
{
  "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

POST /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ámetroTipoDescripción
conversa_id (o id) o protocoloLa conversación (obligatorio).
etiquetaslista de idsReemplaza todas las etiquetas por la lista. Una lista vacía ([] en JSON o etiquetas= en formulario) las quita todas.
adicionarlista de idsAgrega esas etiquetas, sin tocar las demás.
removerlista de idsQuita 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.

terminal
# 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
<?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;
Python
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"]])
respuesta JSON (HTTP 200, resumida)
{
  "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" }
    ]
  }
}
etiqueta inexistente (HTTP 400)
{"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

POST /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ámetroTipoObligatorioDescripción
conversa_id (o id) o protocoloSíLa conversación (abierta o cerrada).
textostringSí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_id si necesita hacer referencia a ella.
terminal
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
<?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);
Python
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())
respuesta JSON (HTTP 200)
{"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

POST /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ámetroTipoObligatorioDescripción
telefonestringSíTeléfono del cliente (misma regla de las rutas de envío).
conexao_identeroCondicionalNú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_identeroNoDepartamento 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_identeroNoEntrega la conversación directo a ese agente, activo.
notastringNoHasta 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.
nomestringNoNombre 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

  1. 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).
  2. La nota, si se envió, se graba en la línea de tiempo, junto con el registro de la clave.
  3. Con atendente_id: la conversación va directo a ese agente (estado humano), sea cual sea el estado en que estaba, y también al departamento_id, si se envió. Sale de la IA o del flujo, si estaba en ellos. El agente recibe el aviso.
  4. Sin atendente_id, con la conversación en la cola: va a la cola del departamento_id pedido (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 como humano; si no, queda en fila y el equipo recibe el aviso.
  5. 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.

terminal
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
<?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;
Python
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"])
respuesta JSON (HTTP 200)
{
  "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": []
  }
}
CampoDescripción
conversa_id, protocoloIdentificación de la atención.
estadoEstado 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_existiatrue 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.
conversaEl 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:

errorHTTPRutasCausa
metodo405transferir, encerrar, etiquetas, nota, abrirLlamada con GET en una ruta de escritura (esperado: POST).
identificador_obrigatorio400todas menos listar y abrirFaltó conversa_id/id o protocolo (o telefone, en consultar).
nao_encontrado404todas menos listar y abrirLa conversación no existe.
parametro_obrigatorio400transferir, etiquetasFaltó el destino o la lista; campo indica cuál.
data_invalida400listarFecha que no se pudo interpretar; campo indica cuál.
telefone_invalido400listar, consultar, abrirTeléfono que no tiene sentido.
conversa_encerrada409transferir, encerrarLa conversación ya está cerrada.
departamento_invalido400transferir, abrirDepartamento inexistente o inactivo.
atendente_invalido400transferir, abrirAgente inexistente, inactivo o eliminado.
motivo_invalido400encerrarMotivo inexistente o inactivo.
etiqueta_invalida400etiquetasEtiqueta inexistente; campo indica en qué lista.
texto_invalido400nota, abrirTexto de la nota vacío o de más de 900 caracteres.
conexao_invalida, sem_conexao, conexao_id_obrigatorio400abrirElección del número de WhatsApp (vea A quién va el mensaje).
conexao_desconectada409abrirEl número de WhatsApp elegido está desconectado.