ChatISP public API

Integrate ChatISP with your internet service provider's (ISP) systems: ERP, CRM, subscriber portal, monitoring (NOC) and BI. There are 37 operations covering messages, conversations, contacts, service orders, calendar and metrics, with granular key permissions, standardized JSON responses and real-time notifications through signed webhooks.

Overview

The public API is ChatISP's machine channel: it lets another system of the ISP work with customer service without opening the panel. It works in both directions:

  • From your system to ChatISP: HTTPS calls authenticated by key to send messages (text, official API approved template and file), list and look up conversations with their history, transfer, close, tag and annotate service requests, open service requests in the queue, look up and rename contacts, open and move service orders, book appointments on the calendar and read the customer service metrics.
  • From ChatISP to your system: outgoing webhooks. ChatISP makes a signed POST to the address you register when a message arrives or is sent, when a conversation is created, changes owner or is closed, and when a service order is opened or updated. See Outgoing webhooks.

The 37 operations

The whole API uses a single address; the operation is chosen by the r parameter, in the recurso/acao (resource/action) format. The Scope column shows the permission the key must have (see Key permissions); "any key" means the key only needs to be active.

Route (r)MethodScopeWhat it does
Catalogs: the ids the rest of the API asks for
chave/infoGETany keyName, scopes and limit of the key; API version, time zone and server plan.
conexoes/listarGETany keyWhatsApp numbers (QR and official API), state and twin pair.
departamentos/listarGETany keyDepartments, active and inactive, and which one receives new conversations.
atendentes/listarGETany keyAgents with profile, departments, presence and pause.
modelos/listarGETany keyOfficial API message templates and whether they can be sent through the API.
etiquetas/listarGETany keyConversation tags, with color.
motivos/listarGETany keyClosing reasons.
Messages
mensagens/enviarPOSTmensagens_enviarSends a text to a phone number or a conversation.
mensagens/enviar_modeloPOSTmensagens_enviarSends a template approved by Meta (outside the 24-hour window).
mensagens/enviar_arquivoPOSTmensagens_enviarSends an image, video, audio or document, with an optional caption.
mensagens/arquivoGETconversas_lerDownloads the file of a message (binary response, outside the JSON).
Conversations
conversas/listarGETconversas_lerLists conversations with filters and pagination.
conversas/consultarGETconversas_lerOne conversation (by id, protocol or phone) with its latest messages.
conversas/mensagensGETconversas_lerMessage history paginated by cursor.
conversas/transferirPOSTconversas_gerirTransfers to a department or agent.
conversas/encerrarPOSTconversas_gerirCloses the service request, with an optional reason.
conversas/etiquetasPOSTconversas_gerirSets, adds or removes tags.
conversas/notaPOSTconversas_gerirSaves an internal note (the team sees it, the customer does not).
Contacts and service requests
atendimentos/abrirPOSTatendimentos_abrirPuts a number in a department queue or straight with an agent, without sending anything to the customer.
contatos/listarGETcontatosContacts (one per number), with search and pagination.
contatos/consultarGETcontatosOne contact with its service history.
contatos/renomearPOSTcontatosSaves the contact name for current and future conversations.
Service orders
tickets/etapasGETtickets_lerStages of the service order board.
tickets/tiposGETtickets_lerService order types.
tickets/listarGETtickets_lerLists service orders with filters and pagination.
tickets/consultarGETtickets_lerOne service order with its timeline.
tickets/criarPOSTtickets_gerirOpens a service order.
tickets/moverPOSTtickets_gerirMoves the service order to another stage.
tickets/atribuirPOSTtickets_gerirSets or removes the assignee.
tickets/comentarPOSTtickets_gerirAdds a comment.
Calendar
agenda/tiposGETagenda_lerAppointment types.
agenda/listarGETagenda_lerAppointments in a period.
agenda/consultarGETagenda_lerOne appointment.
agenda/criarPOSTagenda_gerirBooks an appointment.
agenda/concluirPOSTagenda_gerirCompletes an appointment, with an optional outcome.
agenda/cancelarPOSTagenda_gerirCancels an appointment.
Metrics
relatorios/resumoGETrelatoriosVolumes, business-hours times, satisfaction and figures per department and agent.

In the Method column, GET indicates a read: it also accepts POST. Operations marked POST change something and only accept POST.

Typical ISP use cases

SystemWhat to integrateRoutes and events
ERP / billingPayment confirmation, bill copy, suspension and reactivation notice, boleto in PDF.mensagens/enviar, mensagens/enviar_modelo, mensagens/enviar_arquivo
NOC / monitoringFiber cut or maintenance notice to the subscribers of the region, automatic service order for the field team.mensagens/enviar_modelo, tickets/criar, tickets/comentar
Subscriber portal and websiteA "contact us" form that lands in the right department's queue; technical visit scheduling.atendimentos/abrir, agenda/criar
CRMService history per customer, a record of each closed conversation with protocol, summary and reason.contatos/consultar, conversas/mensagens, webhook conversa_encerrada
Work order systemMirror ChatISP service orders, move stages as the work order progresses, assign a technician.tickets/*, webhooks ticket_criado and ticket_atualizado
BI / management dashboardDaily metrics, incremental sync of conversations.relatorios/resumo, conversas/listar with atualizadas_desde
Internal automationTag and transfer conversations by your own rules, write ERP data into the conversation as a note.conversas/etiquetas, conversas/transferir, conversas/nota, webhook mensagem_recebida

Complete programs for several of these cases are in Practical examples.


Requirements

RequirementDetails
Plan with public API Plan featureThe public API and webhooks are part of the PREMIUM, GOLD and ENTERPRISE plans, and are available during the free trial. On plans without the feature, the keys and webhooks cards on the Integrations screen appear locked with a notice of the required plan, and any call responds HTTP 403 with "error":"plano".
Number of keys Limit per planKeys active at the same time: up to 3 on PREMIUM, up to 10 on GOLD, unlimited on ENTERPRISE and during the trial. The meter next to the card title shows the usage; at the limit, the New key button no longer appears. Inactive keys do not count.
License in good standingWith the license blocked (trial expired, overdue payment, license deactivated or no communication with the central server), the API refuses calls with HTTP 403 plano, even on an eligible plan.
Panel permissionCreating, editing, revealing and deleting keys and webhooks requires the Integrations permission in the user's access profile (see Agents, profiles and permissions).
WhatsApp connectionThe sending and service-opening routes need a connected connection. See WhatsApp connections.
NetworkHTTPS access to your ChatISP address from the server that will call the API.

Self-hosted installation and ChatISP Cloud

The API is the same in both forms of use; only the address changes.

  • Self-hosted installation (the ISP's own server): the address is the domain you use to access the panel. Without a configured domain, the server responds with a self-signed certificate and your system would have to accept it explicitly. Configure a domain with Let's Encrypt in Server before putting the integration into production.
  • ChatISP Cloud: the address is that of your panel hosted by Ravi Systems, already with a valid certificate.

In both cases, the exact address appears on the ChatISP API card, under API address.


Create an API key

The key is the API's only credential: there is no user, password or session. Create one key per integration, with only the permissions it uses and a per-minute call limit.

Step by step

  1. In the side menu, open Integrations.
  2. Find the ChatISP API card ("Keys for other systems to use the API: messages, conversations, contacts, service orders, calendar and metrics, each key with its own permissions."). Right below the title is the API address, with the API documentation button: write down the address, it is the URL the integration will call.
  3. Click New key.
  4. Fill in the Name (up to 120 characters) with something that identifies the integration, for example "Billing ERP" or "NOC". This name appears in the history of everything the key does.
  5. Turn on the permissions the integration uses. They are in three groups: Customer service, Service orders and calendar and Management. On a new key, Send messages, Look up conversations and Open service requests come turned on; the others come turned off. The complete table is in Key permissions.
  6. Adjust the Per-minute limit ("Calls accepted per minute. 0 turns the limit off."). The default is 60 and the maximum is 10000.
  7. Click Save. The Key created window shows the full key in the API key field. Click Copy and store it in the system that will use the API.
key format
cisp_7f3a9c21d84e0b56a1f2c3d4e5f60718293a4b5c6d7e8f90

Every key starts with cisp_, followed by 48 hexadecimal characters. The card list shows only the beginning (for example cisp_7f3a9…) so you can tell them apart.

The key list

ColumnWhat it shows
NameThe name given to the key.
API keyThe beginning of the key, for recognition.
PermissionsA label for each permission turned on.
UsesTotal authenticated calls made with the key. On hover, Last used appears.
StatusEnabled or Disabled.
ActionsEdit, Reveal and Delete.

Reveal, edit and delete

  • Reveal shows the full key again, with the Copy button, at any time, to anyone with the Integrations permission.
  • Edit changes the name, permissions, per-minute limit and the Enabled switch. The key itself does not change: the integration keeps working with the same value, now with the new permissions. Turning off Enabled suspends the key immediately (HTTP 401 chave_invalida) without deleting it, and it stops counting toward the plan limit.
  • Delete asks for confirmation ("Delete this key? Integrations using it will stop working.") and revokes the key immediately.

Since keys can be revealed in the panel, anyone with the Integrations permission has access to all of them. Grant this permission only to the people who administer the integrations.


Key permissions (scopes)

Each permission on the screen corresponds to a scope, a stable code the API uses to decide what the key can do. The code appears in the escopos field of chave/info and in the permissao error, which states which scope was missing.

Name on screenScopeWhat it allowsRoutes
Customer service
Send messagesmensagens_enviarText, approved official API templates and files.mensagens/enviar, mensagens/enviar_modelo, mensagens/enviar_arquivo
Look up conversationsconversas_lerList and look up conversations, read the message history and download files.conversas/listar, conversas/consultar, conversas/mensagens, mensagens/arquivo
Open service requestsatendimentos_abrirPut a number in a department queue or straight with an agent.atendimentos/abrir
Manage conversationsconversas_gerirTransfer, close, tag and add notes to conversations.conversas/transferir, conversas/encerrar, conversas/etiquetas, conversas/nota
ContactscontatosList, look up and rename contacts.contatos/listar, contatos/consultar, contatos/renomear
Service orders and calendar
Look up service orderstickets_lerList service orders, see the timeline, stages and types.tickets/etapas, tickets/tipos, tickets/listar, tickets/consultar
Manage service orderstickets_gerirOpen, move between stages, assign and comment on service orders.tickets/criar, tickets/mover, tickets/atribuir, tickets/comentar
Look up calendaragenda_lerList and look up appointments.agenda/tipos, agenda/listar, agenda/consultar
Manage calendaragenda_gerirSchedule, complete and cancel appointments.agenda/criar, agenda/concluir, agenda/cancelar
Management
MetricsrelatoriosVolumes, business-hours times, satisfaction and figures per department and agent.relatorios/resumo

Scopes are independent: Manage service orders does not include Look up service orders, for example. An integration that opens service orders and then follows their progress needs both.

Catalogs: available to any key

The catalog routes (chave/info, conexoes/listar, departamentos/listar, atendentes/listar, modelos/listar, etiquetas/listar and motivos/listar) require no scope: any active key can call them, even with all permissions turned off. They return the ids the other routes ask for (conexao_id, departamento_id, atendente_id, modelo_id, etiqueta_id, motivo_id); without them no integration could build its calls. The screen states this right below the permissions: "Connections, departments, agents, templates, tags and closing reasons are available to any active key." Reference in Catalogs.

How many keys to create

  • One per integration, with a clear name: this way you can limit, suspend or revoke one system without taking down the others, and the conversation history shows which system performed each action.
  • Separate reading from writing when different systems do each thing. A BI dashboard only needs Look up conversations and Metrics; the ERP that sends boletos only needs Send messages.

Address and authentication

All calls go to the same address, shown on the ChatISP API card:

base URL
https://<your panel address>/api_publica.php?r=<recurso>/<acao>

The key goes in the HTTP Authorization header, using the Bearer scheme. It never goes in the URL or in the body:

header
Authorization: Bearer cisp_7f3a9c21d84e0b56a1f2c3d4e5f60718293a4b5c6d7e8f90
ItemWhere it goesRequiredDescription
AuthorizationHTTP headeryesBearer, a space and the full key. The word Bearer is case-insensitive.
rquery string (recommended) or form bodyyesThe route, in the recurso/acao format (lowercase letters and underscores). With a JSON body, r must be in the URL: it is not read from inside the JSON.

There is no login or session: each call is independent.

First call

The chave/info route is ideal for validating the address and key: it requires no permission, changes nothing and shows what the key can do.

terminal
curl -s "https://atendimento.seuprovedor.com.br/api_publica.php?r=chave/info" \
  -H "Authorization: Bearer $CHATISP_CHAVE"
PHP
<?php
// Minimal client, reusable for the other routes
function chatisp(string $rota, array $dados = [], string $metodo = 'GET'): array {
    $base  = 'https://atendimento.seuprovedor.com.br/api_publica.php';
    $chave = getenv('CHATISP_CHAVE');
    $url   = $base . '?r=' . $rota;
    $ch    = curl_init();
    $opcoes = [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_TIMEOUT        => 30,
        CURLOPT_HTTPHEADER     => ['Authorization: Bearer ' . $chave, 'Content-Type: application/json'],
    ];
    if ($metodo === 'POST') {
        $opcoes[CURLOPT_POST]       = true;
        $opcoes[CURLOPT_POSTFIELDS] = json_encode($dados);
    } elseif ($dados) {
        $url .= '&' . http_build_query($dados);
    }
    $opcoes[CURLOPT_URL] = $url;
    curl_setopt_array($ch, $opcoes);
    $corpo  = curl_exec($ch);
    $status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);
    $resposta = json_decode((string) $corpo, true) ?? ['ok' => false, 'error' => 'resposta_invalida'];
    $resposta['_http'] = $status;
    return $resposta;
}

$info = chatisp('chave/info');
if (!$info['ok']) {
    die("Error {$info['_http']}: {$info['error']}\n");
}
echo $info['chave']['nome'], ': ', implode(', ', $info['chave']['escopos']), PHP_EOL;
Python
import os, requests

BASE  = "https://atendimento.seuprovedor.com.br/api_publica.php"
CHAVE = os.environ["CHATISP_CHAVE"]

def chatisp(rota, dados=None, metodo="GET"):
    cab = {"Authorization": f"Bearer {CHAVE}"}
    if metodo == "POST":
        r = requests.post(BASE, params={"r": rota}, json=dados or {}, headers=cab, timeout=30)
    else:
        r = requests.get(BASE, params={"r": rota, **(dados or {})}, headers=cab, timeout=30)
    corpo = r.json()
    corpo["_http"] = r.status_code
    return corpo

info = chatisp("chave/info")
if not info["ok"]:
    raise SystemExit(f"Error {info['_http']}: {info['error']}")
print(info["chave"]["nome"], info["chave"]["escopos"])
response (HTTP 200)
{
  "ok": true,
  "chave": {
    "id": 3,
    "nome": "ERP financeiro",
    "prefixo": "cisp_7f3a9…",
    "escopos": ["mensagens_enviar", "conversas_ler", "atendimentos_abrir"],
    "cota_minuto": 60
  },
  "servidor": {
    "api_versao": "1",
    "fuso_horario": "America/Sao_Paulo",
    "agora": "2026-10-01T09:30:12-03:00",
    "plano": "PREMIUM"
  }
}

Validation order

Every call goes through these checks, in this order. The first one that fails defines the response:

  1. r missing or not in the recurso/acao format: HTTP 404 rota.
  2. Authorization: Bearer ... header missing or malformed: HTTP 401 autenticacao.
  3. Key nonexistent, deleted, inactive or not in the cisp_... format: HTTP 401 chave_invalida.
  4. Plan without public API or license blocked: HTTP 403 plano.
  5. The call is counted in the key's usage. If the per-minute limit was exceeded: HTTP 429 cota_excedida.
  6. Body declared as JSON but invalid: HTTP 400 json_invalido.
  7. Nonexistent route: HTTP 404 rota.
  8. Write route called without POST: HTTP 405 metodo.
  9. Key without the route's scope: HTTP 403 permissao, with the missing scope.
  10. The operation validates its own parameters and runs.

From step 5 on, every call counts toward the key's usage (Uses column and per-minute limit), even if it ends in a route, permission or parameter error. Calls with an invalid key do not count.

Always call the API from your server (backend). Never put the key in a web page, an app installed on a phone or a script that runs in the browser: anyone could copy it and act on behalf of the ISP.


Request format

Methods

  • Read (list, look up, catalogs, metrics, download file): GET or POST.
  • Write (send, transfer, close, create, move...): POST only. With another method, the response is HTTP 405 metodo with "esperado":"POST".

Where parameters go

FormHeaderWhen to use
Query stringnoneRead filters in GET: ?r=conversas/listar&abertas=1&limite=20.
Formapplication/x-www-form-urlencodedThe default format of curl -d. Lists repeat the field with brackets: variaveis[]=Maria&variaveis[]=R$ 99,90.
JSONapplication/jsonThe whole body is a JSON object. Recommended for writes. r goes in the URL.
Multipartmultipart/form-dataFile upload in mensagens/enviar_arquivo (arquivo field). JSON alternative: arquivo_base64.

The query string works with any method. When the same parameter comes in both the query and the body, the body wins. A body with Content-Type: application/json that is not a valid JSON object is refused with HTTP 400 json_invalido.

the same call as a form and as JSON
# form
curl -s -X POST "https://atendimento.seuprovedor.com.br/api_publica.php?r=mensagens/enviar" \
  -H "Authorization: Bearer $CHATISP_CHAVE" \
  -d "telefone=44999990000" -d "texto=Hi! Your technician is on the way."

# JSON
curl -s -X POST "https://atendimento.seuprovedor.com.br/api_publica.php?r=mensagens/enviar" \
  -H "Authorization: Bearer $CHATISP_CHAVE" \
  -H "Content-Type: application/json" \
  -d '{"telefone":"44999990000","texto":"Hi! Your technician is on the way."}'

Parameter types

TypeAcceptsNotes
integer42 or "42"Ids, pagina, limite. Text that is not an integer: parametro_invalido with the campo.
booleantrue/false, 1/0, sim/nao, yes/noEmpty means the route's default.
listJSON array, campo[]=a&campo[]=b or comma-separated text (3,7,9)Tags, states. An empty list ([] or "") is different from not sending the field.
dateISO 8601, YYYY-MM-DD or YYYY-MM-DD HH:MM:SSSee Dates and time zone.
textUTF-8Leading and trailing spaces are removed. Each route states the maximum length.

Response format

Every response is JSON (Content-Type: application/json; charset=utf-8, Cache-Control: no-store) with the same envelope on all routes:

SituationBody
Success{"ok": true, ...operation fields}
Error{"ok": false, "error": "codigo", ...extra fields}

The error field is always a stable code, without accents and never translated, designed for your system to decide what to do. The extra fields help fix the call: campo (which parameter), aceitos (allowed values), maximo, esperado, escopo, among others. Accented characters come unescaped ("Suporte técnico"), and so do slashes.

error examples
HTTP 400  {"ok":false,"error":"parametro_invalido","campo":"estado","aceitos":["ia","fila","humano","encerrada","auto"]}
HTTP 403  {"ok":false,"error":"permissao","escopo":"tickets_gerir"}
HTTP 404  {"ok":false,"error":"nao_encontrado"}

HTTP status

The HTTP status is real and consistent with the result: check both the status and the ok field.

HTTPWhen
200The operation was performed.
400Missing or invalid parameter, unsuitable connection, invalid JSON.
401No key, malformed header or invalid key.
403Plan without the feature, license blocked or key without the scope.
404Nonexistent route or record not found.
405Write route called without POST.
409Conflict with the current state: connection disconnected, conversation already closed, 24-hour window closed, service order board without stages.
410The message file existed but is no longer on the server.
413File too large.
415Unsupported file type.
429The key's per-minute limit was exceeded, with the Retry-After header.
500Internal failure while saving a file.
502WhatsApp did not accept the message (envio error). The attempt is recorded in the conversation.

Responses that are not JSON

Only mensagens/arquivo: on success it returns the file itself, with the file's Content-Type (images and PDF) or application/octet-stream (other types) and Content-Disposition with the original name. On error, it returns the normal JSON envelope.

Recurring objects

The message, conversation and contact routes always return the conversation and the message with the same fields (details in Conversations and Messages). Values you will find:

FieldValues
conversation estadofila (waiting for an agent), auto (in a support flow), ia (with the AI), humano (with an agent), encerrada (closed).
message direcaorecebida (from the customer) or enviada (from the ISP). Internal notes never appear in the API.
message autorcliente, atendente, ia or sistema (flows, automatic notices, campaigns and messages sent through the API).
message tipotexto, imagem, audio, video, documento, localizacao, contato.
message statuspendente, enviada, entregue, lida, erro.

Pagination and cursor

Numbered pagination

The large listings (conversas/listar, contatos/listar, tickets/listar and agenda/listar) use page and limit:

ParameterTypeDefaultDescription
paginainteger1Desired page, starting at 1.
limiteinteger50Items per page, from 1 to 100. Out of range: parametro_invalido with "maximo":100.

The response includes the paginacao block:

pagination block
"paginacao": {
  "pagina": 2,
  "limite": 50,
  "total": 137,
  "paginas": 3
}

total is the number of records matching the filters and paginas is how many pages exist with that limit (0 when there are no records). To go through everything, call until pagina reaches paginas. A page beyond the last one returns an empty list, without error.

To sync a large base, prefer incremental date filters (atualizadas_desde in conversations, atualizados_desde in contacts) over scanning every page on each run. New records added while you paginate can shift items between pages.

Message history cursor

A conversation's history (conversas/mensagens) uses a cursor based on the message id, which does not shift when new messages arrive:

ParameterDescription
no cursorThe most recent messages, in chronological order.
antes_deMessages with an id lower than the one given: to scroll back through the history.
depois_deMessages with an id higher than the one given: to fetch what arrived since the last read.
limiteFrom 1 to 100, default 50.
end of the conversas/mensagens response
"tem_mais": true,
"cursor": {
  "antes_de": 402801,
  "depois_de": 402850
}

tem_mais says whether there are still messages in the requested direction. To keep scrolling back, pass cursor.antes_de as the next antes_de; to follow new ones, store cursor.depois_de and use it as depois_de in the next query. Messages always come in chronological order (oldest to newest).


Dates and time zone

Dates in the response

All output dates come in ISO 8601 with the server's time zone, for example 2026-10-01T09:12:40-03:00. Missing dates come as null. The server's time zone appears in chave/info (servidor.fuso_horario), along with its clock (servidor.agora), useful for checking the time difference with your system.

Dates in the request

FormatExampleInterpretation
ISO 8601 with time zone2026-10-01T09:00:00-03:00, 2026-10-01T12:00:00ZConverted to the server's time zone. Recommended.
Date and time without time zone2026-10-01 09:00:00 or 2026-10-01T09:00:00Read in the server's time zone.
Date only2026-10-01Start of the day (00:00:00) in "since" and "from" filters; end of the day (23:59:59) in "until" filters.

Years outside 2000 to 2100, or text that is not a date, respond HTTP 400 data_invalida with the campo. Periods whose end is before the start, or longer than the route allows (366 days in metrics and calendar), respond periodo_invalido.


Phone numbers

Phone numbers follow the same rule as the panel screen. Punctuation, spaces and parentheses are ignored.

How you send itHow ChatISP reads it
44999990000 or (44) 99999-0000Brazilian number: country code 55 is added. It becomes 5544999990000.
4433330000Brazilian landline with area code (10 digits). It becomes 554433330000.
5544999990000Already with 55: accepted as is.
+55 44 99999-0000With + and 55: accepted as Brazilian.
+351 912 345 678Another country: it must start with +. Accepted with 8 to 15 digits.

Rules for Brazilian numbers: area code from 11 to 99 (no zero) and, when the number has 11 digits, the third must be the mobile 9. Otherwise, the response is HTTP 400 telefone_invalido. Without the +, a number that does not fit as Brazilian is also refused, so that no message is sent to the wrong country.

In lookups by phone (conversations, contacts), ChatISP searches for the number with and without the ninth digit, because WhatsApp on some older phones still uses the 8-digit format. In responses, the phone comes with digits only and with the country code, in the format in which the conversation is recorded.


Error codes

Complete list of the possible values of error. Each reference page shows which errors each route can return.

errorHTTPMeaning and what to do
Access
rota404r missing, not in the recurso/acao format or nonexistent route. Check the spelling and, with a JSON body, remember that r goes in the URL.
autenticacao401The Authorization header is missing or not in the Bearer <key> format.
chave_invalida401Key nonexistent, deleted or inactive; or copied only partially, with a space or a line break.
plano403The plan does not include the public API or the license is blocked. The plano extra has the name of the current plan.
cota_excedida429The key exceeded its per-minute limit. Extra cota_minuto; wait the seconds given in the Retry-After header.
permissao403The key does not have the route's scope. The escopo extra says which one to turn on in the key.
metodo405Write route called without POST. Extra esperado.
json_invalido400The body was declared application/json, but is not a valid JSON object.
Parameters
parametro_obrigatorio400A parameter is missing. The campo extra says which; when there are alternatives, they are separated by a vertical bar ("modelo_id|modelo": provide one of them).
parametro_invalido400Value in the wrong format (integer, boolean, list) or outside what is allowed. Extras campo and, depending on the case, aceitos or maximo.
identificador_obrigatorio400The route needs to know which record is meant: provide the id (conversa_id, ticket_id...), the protocolo or the numero, depending on the route.
data_invalida400A date that could not be read. Extra campo.
periodo_invalido400End before start, or period longer than allowed (extra maximo_dias).
telefone_invalido400Phone number outside the rules. See Phone numbers.
texto_invalido400Text empty or longer than the route's limit (message, note, comment). Extra campo when it is not texto.
titulo_invalido400Service order or appointment title empty or longer than 160 characters.
nome_invalido400Contact name empty or longer than 120 characters.
legenda_invalida400File caption longer than 1024 characters.
link_invalido400Appointment link that does not start with http:// or https://, contains a space or exceeds 500 characters.
Records
nao_encontrado404The requested record does not exist: conversation, message (or message without a file), contact, service order or appointment.
conversa_invalida400The conversa_id given to link a service order does not exist.
conversa_encerrada409Transferring or closing requires an open conversation.
departamento_invalido400Department nonexistent or inactive.
atendente_invalido400Agent nonexistent, inactive or deleted.
motivo_invalido400Closing reason nonexistent or inactive.
etiqueta_invalida400Some tag id does not exist. Extra campo (etiquetas, adicionar or remover).
etapa_invalida400Service order stage nonexistent or inactive.
sem_etapas409The service order board has no active stage to receive the new service order. Configure the stages in the panel.
Connection and sending
sem_conexao400No conexao_id was given and there is no connected connection (of the type the route requires).
conexao_id_obrigatorio400There is more than one connected number and the key has no Sending number: provide conexao_id (see Connections) or set the Sending number on the API key (Integrations, API keys).
conexao_invalida400The connection does not exist or is not of the required type (for example, a template on a number without the official API). Extra tipo_esperado when it is a matter of type.
conexao_desconectada409The connection exists but is not connected (for a twin number, neither of the two is).
modelo_invalido400Template nonexistent, from another connection or not approved by Meta.
modelo_com_midia400The template has a media header (image, video, document), which the API does not send.
variaveis_invalidas400variaveis is not a list, or values are missing. Extra esperadas with how many the template requires.
janela_fechada409On the official API, free text can only be sent up to 24 hours after the customer's last message. Use an approved template. The attempt is recorded; extras conversa_id, protocolo, mensagem_id and mensagem.
envio502WhatsApp did not accept the message. Extra detalhe with the channel's reason, plus conversa_id, protocolo, mensagem_id and mensagem (the attempt stays in the conversation, marked with an error).
Files
arquivo_invalido400Upload failed, empty file or arquivo_base64 that is not valid base64.
arquivo_grande413 or 400File larger than 45 MB (413, extra maximo_bytes). When the web server itself refuses the upload because of its size, the response is 400 with the same code.
tipo_nao_suportado415File type that WhatsApp does not accept (for example, executables). Extra mimetype detected from the content.
arquivo_indisponivel410The message had a file, but it is no longer on the server (for example, removed from disk).
arquivo500Failure saving the file on the server. Try again later; if it persists, contact support.

Handle errors by code, never by the text of detalhe (which comes from the channel and may change). Ignore extra fields your system does not know: new extras may appear to help with diagnosis.


Limits

Key per-minute limit

Each key has its own Per-minute limit (default 60, maximum 10000, 0 for no limit). The count is per clock minute of the server: it resets when the minute turns (at 10:41:00, at 10:42:00...), not in a sliding 60-second window.

With a limit set, every response includes the headers:

HeaderContent
X-RateLimit-LimitThe key's per-minute limit.
X-RateLimit-RemainingHow many calls still fit in the current minute.
Retry-AfterOnly in the 429 response: seconds until the next minute.

A key with limit 0 does not receive the X-RateLimit-* headers.

HTTP 429 response
HTTP/1.1 429 Too Many Requests
Retry-After: 23
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 0
Content-Type: application/json; charset=utf-8

{"ok":false,"error":"cota_excedida","cota_minuto":60}

The refused call was not executed: wait for Retry-After and try again. Refusals also count toward the minute, so retrying in a loop without waiting does not help.

Sizes and quantities

ItemLimit
Items per page1 to 100 (default 50).
Message text4096 characters.
File caption1024 characters.
Uploaded file45 MB. The installation's web server may impose a lower limit for multipart uploads; in that case use arquivo_base64 or reduce the file.
Internal note and service-opening note900 characters.
Service order or appointment title160 characters.
Metrics and calendar period366 days.
Active keysDepending on the plan: 3 (PREMIUM), 10 (GOLD), unlimited (ENTERPRISE and trial).

Pace of bulk sending

The API sends each message at the moment it is called. For notices to many subscribers, spread the sends over time instead of firing hundreds of simultaneous calls, and adjust the key's per-minute limit to that pace. For large lists with tracking of deliveries and replies, the Sending automation screen already handles the pace on its own.


API version

Every response, including error responses, carries the header:

header
X-ChatISP-Api-Versao: 1

The same value appears in chave/info, in the servidor.api_versao field. The current version is 1. Record the value in the integration's logs: it helps support identify which version your system talked to.

The routes, field names and error codes documented here are stable. The API may gain routes, optional parameters and new response fields without changing version; that is why your system should ignore unknown fields instead of rejecting the response. Old fields are kept for existing integrations: for example, atendimentos/abrir still returns conversa_id, protocolo, estado and ja_existia at the top level, in addition to the full conversa object.


Security and best practices

Checklist

  • One key per integration, with a clear Name and only the scopes it needs.
  • Store the key in an environment variable or in the server's secrets vault, never in versioned code or in logs.
  • Always use HTTPS. On a self-hosted installation, configure a domain with a valid certificate before production.
  • A per-minute limit compatible with actual usage: it also protects against a runaway loop in your system.
  • Check ok and the HTTP status in every response; decide based on the error code.
  • On 429, respect Retry-After. On a network error or HTTP 5xx, retry reads with increasing waits. Do not retry sends blindly: a message that timed out may have been delivered, and retrying duplicates the send to the customer. Check the history first with conversas/mensagens.
  • Store the returned ids (conversa_id, mensagem_id, ticket_id) to match them with webhooks.
  • Cache the catalogs (departments, agents, tags, closing reasons) and refresh them from time to time, instead of querying them on every call.
  • Prefer webhooks to polling in a loop to find out what changed.
  • Disable or delete the keys of integrations that were shut down. To replace a key, create the new one, update the integration and only then delete the old one.

What gets recorded

Actions performed through the API appear in the panel with the key's name: the "API" event in the conversation timeline, notes as "API (key name): ...", transfers and closings with "API: key name", and service order comments with "[API: key name]". Messages sent through the API go out with author sistema, without an agent signature.

Common problems

SymptomLikely cause and solution
HTTP 401 autenticacao with the right keyThe header did not arrive as Authorization: Bearer cisp_.... Some proxies and libraries drop the header on redirects: call the https:// address directly.
HTTP 401 chave_invalidaKey deleted, inactive, or copied with a space or line break. Use Reveal and Copy again.
HTTP 404 rota on a call with JSONr was placed inside the JSON. Move it to the URL.
HTTP 403 permissaoTurn on in the key the scope given in the response's escopo field.
HTTP 403 planoPlan without the public API or license blocked. Check the license status in Account and license.
HTTP 400 conexao_id_obrigatorioThere is more than one connected number and the key has no Sending number. List them with conexoes/listar and provide conexao_id, or set the Sending number on the API key (Integrations, API keys).
HTTP 409 janela_fechadaOfficial API number with no message from the customer in the last 24 hours. Send an approved template with mensagens/enviar_modelo.
Certificate error in your systemA self-hosted installation without a domain responds with a self-signed certificate. Configure a domain with Let's Encrypt in Server.

Postman and Insomnia collection

To test the API without writing code, download the ready-made collection with the 37 routes, already with the method, sample parameters and a description of each one:

Download the collection (chatisp-api.postman_collection.json)

  1. In Postman, use Import and choose the file. In Insomnia, use Import in a collection; the Postman format is recognized.
  2. In the collection variables, fill in base_url with your panel address (for example https://atendimento.seuprovedor.com.br, without a trailing slash) and chave with your cisp_... key.
  3. First run Informações da chave (key information, chave/info) to confirm the address, key and scopes.
  4. Use the catalogs to discover the real ids of your installation before testing the write routes.

The collection's write routes act for real: they send messages to customers, close conversations, open service orders. Test with your own phone number and with a test key that has only the scopes it needs.