Contacts

Reference for the contact routes: the list of everyone who has ever talked to your service desk, with search and incremental sync, the lookup of a contact with all of their conversations, and changing the name the team sees. Includes the phone grouping rule (with and without the ninth digit), examples in curl, PHP and Python, and the errors for each route.

Overview

ChatISP has no separate contact registry to maintain: a contact is the set of all conversations with the same phone number, on any of the company's WhatsApp numbers. It is the same view as the panel's Contacts screen (every contact that has ever reached the service desk, with their conversation history). A contact appears as soon as the first conversation with that phone number is created, whether because the customer wrote in or because of a send or open through the API.

RouteMethodKey permissionSummary
contatos/listarGET or POSTContactsPaginated list, with search by name or phone and a recent activity filter.
contatos/consultarGET or POSTContactsOne contact, with the identified customer, the totals and the latest 50 conversations.
contatos/renomearPOSTContactsSaves the contact's name, in all of their conversations and in future ones.

Typical cases: feeding the CRM with every number that has ever contacted the internet service provider (ISP), showing in the ERP how many conversations the subscriber has had, correcting the customer's name in ChatISP from the official record.

The Contacts permission stands on its own: a key with only Look up conversations cannot list contacts (403 permissao), and vice versa. The key sees the contacts of the whole company; the history restriction of agent profiles does not apply to the API.


One contact per phone number

The phone number is the contact's identity. Two details matter for integrators:

  • With and without the ninth digit are the same contact. The WhatsApp of some Brazilian mobile phones still arrives with 8 digits after the area code. ChatISP merges 5544999990000 and 554499990000 into a single contact, in the list, in the lookup and in the rename. You can search by either form.
  • All connections together. Conversations with the same phone number on different company WhatsApp numbers (for example, the support number and the sales number) form a single contact.

The returned telefone field is the one from the contact's most recent conversation, in the form WhatsApp delivered it, digits only and with the country code. When storing it in your system, normalize both Brazilian formats to the same key, as ChatISP does, so you do not duplicate the customer.

Phone numbers in conversationsContacts in the API
5544999990000 and 554499990000Just one (Brazilian mobile, with and without the ninth digit).
5544999990000 on support and on salesJust one (same phone, different connections).
351912345678 (Portugal)Numbers from other countries are not grouped: each form is a separate contact.

Contact object

contact (as returned by contatos/listar)
{
  "telefone": "5544999990000",
  "nome": "Maria Souza",
  "atendimentos": 7,
  "abertos": 1,
  "primeiro_contato_em": "2025-03-14T16:20:05-03:00",
  "ultimo_contato_em": "2026-10-01T09:41:03-03:00",
  "cliente": { "nome": "MARIA APARECIDA SOUZA", "origem": "IXC" }
}
FieldTypeDescription
telefonestringPhone number of the contact's most recent conversation, digits only, with the country code.
nomestringName as shown in the conversation: the one saved by the team (or by contatos/renomear) or, without it, the WhatsApp profile name. Empty when unknown.
atendimentosintegerTotal conversations (protocols) of the contact, open and closed, including notices sent.
abertosintegerHow many of these conversations are open right now. Only in contatos/listar.
primeiro_contato_em, ultimo_contato_emdate and timeOpening of the first conversation and latest activity (last message or, with no messages, the opening of the most recent conversation). Only in contatos/listar.
clienteobject or nullThe customer identified in the ERP or in an integrated external system, as shown on the customer card during the conversation: nome (the one in the record) and origem (the name of the ERP or external system that identified them). null when no conversation with the contact ever identified a customer.
ticketsintegerTotal service orders recorded for this phone number, in any stage (including completed ones). Only in contatos/consultar.

nome and cliente.nome are different things: the first is what the team calls the person on WhatsApp (it may be a nickname, or the name of whoever uses the phone); the second is the account holder in the ERP. See Customer record.


List contacts

GET POST /api_publica.php?r=contatos/listar

Paginated list of contacts, from the one with the most recent activity to the oldest.

Permission: Contacts.

ParameterTypeDescription
buscastringUp to 120 characters. Searches anywhere in the contact name (the one in the conversations and the one saved by the team) and, when the text has digits, anywhere in the phone number. Maria, souza and 99999 all work.
atualizados_desdedateOnly contacts with activity (conversation opened or message exchanged) from that moment on. Accepts ISO 8601, YYYY-MM-DD or YYYY-MM-DD HH:MM:SS; without a time zone, the server's time zone applies.
pagina, limiteintegerPagination: pagina starting at 1; limite from 1 to 100, default 50.

Syncing with the CRM

  1. On the first load, go through all pages without filters.
  2. On the following ones, use atualizados_desde with the time of the previous run minus a small margin, and store each contact by the phone key (normalized with and without the ninth digit, as in One contact per phone number).
  3. For the details of a changed contact (identified customer, service orders, conversations), call contatos/consultar.

The list is ordered by latest activity. If messages arrive while you are going through the pages, a contact may move up to page 1 and you may skip another one. When syncing, the margin in atualizados_desde fixes this on the next run.

terminal
# search by name
curl -s "https://atendimento.seuprovedor.com.br/api_publica.php?r=contatos/listar&busca=souza" \
  -H "Authorization: Bearer $CHATISP_CHAVE"

# contacts with activity since yesterday, 100 per page
curl -s "https://atendimento.seuprovedor.com.br/api_publica.php?r=contatos/listar&atualizados_desde=2026-09-30&limite=100" \
  -H "Authorization: Bearer $CHATISP_CHAVE"
PHP
<?php
// full load of contacts into the CRM
$pagina = 1;
do {
    $ch = curl_init('https://atendimento.seuprovedor.com.br/api_publica.php?' . http_build_query([
        'r' => 'contatos/listar', 'limite' => 100, 'pagina' => $pagina,
    ]));
    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['contatos'] as $c) {
        gravarNoCrm($c['telefone'], $c['nome'], $c['atendimentos']); // your code
    }
    $pagina++;
} while ($pagina <= $r['paginacao']['paginas']);
Python
import os, requests

r = requests.get("https://atendimento.seuprovedor.com.br/api_publica.php", params={
    "r": "contatos/listar", "busca": "99999", "limite": 20,
}, headers={"Authorization": "Bearer " + os.environ["CHATISP_CHAVE"]}, timeout=30)

for c in r.json()["contatos"]:
    titular = c["cliente"]["nome"] if c["cliente"] else "(no record)"
    print(c["telefone"], c["nome"], titular, c["abertos"])
JSON response (HTTP 200)
{
  "ok": true,
  "contatos": [
    {
      "telefone": "5544999990000",
      "nome": "Maria Souza",
      "atendimentos": 7,
      "abertos": 1,
      "primeiro_contato_em": "2025-03-14T16:20:05-03:00",
      "ultimo_contato_em": "2026-10-01T09:41:03-03:00",
      "cliente": { "nome": "MARIA APARECIDA SOUZA", "origem": "IXC" }
    },
    {
      "telefone": "554498887777",
      "nome": "Paulo",
      "atendimentos": 1,
      "abertos": 0,
      "primeiro_contato_em": "2026-09-30T18:02:44-03:00",
      "ultimo_contato_em": "2026-09-30T18:10:12-03:00",
      "cliente": null
    }
  ],
  "paginacao": { "pagina": 1, "limite": 50, "total": 2, "paginas": 1 }
}

Route-specific errors: parametro_invalido (limite or pagina outside the accepted range), data_invalida.


Look up a contact

GET POST /api_publica.php?r=contatos/consultar

Returns a contact with the identified customer, the total conversations and service orders, and the 50 most recent conversations, newest first. Use it to build the subscriber profile in the ERP or CRM: "this customer has contacted the service desk 7 times; the last one was today, about slow speeds".

Permission: Contacts.

ParameterTypeRequiredDescription
telefonestringYesContact's phone number, in any form (with or without 55, with or without the ninth digit). Same rule as the other routes.
terminal
curl -s "https://atendimento.seuprovedor.com.br/api_publica.php?r=contatos/consultar&telefone=44999990000" \
  -H "Authorization: Bearer $CHATISP_CHAVE"
PHP
<?php
$ch = curl_init('https://atendimento.seuprovedor.com.br/api_publica.php?' . http_build_query([
    'r' => 'contatos/consultar', 'telefone' => '44999990000',
]));
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => ['Authorization: Bearer ' . getenv('CHATISP_CHAVE')],
]);
$r = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

if ($status === 404) {
    echo 'This phone number has never contacted the service desk.', PHP_EOL;
} else {
    echo $r['contato']['atendimentos'], ' conversations, ', $r['contato']['tickets'], ' tickets', PHP_EOL;
    foreach ($r['conversas'] as $c) {
        echo $c['protocolo'], ' ', $c['resumo']['titulo'] ?? '(no summary)', PHP_EOL;
    }
}
Python
import os, requests

r = requests.get("https://atendimento.seuprovedor.com.br/api_publica.php",
    params={"r": "contatos/consultar", "telefone": "44999990000"},
    headers={"Authorization": "Bearer " + os.environ["CHATISP_CHAVE"]}, timeout=30)

if r.status_code == 404:
    print("No history")
else:
    dados = r.json()
    for c in dados["conversas"]:
        print(c["protocolo"], c["estado"], c["motivo_encerramento"])
JSON response (HTTP 200)
{
  "ok": true,
  "contato": {
    "telefone": "5544999990000",
    "nome": "Maria Souza",
    "cliente": { "nome": "MARIA APARECIDA SOUZA", "origem": "IXC" },
    "atendimentos": 7,
    "tickets": 2
  },
  "conversas": [
    {
      "id": 18342,
      "protocolo": "20261001-0042",
      "conexao_id": 1,
      "telefone": "5544999990000",
      "nome": "Maria Souza",
      "estado": "encerrada",
      "aguardando_cliente": false,
      "departamento_id": 1,
      "departamento": "Technical support",
      "atendente_id": 7,
      "atendente": "Carlos Lima",
      "nao_lidas": 0,
      "motivo_encerramento": "Resolved",
      "resumo": {
        "titulo": "No internet after power outage",
        "texto": "The customer reported no internet since the morning. The agent found the ONU without signal and scheduled a technical visit for the afternoon."
      },
      "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"
    }
  ]
}
  • contato.atendimentos counts all conversations with the phone number; the conversas list brings at most the 50 most recent. For older ones, use conversas/listar with telefone and pagination.
  • Conversations come in the conversation format, without the etiquetas field (look up a single conversation to get them).
  • contato.telefone and contato.nome come from the most recent conversation.

Route-specific errors: parametro_obrigatorio (telefone missing), telefone_invalido, nao_encontrado (404: the phone number has never had a conversation).


Rename a contact

POST /api_publica.php?r=contatos/renomear

Saves the contact's name, like the name edit an agent makes in the panel. Use it to replace the WhatsApp nickname ("Mom", "Joe from the farm") with the name from the ERP record, so the team knows who they are talking to.

Permission: Contacts.

ParameterTypeRequiredDescription
telefonestringYesContact's phone number. It must have at least one conversation.
nomestringYesFrom 1 to 120 characters; leading and trailing spaces are removed.

Effect

  • In the panel, immediately: the name applies to all conversations with the phone number, open and closed, on all of the company's WhatsApp numbers and in both forms of the number (with and without the ninth digit). The conversation list, kanban, history and Contacts screen show the new name.
  • In future conversations: every new conversation with this phone number starts with the saved name. It takes precedence over the WhatsApp profile name and over the nome parameter of the send routes and of atendimentos/abrir.
  • The name is also used wherever automatic messages use {nome} (for example, the closed service request message).
  • The customer's record in the ERP is not changed: it still appears in cliente.nome.
terminal
curl -s -X POST "https://atendimento.seuprovedor.com.br/api_publica.php?r=contatos/renomear" \
  -H "Authorization: Bearer $CHATISP_CHAVE" \
  -H "Content-Type: application/json" \
  -d '{"telefone": "44999990000", "nome": "Maria Aparecida Souza"}'
PHP
<?php
$ch = curl_init('https://atendimento.seuprovedor.com.br/api_publica.php?r=contatos/renomear');
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(['telefone' => '44999990000', 'nome' => 'Maria Aparecida Souza']),
]);
$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": "contatos/renomear"},
    headers={"Authorization": "Bearer " + os.environ["CHATISP_CHAVE"]},
    json={"telefone": "44999990000", "nome": "Maria Aparecida Souza"}, timeout=30)
print(r.status_code, r.json())
JSON response (HTTP 200)
{"ok": true, "telefone": "5544999990000", "nome": "Maria Aparecida Souza"}

The returned telefone is the one from the contact's most recent conversation. Route-specific errors: parametro_obrigatorio (telefone missing), telefone_invalido, nome_invalido (empty or over 120 characters), nao_encontrado (404: the phone number has never had a conversation; to register a new number, open a service request or send a message first).


Errors by route

In addition to the general errors (rota, autenticacao, chave_invalida, plano, cota_excedida, permissao, json_invalido), described in Error codes:

errorHTTPlistarconsultarrenomear
metodo405yes
parametro_invalido400yesyesyes
data_invalida400yes
parametro_obrigatorio400yesyes
telefone_invalido400yesyes
nome_invalido400yes
nao_encontrado404yesyes