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) | Method | Scope | What it does |
|---|---|---|---|
| Catalogs: the ids the rest of the API asks for | |||
chave/info | GET | any key | Name, scopes and limit of the key; API version, time zone and server plan. |
conexoes/listar | GET | any key | WhatsApp numbers (QR and official API), state and twin pair. |
departamentos/listar | GET | any key | Departments, active and inactive, and which one receives new conversations. |
atendentes/listar | GET | any key | Agents with profile, departments, presence and pause. |
modelos/listar | GET | any key | Official API message templates and whether they can be sent through the API. |
etiquetas/listar | GET | any key | Conversation tags, with color. |
motivos/listar | GET | any key | Closing reasons. |
| Messages | |||
mensagens/enviar | POST | mensagens_enviar | Sends a text to a phone number or a conversation. |
mensagens/enviar_modelo | POST | mensagens_enviar | Sends a template approved by Meta (outside the 24-hour window). |
mensagens/enviar_arquivo | POST | mensagens_enviar | Sends an image, video, audio or document, with an optional caption. |
mensagens/arquivo | GET | conversas_ler | Downloads the file of a message (binary response, outside the JSON). |
| Conversations | |||
conversas/listar | GET | conversas_ler | Lists conversations with filters and pagination. |
conversas/consultar | GET | conversas_ler | One conversation (by id, protocol or phone) with its latest messages. |
conversas/mensagens | GET | conversas_ler | Message history paginated by cursor. |
conversas/transferir | POST | conversas_gerir | Transfers to a department or agent. |
conversas/encerrar | POST | conversas_gerir | Closes the service request, with an optional reason. |
conversas/etiquetas | POST | conversas_gerir | Sets, adds or removes tags. |
conversas/nota | POST | conversas_gerir | Saves an internal note (the team sees it, the customer does not). |
| Contacts and service requests | |||
atendimentos/abrir | POST | atendimentos_abrir | Puts a number in a department queue or straight with an agent, without sending anything to the customer. |
contatos/listar | GET | contatos | Contacts (one per number), with search and pagination. |
contatos/consultar | GET | contatos | One contact with its service history. |
contatos/renomear | POST | contatos | Saves the contact name for current and future conversations. |
| Service orders | |||
tickets/etapas | GET | tickets_ler | Stages of the service order board. |
tickets/tipos | GET | tickets_ler | Service order types. |
tickets/listar | GET | tickets_ler | Lists service orders with filters and pagination. |
tickets/consultar | GET | tickets_ler | One service order with its timeline. |
tickets/criar | POST | tickets_gerir | Opens a service order. |
tickets/mover | POST | tickets_gerir | Moves the service order to another stage. |
tickets/atribuir | POST | tickets_gerir | Sets or removes the assignee. |
tickets/comentar | POST | tickets_gerir | Adds a comment. |
| Calendar | |||
agenda/tipos | GET | agenda_ler | Appointment types. |
agenda/listar | GET | agenda_ler | Appointments in a period. |
agenda/consultar | GET | agenda_ler | One appointment. |
agenda/criar | POST | agenda_gerir | Books an appointment. |
agenda/concluir | POST | agenda_gerir | Completes an appointment, with an optional outcome. |
agenda/cancelar | POST | agenda_gerir | Cancels an appointment. |
| Metrics | |||
relatorios/resumo | GET | relatorios | Volumes, 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
| System | What to integrate | Routes and events |
|---|---|---|
| ERP / billing | Payment confirmation, bill copy, suspension and reactivation notice, boleto in PDF. | mensagens/enviar, mensagens/enviar_modelo, mensagens/enviar_arquivo |
| NOC / monitoring | Fiber 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 website | A "contact us" form that lands in the right department's queue; technical visit scheduling. | atendimentos/abrir, agenda/criar |
| CRM | Service history per customer, a record of each closed conversation with protocol, summary and reason. | contatos/consultar, conversas/mensagens, webhook conversa_encerrada |
| Work order system | Mirror ChatISP service orders, move stages as the work order progresses, assign a technician. | tickets/*, webhooks ticket_criado and ticket_atualizado |
| BI / management dashboard | Daily metrics, incremental sync of conversations. | relatorios/resumo, conversas/listar with atualizadas_desde |
| Internal automation | Tag 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
| Requirement | Details |
|---|---|
| Plan with public API Plan feature | The 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 plan | Keys 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 standing | With 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 permission | Creating, editing, revealing and deleting keys and webhooks requires the Integrations permission in the user's access profile (see Agents, profiles and permissions). |
| WhatsApp connection | The sending and service-opening routes need a connected connection. See WhatsApp connections. |
| Network | HTTPS 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
- In the side menu, open Integrations.
- 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.
- Click New key.
- 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.
- 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.
- Adjust the Per-minute limit ("Calls accepted per minute. 0 turns the limit off."). The default is 60 and the maximum is 10000.
- 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.
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
| Column | What it shows |
|---|---|
| Name | The name given to the key. |
| API key | The beginning of the key, for recognition. |
| Permissions | A label for each permission turned on. |
| Uses | Total authenticated calls made with the key. On hover, Last used appears. |
| Status | Enabled or Disabled. |
| Actions | Edit, 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 screen | Scope | What it allows | Routes |
|---|---|---|---|
| Customer service | |||
| Send messages | mensagens_enviar | Text, approved official API templates and files. | mensagens/enviar, mensagens/enviar_modelo, mensagens/enviar_arquivo |
| Look up conversations | conversas_ler | List and look up conversations, read the message history and download files. | conversas/listar, conversas/consultar, conversas/mensagens, mensagens/arquivo |
| Open service requests | atendimentos_abrir | Put a number in a department queue or straight with an agent. | atendimentos/abrir |
| Manage conversations | conversas_gerir | Transfer, close, tag and add notes to conversations. | conversas/transferir, conversas/encerrar, conversas/etiquetas, conversas/nota |
| Contacts | contatos | List, look up and rename contacts. | contatos/listar, contatos/consultar, contatos/renomear |
| Service orders and calendar | |||
| Look up service orders | tickets_ler | List service orders, see the timeline, stages and types. | tickets/etapas, tickets/tipos, tickets/listar, tickets/consultar |
| Manage service orders | tickets_gerir | Open, move between stages, assign and comment on service orders. | tickets/criar, tickets/mover, tickets/atribuir, tickets/comentar |
| Look up calendar | agenda_ler | List and look up appointments. | agenda/tipos, agenda/listar, agenda/consultar |
| Manage calendar | agenda_gerir | Schedule, complete and cancel appointments. | agenda/criar, agenda/concluir, agenda/cancelar |
| Management | |||
| Metrics | relatorios | Volumes, 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:
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:
Authorization: Bearer cisp_7f3a9c21d84e0b56a1f2c3d4e5f60718293a4b5c6d7e8f90
| Item | Where it goes | Required | Description |
|---|---|---|---|
Authorization | HTTP header | yes | Bearer, a space and the full key. The word Bearer is case-insensitive. |
r | query string (recommended) or form body | yes | The 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.
curl -s "https://atendimento.seuprovedor.com.br/api_publica.php?r=chave/info" \ -H "Authorization: Bearer $CHATISP_CHAVE"
<?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;
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"])
{
"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:
rmissing or not in therecurso/acaoformat: HTTP 404rota.Authorization: Bearer ...header missing or malformed: HTTP 401autenticacao.- Key nonexistent, deleted, inactive or not in the
cisp_...format: HTTP 401chave_invalida. - Plan without public API or license blocked: HTTP 403
plano. - The call is counted in the key's usage. If the per-minute limit was exceeded: HTTP 429
cota_excedida. - Body declared as JSON but invalid: HTTP 400
json_invalido. - Nonexistent route: HTTP 404
rota. - Write route called without POST: HTTP 405
metodo. - Key without the route's scope: HTTP 403
permissao, with the missing scope. - 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
metodowith"esperado":"POST".
Where parameters go
| Form | Header | When to use |
|---|---|---|
| Query string | none | Read filters in GET: ?r=conversas/listar&abertas=1&limite=20. |
| Form | application/x-www-form-urlencoded | The default format of curl -d. Lists repeat the field with brackets: variaveis[]=Maria&variaveis[]=R$ 99,90. |
| JSON | application/json | The whole body is a JSON object. Recommended for writes. r goes in the URL. |
| Multipart | multipart/form-data | File 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.
# 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
| Type | Accepts | Notes |
|---|---|---|
| integer | 42 or "42" | Ids, pagina, limite. Text that is not an integer: parametro_invalido with the campo. |
| boolean | true/false, 1/0, sim/nao, yes/no | Empty means the route's default. |
| list | JSON 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. |
| date | ISO 8601, YYYY-MM-DD or YYYY-MM-DD HH:MM:SS | See Dates and time zone. |
| text | UTF-8 | Leading 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:
| Situation | Body |
|---|---|
| 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.
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.
| HTTP | When |
|---|---|
| 200 | The operation was performed. |
| 400 | Missing or invalid parameter, unsuitable connection, invalid JSON. |
| 401 | No key, malformed header or invalid key. |
| 403 | Plan without the feature, license blocked or key without the scope. |
| 404 | Nonexistent route or record not found. |
| 405 | Write route called without POST. |
| 409 | Conflict with the current state: connection disconnected, conversation already closed, 24-hour window closed, service order board without stages. |
| 410 | The message file existed but is no longer on the server. |
| 413 | File too large. |
| 415 | Unsupported file type. |
| 429 | The key's per-minute limit was exceeded, with the Retry-After header. |
| 500 | Internal failure while saving a file. |
| 502 | WhatsApp 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:
| Field | Values |
|---|---|
conversation estado | fila (waiting for an agent), auto (in a support flow), ia (with the AI), humano (with an agent), encerrada (closed). |
message direcao | recebida (from the customer) or enviada (from the ISP). Internal notes never appear in the API. |
message autor | cliente, atendente, ia or sistema (flows, automatic notices, campaigns and messages sent through the API). |
message tipo | texto, imagem, audio, video, documento, localizacao, contato. |
message status | pendente, 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:
| Parameter | Type | Default | Description |
|---|---|---|---|
pagina | integer | 1 | Desired page, starting at 1. |
limite | integer | 50 | Items per page, from 1 to 100. Out of range: parametro_invalido with "maximo":100. |
The response includes the paginacao 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:
| Parameter | Description |
|---|---|
| no cursor | The most recent messages, in chronological order. |
antes_de | Messages with an id lower than the one given: to scroll back through the history. |
depois_de | Messages with an id higher than the one given: to fetch what arrived since the last read. |
limite | From 1 to 100, default 50. |
"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
| Format | Example | Interpretation |
|---|---|---|
| ISO 8601 with time zone | 2026-10-01T09:00:00-03:00, 2026-10-01T12:00:00Z | Converted to the server's time zone. Recommended. |
| Date and time without time zone | 2026-10-01 09:00:00 or 2026-10-01T09:00:00 | Read in the server's time zone. |
| Date only | 2026-10-01 | Start 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 it | How ChatISP reads it |
|---|---|
44999990000 or (44) 99999-0000 | Brazilian number: country code 55 is added. It becomes 5544999990000. |
4433330000 | Brazilian landline with area code (10 digits). It becomes 554433330000. |
5544999990000 | Already with 55: accepted as is. |
+55 44 99999-0000 | With + and 55: accepted as Brazilian. |
+351 912 345 678 | Another 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.
error | HTTP | Meaning and what to do |
|---|---|---|
| Access | ||
rota | 404 | r 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. |
autenticacao | 401 | The Authorization header is missing or not in the Bearer <key> format. |
chave_invalida | 401 | Key nonexistent, deleted or inactive; or copied only partially, with a space or a line break. |
plano | 403 | The plan does not include the public API or the license is blocked. The plano extra has the name of the current plan. |
cota_excedida | 429 | The key exceeded its per-minute limit. Extra cota_minuto; wait the seconds given in the Retry-After header. |
permissao | 403 | The key does not have the route's scope. The escopo extra says which one to turn on in the key. |
metodo | 405 | Write route called without POST. Extra esperado. |
json_invalido | 400 | The body was declared application/json, but is not a valid JSON object. |
| Parameters | ||
parametro_obrigatorio | 400 | A 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_invalido | 400 | Value in the wrong format (integer, boolean, list) or outside what is allowed. Extras campo and, depending on the case, aceitos or maximo. |
identificador_obrigatorio | 400 | The 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_invalida | 400 | A date that could not be read. Extra campo. |
periodo_invalido | 400 | End before start, or period longer than allowed (extra maximo_dias). |
telefone_invalido | 400 | Phone number outside the rules. See Phone numbers. |
texto_invalido | 400 | Text empty or longer than the route's limit (message, note, comment). Extra campo when it is not texto. |
titulo_invalido | 400 | Service order or appointment title empty or longer than 160 characters. |
nome_invalido | 400 | Contact name empty or longer than 120 characters. |
legenda_invalida | 400 | File caption longer than 1024 characters. |
link_invalido | 400 | Appointment link that does not start with http:// or https://, contains a space or exceeds 500 characters. |
| Records | ||
nao_encontrado | 404 | The requested record does not exist: conversation, message (or message without a file), contact, service order or appointment. |
conversa_invalida | 400 | The conversa_id given to link a service order does not exist. |
conversa_encerrada | 409 | Transferring or closing requires an open conversation. |
departamento_invalido | 400 | Department nonexistent or inactive. |
atendente_invalido | 400 | Agent nonexistent, inactive or deleted. |
motivo_invalido | 400 | Closing reason nonexistent or inactive. |
etiqueta_invalida | 400 | Some tag id does not exist. Extra campo (etiquetas, adicionar or remover). |
etapa_invalida | 400 | Service order stage nonexistent or inactive. |
sem_etapas | 409 | The service order board has no active stage to receive the new service order. Configure the stages in the panel. |
| Connection and sending | ||
sem_conexao | 400 | No conexao_id was given and there is no connected connection (of the type the route requires). |
conexao_id_obrigatorio | 400 | There 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_invalida | 400 | The 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_desconectada | 409 | The connection exists but is not connected (for a twin number, neither of the two is). |
modelo_invalido | 400 | Template nonexistent, from another connection or not approved by Meta. |
modelo_com_midia | 400 | The template has a media header (image, video, document), which the API does not send. |
variaveis_invalidas | 400 | variaveis is not a list, or values are missing. Extra esperadas with how many the template requires. |
janela_fechada | 409 | On 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. |
envio | 502 | WhatsApp 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_invalido | 400 | Upload failed, empty file or arquivo_base64 that is not valid base64. |
arquivo_grande | 413 or 400 | File 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_suportado | 415 | File type that WhatsApp does not accept (for example, executables). Extra mimetype detected from the content. |
arquivo_indisponivel | 410 | The message had a file, but it is no longer on the server (for example, removed from disk). |
arquivo | 500 | Failure 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:
| Header | Content |
|---|---|
X-RateLimit-Limit | The key's per-minute limit. |
X-RateLimit-Remaining | How many calls still fit in the current minute. |
Retry-After | Only in the 429 response: seconds until the next minute. |
A key with limit 0 does not receive the X-RateLimit-* headers.
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
| Item | Limit |
|---|---|
| Items per page | 1 to 100 (default 50). |
| Message text | 4096 characters. |
| File caption | 1024 characters. |
| Uploaded file | 45 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 note | 900 characters. |
| Service order or appointment title | 160 characters. |
| Metrics and calendar period | 366 days. |
| Active keys | Depending 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:
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
okand the HTTP status in every response; decide based on theerrorcode. - 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 withconversas/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
| Symptom | Likely cause and solution |
|---|---|
HTTP 401 autenticacao with the right key | The 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_invalida | Key deleted, inactive, or copied with a space or line break. Use Reveal and Copy again. |
HTTP 404 rota on a call with JSON | r was placed inside the JSON. Move it to the URL. |
HTTP 403 permissao | Turn on in the key the scope given in the response's escopo field. |
HTTP 403 plano | Plan without the public API or license blocked. Check the license status in Account and license. |
HTTP 400 conexao_id_obrigatorio | There 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_fechada | Official 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 system | A 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)
- In Postman, use Import and choose the file. In Insomnia, use Import in a collection; the Postman format is recognized.
- In the collection variables, fill in
base_urlwith your panel address (for examplehttps://atendimento.seuprovedor.com.br, without a trailing slash) andchavewith yourcisp_...key. - First run Informações da chave (key information,
chave/info) to confirm the address, key and scopes. - 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.