Service orders
Reference for the eight service order routes of the ChatISP API: look up the board's stages and types, list and follow service orders with their full timeline, and open, move, assign and comment on service orders from your ERP, your monitoring system or the subscriber portal.
Overview
A service order is an internal task for the internet service provider's (ISP's) team: a technical visit, a network repair, an installation, a billing adjustment. It lives on the panel's Service orders board, moves through stages until it is resolved or cancelled, and can be linked to a WhatsApp conversation. Through the API, another system sees and operates the same board, under the same rules as the panel: deadline, team notifications, trail in the originating conversation and webhooks.
Typical integration cases:
- Monitoring and NOC: the system that detects an outage at a business customer or at a distribution box opens the repair service order with priority, address and technical description already filled in.
- ERP: the work order created in the ERP becomes a service order on the team's board, and its completion in ChatISP flows back to the ERP through the webhook.
- Field team: the technician's app moves the service order to the execution stage, comments on what was done and marks it as resolved.
- Custom dashboard and BI: list of overdue service orders, service orders per assignee and incremental sync by the last update timestamp.
Routes
| Route | Method | Key permission | What it does |
|---|---|---|---|
tickets/etapas | GET | Look up service orders | Board stages, with the nature of each one. |
tickets/tipos | GET | Look up service orders | Service order types, with default priority and deadline. |
tickets/listar | GET | Look up service orders | Paginated list with filters. |
tickets/consultar | GET | Look up service orders | One service order with its entire timeline. |
tickets/criar | POST | Manage service orders | Opens a service order. |
tickets/mover | POST | Manage service orders | Moves the service order to another stage. |
tickets/atribuir | POST | Manage service orders | Sets or removes the assignee. |
tickets/comentar | POST | Manage service orders | Comments on the timeline. |
The permission is checked on the key, under Integrations, ChatISP API card. Manage service orders does not include reading: to open a service order and then follow it, check both. Authentication, call format, pagination and errors common to all routes are covered in the Introduction.
Who shows up as the author
The API key is not a team user. Therefore, in actions taken through the API:
- A service order opened through the API is created with
origemequal toapiand, on the timeline, gets a system event with the textAPI: <key name>, right after the opening event. - Comments are saved with the prefix
[API: <key name>]in the text, so the team knows where they came from. - Stage changes made through the API record the text
[API: <key name>]in the event, followed by the comment when there is one. - Events recorded by the API have a null
autor_idand an emptyautor; the origin is in the text. - The notes the service order leaves in the linked conversation cite the key as the author (for example, "moved to stage In progress by API: ERP").
Give the key a name the team recognizes, such as ERP, NOC or Field app. That is the name shown on the board, on the timeline and in the conversation.
Company-wide view
The key sees every service order, from all departments and assignees. The visibility rules of access profiles (who sees only their own team's service orders) apply to people in the panel, not to the API.
The service order object
Every route that returns a service order uses the same format. Dates in ISO 8601 with time zone; a field without a value comes as null (dates, ids) or empty text (names).
| Field | Type | Description |
|---|---|---|
id | integer | Internal service order identifier. Use it in calls as ticket_id. |
numero | text | Number the team sees on the board, in the OS-000128 format (older service orders keep their T- number). It also locates the service order in calls (numero parameter). |
titulo | text | Short title, up to 160 characters. |
descricao | text | Free description; empty when not filled in. |
tipo | object or null | {"id", "nome"} of the service order type; null when the service order has no type. |
etapa | object | {"id", "nome", "natureza"} of the current stage. The nature tells how the system treats the stage: see Natures, priorities and deadline. |
concluido | boolean | true when the current stage is final (nature resolvido or cancelado). |
prioridade | text | baixa, normal, alta or urgente. |
origem | text | Where the service order came from: manual (opened on the board), chat (opened from a conversation), ia (opened by the AI), copiloto (opened by the Copilot) or api. |
conversa_id | integer or null | Linked conversation, when there is one. |
protocolo | text or null | Protocol number of the linked conversation. |
cliente | object | {"telefone", "nome", "endereco"}. Phone with country code, digits only. All three may be empty in an internal service order with no customer. |
departamento_id / departamento | integer or null / text | Team that owns the service order. |
responsavel_id / responsavel | integer or null / text | Person in charge. null and empty when the service order is unassigned. |
prazo_em | date or null | Deadline for resolution. null when the service order has no deadline. |
prazo_situacao | text | sem_prazo, ok, atencao, vencido or concluido. Calculated at the time of the call. |
resumo_ia | text or null | Summary of the linked conversation written by the AI for whoever will carry out the service order. It arrives a few moments after opening; null while it does not exist or when there is no linked conversation. |
total_comentarios | integer | Number of comments on the timeline. |
total_anexos | integer | Number of files attached by the team. |
aberto_em | date | When the service order was opened. |
iniciado_em | date or null | First time the service order entered a stage with nature andamento. |
resolvido_em | date or null | When it entered a resolvido stage. |
fechado_em | date or null | When it entered a final stage (resolved or cancelled). |
ultima_atividade_em | date | Latest entry on the timeline (opening, stage, comment, attachment, edit, summary). This is the field used by the atualizados_desde filter and by the list order. |
erp | object or null | The service order in the provider's ERP (see In the provider's ERP in the system documentation). null when the service order has neither a linked ERP customer nor a record in any ERP. It carries erp_id (ERP of the linked customer, or null), id_externo_cliente and id_externo_contrato (identifiers of the customer and of the contract in the ERP, text or null) and ordens, the list of service orders opened in the ERP for this service order: zero or one per ERP, with the fields of the next table. |
Fields of erp.ordens[]
| Field | Type | Description |
|---|---|---|
erp_id / erp | integer / text | Identifier and name of the ERP, as registered in Integrations. |
tipo_erp | text | ixc, hubsoft, sgp, gesprov or pipesoft. |
id | text | Internal identifier of the service order in the ERP. |
numero | text | Service order number as the ERP shows it to the team over there. |
protocolo | text or null | Protocol of the occurrence or of the call, on ERPs that work with a protocol in addition to the number. |
situacao | text | Status in ChatISP's common vocabulary: aberta, em_analise, agendada, em_execucao, aguardando, concluida or desconhecida (the ERP returned something ChatISP does not recognize). |
situacao_erp | text or null | The raw status, as the ERP calls it. |
tecnico | text or null | Technician assigned in the ERP, when provided. |
agendado_em / concluido_em | date or null | Schedule and completion read from the ERP. |
reaproveitada | boolean | true when the service order here was linked to a service order that was already open in the ERP for the same customer and type, instead of opening another. |
acompanha | boolean | true when ChatISP checks the ERP periodically and completes the service order here when the ERP completes it; false on Gesprov, which only allows opening. |
ultima_consulta_em | date or null | Last successful check of the ERP. |
{
"id": 128,
"numero": "OS-000128",
"titulo": "No signal on Rua das Palmeiras",
"descricao": "ONU with no PON light since 09:40. Customer says the neighbor is also without internet.",
"tipo": {"id": 2, "nome": "Network repair"},
"etapa": {"id": 1, "nome": "Open", "natureza": "inicial"},
"concluido": false,
"prioridade": "urgente",
"origem": "api",
"conversa_id": 5310,
"protocolo": "20261001-0042",
"cliente": {
"telefone": "5544999990000",
"nome": "Maria Souza",
"endereco": "Rua das Palmeiras, 120, Centro"
},
"departamento_id": 3,
"departamento": "Technical support",
"responsavel_id": null,
"responsavel": "",
"prazo_em": "2026-10-01T18:15:00-03:00",
"prazo_situacao": "ok",
"resumo_ia": null,
"total_comentarios": 0,
"total_anexos": 0,
"erp": {
"erp_id": 1,
"id_externo_cliente": "8841",
"id_externo_contrato": "9120",
"ordens": [
{
"erp_id": 1,
"erp": "IXC Provedor",
"tipo_erp": "ixc",
"id": "4521",
"numero": "4521",
"protocolo": null,
"situacao": "aberta",
"situacao_erp": "A",
"tecnico": null,
"agendado_em": null,
"concluido_em": null,
"reaproveitada": false,
"acompanha": true,
"ultima_consulta_em": "2026-10-01T10:15:05-03:00"
}
]
},
"aberto_em": "2026-10-01T10:15:00-03:00",
"iniciado_em": null,
"resolvido_em": null,
"fechado_em": null,
"ultima_atividade_em": "2026-10-01T10:15:00-03:00"
}Natures, priorities and deadline
Board stages have free names and colors, configured by each ISP. What the system understands about each one is its nature. Always integrate by nature, never by name: "Resolved" at one ISP may be "Finished" at another.
Stage natures
| Nature | Panel label | What the system does |
|---|---|---|
inicial | Initial (intake) | Where the service order is born. A new service order enters the first active stage with initial nature (in board order); if there is none, the first active stage. |
andamento | In progress | The first entry into a stage like this records iniciado_em. |
aguardando | Waiting on others | Service order on hold waiting for something external (customer, supplier). The deadline keeps running. |
resolvido | Final: resolved | Final stage: records resolvido_em and fechado_em, stops the deadline and notifies whoever opened the service order. |
cancelado | Final: cancelled | Final stage: records fechado_em, stops the deadline and notifies whoever opened the service order. |
Taking a service order from a final stage to a non-final stage is a reopening: resolvido_em and fechado_em go back to null, the deadline starts running again and the timeline records a reaberto event.
Priorities
| Value | Panel label |
|---|---|
baixa | Low |
normal | Normal |
alta | High |
urgente | Urgent |
When a service order is opened without prioridade, the type's default priority applies; without a type, normal.
Deadline status
prazo_situacao | When |
|---|---|
sem_prazo | Open service order with no deadline set. |
ok | Deadline in the future, with time to spare. |
atencao | 2 hours or less remain, or less than 25% of the total time between opening and deadline. |
vencido | The deadline has passed and the service order is not in a final stage. |
concluido | The service order is in a final stage: the deadline stopped running. |
When the deadline passes with the service order still open, the system records the system event "Deadline passed without completion." on the timeline just once and notifies the assignee via WhatsApp (or the department team, if the service order is unassigned). This event updates ultima_atividade_em.
Board stages
/api_publica.php?r=tickets/etapasReturns all board stages, in column order, including disabled ones. No parameters. Use it to find the etapa_id that tickets/mover asks for, always choosing by natureza.
Field of etapas[] | Description |
|---|---|
id | Stage identifier. |
nome | Column name on the board. |
natureza | inicial, andamento, aguardando, resolvido or cancelado. |
cor | Column color in hexadecimal. |
ativa | false when the stage has been disabled. A disabled stage accepts neither new service orders nor moves. |
curl -s -G "https://atendimento.seuprovedor.com.br/api_publica.php" \ -H "Authorization: Bearer $CHATISP_CHAVE" \ --data-urlencode "r=tickets/etapas"
<?php
$url = 'https://atendimento.seuprovedor.com.br/api_publica.php?' . http_build_query([
'r' => 'tickets/etapas',
]);
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('CHATISP_CHAVE')],
]);
$resposta = json_decode(curl_exec($ch), true);import os, requests
resposta = requests.get(
"https://atendimento.seuprovedor.com.br/api_publica.php",
params={"r": "tickets/etapas"},
headers={"Authorization": "Bearer " + os.environ["CHATISP_CHAVE"]},
timeout=30,
).json(){
"ok": true,
"etapas": [
{"id": 1, "nome": "Open", "natureza": "inicial", "cor": "#38bdf8", "ativa": true},
{"id": 2, "nome": "Under review", "natureza": "andamento", "cor": "#8b5cf6", "ativa": true},
{"id": 3, "nome": "In progress", "natureza": "andamento", "cor": "#f59e0b", "ativa": true},
{"id": 4, "nome": "Waiting on customer", "natureza": "aguardando", "cor": "#64748b", "ativa": true},
{"id": 5, "nome": "Resolved", "natureza": "resolvido", "cor": "#10b981", "ativa": true},
{"id": 6, "nome": "Cancelled", "natureza": "cancelado", "cor": "#d23939", "ativa": true}
]
}Ids change from one installation to another, and the ISP can create, rename and reorder stages. Cache them for a few hours and query again if you receive etapa_invalida.
Service order types
/api_publica.php?r=tickets/tiposReturns the service order types registered in the module's Settings, including disabled ones. No parameters. The type defines the priority, deadline and team the service order receives when the opening call does not provide those fields.
Field of tipos[] | Description |
|---|---|
id | Type identifier, used in tipo_id. |
nome | Type name. |
ativo | false when disabled. A disabled type is rejected when opening (tipo_invalido). |
prioridade_padrao | Priority applied when the opening call does not provide one. |
prazo_horas | Hours until the deadline, counted from opening. 0 means no automatic deadline. |
departamento_id | Team that receives the service order when the opening call does not provide departamento_id; null if the type has no team. |
curl -s -G "https://atendimento.seuprovedor.com.br/api_publica.php" \ -H "Authorization: Bearer $CHATISP_CHAVE" \ --data-urlencode "r=tickets/tipos"
<?php
$url = 'https://atendimento.seuprovedor.com.br/api_publica.php?' . http_build_query([
'r' => 'tickets/tipos',
]);
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('CHATISP_CHAVE')],
]);
$resposta = json_decode(curl_exec($ch), true);import os, requests
resposta = requests.get(
"https://atendimento.seuprovedor.com.br/api_publica.php",
params={"r": "tickets/tipos"},
headers={"Authorization": "Bearer " + os.environ["CHATISP_CHAVE"]},
timeout=30,
).json(){
"ok": true,
"tipos": [
{"id": 1, "nome": "Technical visit", "ativo": true, "prioridade_padrao": "alta", "prazo_horas": 24, "departamento_id": 3},
{"id": 2, "nome": "Network repair", "ativo": true, "prioridade_padrao": "urgente", "prazo_horas": 8, "departamento_id": 3},
{"id": 3, "nome": "Installation", "ativo": true, "prioridade_padrao": "normal", "prazo_horas": 48, "departamento_id": null},
{"id": 4, "nome": "Billing", "ativo": true, "prioridade_padrao": "normal", "prazo_horas": 24, "departamento_id": 2},
{"id": 5, "nome": "Other", "ativo": true, "prioridade_padrao": "normal", "prazo_horas": 24, "departamento_id": null}
]
}List service orders
/api_publica.php?r=tickets/listarPaginated list of service orders, from the most recently active to the oldest (ultima_atividade_em order). All filters are optional and combine (all must hold at the same time). Without filters, every service order is returned, open and completed.
| Parameter | Type | Description |
|---|---|---|
situacao | text | todos (default), abertos (non-final stage) or concluidos (resolved and cancelled). |
etapa_id | integer | Only service orders in this stage. |
tipo_id | integer | Only service orders of this type. |
departamento_id | integer | Only service orders of this team. |
conversa_id | integer | Only service orders linked to this conversation. |
responsavel_id | integer | Only service orders of this person. With 0, only unassigned service orders. |
prioridade | text | baixa, normal, alta or urgente. |
atrasados | boolean | 1 (or true): only open service orders past their deadline. |
telefone | text | Only service orders of the customer with this number. Same phone rule as the rest of the API (without +, it is treated as Brazilian); the number is searched with and without the ninth digit. |
abertos_desde / abertos_ate | date | Opening date range. A date without time in abertos_ate covers up to 23:59:59 of that day. |
atualizados_desde | date | Only service orders with any timeline entry from that moment on. The basis for incremental sync. |
busca | text | Searches the number, title, description, customer name and address (up to 120 characters). |
pagina / limite | integer | Numbered pagination: limite from 1 to 100 (default 50). |
curl -s -G "https://atendimento.seuprovedor.com.br/api_publica.php" \ -H "Authorization: Bearer $CHATISP_CHAVE" \ --data-urlencode "r=tickets/listar" \ --data-urlencode "situacao=abertos" \ --data-urlencode "departamento_id=3" \ --data-urlencode "atrasados=1" \ --data-urlencode "limite=20"
<?php
$url = 'https://atendimento.seuprovedor.com.br/api_publica.php?' . http_build_query([
'r' => 'tickets/listar',
'situacao' => 'abertos',
'departamento_id' => 3,
'atrasados' => '1',
'limite' => 20,
]);
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('CHATISP_CHAVE')],
]);
$resposta = json_decode(curl_exec($ch), true);import os, requests
resposta = requests.get(
"https://atendimento.seuprovedor.com.br/api_publica.php",
params={"r": "tickets/listar", "situacao": "abertos", "departamento_id": 3, "atrasados": "1", "limite": 20},
headers={"Authorization": "Bearer " + os.environ["CHATISP_CHAVE"]},
timeout=30,
).json(){
"ok": true,
"tickets": [
{
"id": 121,
"numero": "OS-000121",
"titulo": "Connector replacement at CTO 14",
"etapa": {"id": 3, "nome": "In progress", "natureza": "andamento"},
"concluido": false,
"prioridade": "alta",
"responsavel_id": 7,
"responsavel": "Carlos Lima",
"prazo_em": "2026-10-01T08:00:00-03:00",
"prazo_situacao": "vencido",
"ultima_atividade_em": "2026-10-01T09:12:44-03:00",
"...": "other fields of the ticket object"
}
],
"paginacao": {"pagina": 1, "limite": 20, "total": 1, "paginas": 1}
}Incremental sync
To keep a mirror of the service orders in your system, store the time of each run and, on the next one, request atualizados_desde with that time (preferably with a few minutes of margin, saving by id to avoid duplicates). Opening, stage change, assignee change, comment, attachment, edit, AI summary and missed deadline all update the timestamp. The server's exact time is in servidor.agora of the chave/info route.
If your system only needs to react when something changes, a webhook is more efficient than polling: the ticket_criado and ticket_atualizado events arrive right away. See Webhooks.
Look up a service order
/api_publica.php?r=tickets/consultarReturns the service order and its complete timeline, from the oldest event to the most recent.
| Parameter | Type | Required | Description |
|---|---|---|---|
ticket_id | integer | one of the two | Service order id (also accepted as id). |
numero | text | one of the two | Service order number, such as OS-000128. Useful when the number was typed by a person. |
curl -s -G "https://atendimento.seuprovedor.com.br/api_publica.php" \ -H "Authorization: Bearer $CHATISP_CHAVE" \ --data-urlencode "r=tickets/consultar" \ --data-urlencode "numero=OS-000128"
<?php
$url = 'https://atendimento.seuprovedor.com.br/api_publica.php?' . http_build_query([
'r' => 'tickets/consultar',
'numero' => 'OS-000128',
]);
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('CHATISP_CHAVE')],
]);
$resposta = json_decode(curl_exec($ch), true);import os, requests
resposta = requests.get(
"https://atendimento.seuprovedor.com.br/api_publica.php",
params={"r": "tickets/consultar", "numero": "OS-000128"},
headers={"Authorization": "Bearer " + os.environ["CHATISP_CHAVE"]},
timeout=30,
).json(){
"ok": true,
"ticket": {"id": 128, "numero": "OS-000128", "...": "full ticket object"},
"eventos": [
{
"id": 901, "tipo": "aberto", "autor_id": null, "autor": "", "texto": "",
"dados": {"origem": "api", "etapa": "Open"}, "anexos": [],
"criado_em": "2026-10-01T10:15:00-03:00"
},
{
"id": 902, "tipo": "sistema", "autor_id": null, "autor": "", "texto": "API: NOC",
"dados": [], "anexos": [], "criado_em": "2026-10-01T10:15:00-03:00"
},
{
"id": 910, "tipo": "responsavel", "autor_id": 4, "autor": "Paula Andrade", "texto": "",
"dados": {"de": null, "de_nome": "", "para": 7, "para_nome": "Carlos Lima"}, "anexos": [],
"criado_em": "2026-10-01T10:31:20-03:00"
},
{
"id": 915, "tipo": "etapa", "autor_id": 7, "autor": "Carlos Lima", "texto": "Team on the way",
"dados": {"de": 1, "de_nome": "Open", "de_cor": "#38bdf8", "para": 3, "para_nome": "In progress",
"para_cor": "#f59e0b", "natureza": "andamento"},
"anexos": [], "criado_em": "2026-10-01T10:40:02-03:00"
},
{
"id": 921, "tipo": "comentario", "autor_id": 7, "autor": "Carlos Lima",
"texto": "Fiber broken at pole 32. Splice done, signal back to normal.",
"dados": [], "anexos": [{"nome": "emenda.jpg", "tamanho": 284113}],
"criado_em": "2026-10-01T12:05:51-03:00"
}
]
}Field of eventos[] | Description |
|---|---|
id | Event identifier (ascending). |
tipo | Event type: table below. |
autor_id / autor | Who did it. null and empty for the system, the AI and actions taken through the API. |
texto | Text of the comment, of the AI summary, of the comment that accompanied the stage change or of the system message. |
dados | Structured event details (object); empty list when there are none. |
anexos | Files attached along with the event: {"nome", "tamanho"} (size in bytes). Attachments are downloaded through the panel. |
criado_em | When it happened. |
Timeline event types
tipo | When it appears | Content of dados and texto |
|---|---|---|
aberto | Service order opened. | origem and etapa (name of the initial stage). |
responsavel | Assignee set, changed or removed; also when a team member moves an unassigned service order to a working stage and becomes its owner. | de, de_nome, para, para_nome. Null para = the service order became unassigned. |
etapa | Stage change. | de, de_nome, de_cor, para, para_nome, para_cor, natureza (of the destination stage). The comment that accompanied the change comes in texto. |
reaberto | Exit from a final stage to a non-final stage. | Same fields as etapa. |
comentario | Comment from the team or the API. | Text in texto; the comment's files in anexos. |
anexo | File attached without a comment, uploaded from the computer or brought from the conversation. | File nome, or origem equal to conversa when it came from the chat. |
tipo | Service order type changed in the panel. | de_nome, para_nome. |
prioridade | Priority changed in the panel. | de, para. |
prazo | Deadline changed in the panel. | de, para, as short date and time text (the format shown on screen). |
editado | Other fields edited in the panel (title, description, customer, address, team). | campos (list of changed fields) and detalhe. |
ia | Summary of the linked conversation, generated by the AI. | Summary in texto. |
erp | What happened in the provider's ERP: opening, link to a service order that already existed, failure to open, change of status, technician or schedule, and completion. | dados.acao (aberta, vinculada, falha, situacao or concluida), dados.erp_nome, dados.os_numero and, depending on the action, the new status, the technician, the schedule or the failure reason (erro and mensagem). |
sistema | Automatic entry: the API: <key name> mark when opened through the API, or "Deadline passed without completion.". | Text in texto; for the missed deadline, dados.prazo. |
Handle unknown event types without breaking the integration: new types may appear as the board evolves. Show the texto when present and ignore the rest.
Open a service order
/api_publica.php?r=tickets/criarOpens a service order in the board's initial stage. Only the title is required; the rest completes the service order and, when omitted, is filled in from the type or the linked conversation.
| Parameter | Type | Required | Description |
|---|---|---|---|
titulo | text | yes | From 1 to 160 characters. |
descricao | text | no | Up to 20,000 characters. Put here everything the technician needs: symptoms, equipment, signal, address landmarks. |
tipo_id | integer | no | Service order type (service orders/tipos). A nonexistent or disabled type returns tipo_invalido. |
prioridade | text | no | baixa, normal, alta or urgente. Without it, the type's priority applies; without a type, normal. |
prazo_em | date | no | Explicit deadline. Without it, the deadline is the opening time plus the type's prazo_horas (type with 0 = no deadline); without a type, 24 hours. |
conversa_id | integer | no | Originating conversation. Links the service order to the conversation and fills in the customer's phone and name when not provided. |
telefone | text | no | Customer's WhatsApp. Same phone rule as the rest of the API. |
nome_cliente | text | no | Customer name, up to 190 characters. |
endereco | text | no | Service address, up to 255 characters. |
departamento_id | integer | no | Owning team. Without it, the type's team applies. A nonexistent department returns departamento_invalido. |
responsavel_id | integer | no | Person in charge (atendentes/listar). Without it, the service order is created unassigned. A nonexistent or inactive user returns atendente_invalido. |
erp_abrir | 0 or 1 | no | Opening of the service order in the provider's ERP as well. 1 requests the opening on types with the Ask when opening behavior; 0 holds the opening on types with Always: opens in the ERP as well. When omitted, the type's behavior applies (Always opens, Ask does not). It only has an effect with service orders turned on in the ERP card, the type mapped to the ERP and an identified ERP customer (through the conversa_id of a conversation with a recognized subscriber). The result comes back in erp. |
curl -s -X POST "https://atendimento.seuprovedor.com.br/api_publica.php?r=tickets/criar" \
-H "Authorization: Bearer $CHATISP_CHAVE" \
-H "Content-Type: application/json" \
-d '{"titulo": "No signal on Rua das Palmeiras", "descricao": "ONU with no PON light since 09:40. Neighbor also without internet.", "tipo_id": 2, "prioridade": "urgente", "telefone": "44999990000", "nome_cliente": "Maria Souza", "endereco": "Rua das Palmeiras, 120, Centro", "departamento_id": 3}'<?php
$dados = [
'titulo' => 'No signal on Rua das Palmeiras',
'descricao' => 'ONU with no PON light since 09:40. Neighbor also without internet.',
'tipo_id' => 2,
'prioridade' => 'urgente',
'telefone' => '44999990000',
'nome_cliente' => 'Maria Souza',
'endereco' => 'Rua das Palmeiras, 120, Centro',
'departamento_id' => 3,
];
$ch = curl_init('https://atendimento.seuprovedor.com.br/api_publica.php?r=tickets/criar');
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($dados, JSON_UNESCAPED_UNICODE),
]);
$resposta = json_decode(curl_exec($ch), true);import os, requests
resposta = requests.post(
"https://atendimento.seuprovedor.com.br/api_publica.php",
params={"r": "tickets/criar"},
headers={"Authorization": "Bearer " + os.environ["CHATISP_CHAVE"]},
json={"titulo": "No signal on Rua das Palmeiras", "descricao": "ONU with no PON light since 09:40. Neighbor also without internet.", "tipo_id": 2, "prioridade": "urgente", "telefone": "44999990000", "nome_cliente": "Maria Souza", "endereco": "Rua das Palmeiras, 120, Centro", "departamento_id": 3},
timeout=30,
).json()The response brings the full service order in the service order object format:
{
"ok": true,
"ticket": {
"id": 128,
"numero": "OS-000128",
"titulo": "No signal on Rua das Palmeiras",
"etapa": {"id": 1, "nome": "Open", "natureza": "inicial"},
"prioridade": "urgente",
"origem": "api",
"prazo_em": "2026-10-01T18:15:00-03:00",
"prazo_situacao": "ok",
"...": "other fields of the ticket object"
}
}What happens on opening
- Number and stage: the service order receives its
OS-number (older service orders keep theirT-number) and enters the board's initial stage. The timeline records theabertoevent (withorigemequal toapi) and the system eventAPI: <key name>. - Deadline and priority from the type: without
prazo_emand withoutprioridade, the type's defaults apply. The factory Network repair type, for example, gives urgent priority and an 8-hour deadline. - Assignee: with a valid
responsavel_id, the timeline records theresponsavelevent. - Team notifications: with an assignee, that person receives "Service order assigned to you" via WhatsApp. Without an assignee and with a team, the department's members receive "New service order for the team". WhatsApp notifications go to people with a verified WhatsApp and notifications turned on in My account.
- Trail in the linked conversation: with
conversa_id, the conversation timeline gets the service order entry (number and title) and, with the AI configured and the conversation having actual messages, the AI prepares the conversation summary for the technician, which appears inresumo_iaand as aniaevent on the service order timeline. The customer sees none of this. - Service order in the ERP: according to the type's behavior and
erp_abrir, the service order is also opened in the provider's ERP. First, ChatISP checks whether the customer already has an open service order of the same type there and, if so, links the one here to it (reaproveitada: true) instead of opening another. Number and status come back inerp.ordens; a failure to open does not prevent the creation of the service order here and is recorded on the timeline as anerpevent. Afterwards, each change in the ERP becomes anerpevent and triggersticket_atualizado; completion in the ERP moves the service order to the stage withresolvidonature. - Webhook: the
ticket_criadoevent goes out to the webhooks subscribed to it.
The service order is internal: nothing is sent to the customer. To let the customer know the technical team has been dispatched, send a message through the mensagens/enviar route. The recipe NOC opens a service order and notifies the customer shows the complete flow.
Move to another stage
/api_publica.php?r=tickets/moverMoves the service order to another board stage, with an optional comment. It is the same action as dragging the card in the panel.
| Parameter | Type | Required | Description |
|---|---|---|---|
ticket_id or numero | integer / text | yes | Which service order. |
etapa_id | integer | yes | Destination stage (service orders/etapas). It must be active. |
comentario | text | no | Up to 1,900 characters. Stored in the stage change event, after the [API: <key name>] mark, and, shortened, in the note on the linked conversation. |
curl -s -X POST "https://atendimento.seuprovedor.com.br/api_publica.php?r=tickets/mover" \
-H "Authorization: Bearer $CHATISP_CHAVE" \
-H "Content-Type: application/json" \
-d '{"numero": "OS-000128", "etapa_id": 5, "comentario": "Splice done at pole 32, signal back to normal."}'<?php
$dados = [
'numero' => 'OS-000128',
'etapa_id' => 5,
'comentario' => 'Splice done at pole 32, signal back to normal.',
];
$ch = curl_init('https://atendimento.seuprovedor.com.br/api_publica.php?r=tickets/mover');
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($dados, JSON_UNESCAPED_UNICODE),
]);
$resposta = json_decode(curl_exec($ch), true);import os, requests
resposta = requests.post(
"https://atendimento.seuprovedor.com.br/api_publica.php",
params={"r": "tickets/mover"},
headers={"Authorization": "Bearer " + os.environ["CHATISP_CHAVE"]},
json={"numero": "OS-000128", "etapa_id": 5, "comentario": "Splice done at pole 32, signal back to normal."},
timeout=30,
).json(){
"ok": true,
"ticket": {
"id": 128,
"numero": "OS-000128",
"etapa": {"id": 5, "nome": "Resolved", "natureza": "resolvido"},
"concluido": true,
"prazo_situacao": "concluido",
"resolvido_em": "2026-10-01T12:06:10-03:00",
"fechado_em": "2026-10-01T12:06:10-03:00",
"...": "other fields of the ticket object"
}
}Move rules
- The key does not become the owner. In the panel, whoever moves an unassigned service order to a working stage becomes its assignee. Through the API this does not happen: the service order stays unassigned until someone takes it or until you call service orders/atribuir.
- Same stage: moving to the stage the service order is already in records nothing and returns the service order as is.
- Dates: the first entry into an
andamentostage recordsiniciado_em; aresolvidostage recordsresolvido_emandfechado_em;canceladorecordsfechado_em. Leaving a final stage is a reopening (reabertoevent). - Notifications: on entering a final stage, whoever opened the service order in the panel is notified via WhatsApp. Service orders opened through the API do not have a person as author, so this notification is not sent.
- Linked conversation: while the conversation is open, it gets an internal note "Service order OS-000128 moved to stage Resolved by API: <key name>." followed by the comment. A closed conversation gets no note.
- Webhook:
ticket_atualizadogoes out withmudancaequal toetapa(orreaberto).
To complete a service order without depending on the stage name, look in tickets/etapas for the first active stage with nature resolvido and use its id.
Assign the person in charge
/api_publica.php?r=tickets/atribuirSets, changes or removes the service order's assignee.
| Parameter | Type | Required | Description |
|---|---|---|---|
ticket_id or numero | integer / text | yes | Which service order. |
responsavel_id | integer | yes | Id of the person (atendentes/listar), who must be active. 0 removes the owner: the service order goes back to unassigned. |
curl -s -X POST "https://atendimento.seuprovedor.com.br/api_publica.php?r=tickets/atribuir" \
-H "Authorization: Bearer $CHATISP_CHAVE" \
-H "Content-Type: application/json" \
-d '{"ticket_id": 128, "responsavel_id": 7}'<?php
$dados = [
'ticket_id' => 128,
'responsavel_id' => 7,
];
$ch = curl_init('https://atendimento.seuprovedor.com.br/api_publica.php?r=tickets/atribuir');
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($dados, JSON_UNESCAPED_UNICODE),
]);
$resposta = json_decode(curl_exec($ch), true);import os, requests
resposta = requests.post(
"https://atendimento.seuprovedor.com.br/api_publica.php",
params={"r": "tickets/atribuir"},
headers={"Authorization": "Bearer " + os.environ["CHATISP_CHAVE"]},
json={"ticket_id": 128, "responsavel_id": 7},
timeout=30,
).json(){
"ok": true,
"ticket": {
"id": 128,
"numero": "OS-000128",
"responsavel_id": 7,
"responsavel": "Carlos Lima",
"...": "other fields of the ticket object"
}
}- Assigning to the person who is already the assignee records nothing.
- The new assignee receives "Service order assigned to you" via WhatsApp (if they have a verified WhatsApp and notifications turned on).
- The timeline records the
responsavelevent; the linked conversation, if open, gets the corresponding internal note. - The
ticket_atualizadowebhook goes out withmudancaequal toresponsavel.
Comment
/api_publica.php?r=tickets/comentarAdds a comment to the service order timeline, without changing stage or assignee. Good for reporting back to the board what happened in your system: work order closed in the ERP, signal reading collected by monitoring, supplier response.
| Parameter | Type | Required | Description |
|---|---|---|---|
ticket_id or numero | integer / text | yes | Which service order. |
texto | text | yes | From 1 to 19,000 characters. Saved with the prefix [API: <key name>]. |
curl -s -X POST "https://atendimento.seuprovedor.com.br/api_publica.php?r=tickets/comentar" \
-H "Authorization: Bearer $CHATISP_CHAVE" \
-H "Content-Type: application/json" \
-d '{"numero": "OS-000128", "texto": "ONU signal back to normal: -19.8 dBm at 12:04."}'<?php
$dados = [
'numero' => 'OS-000128',
'texto' => 'ONU signal back to normal: -19.8 dBm at 12:04.',
];
$ch = curl_init('https://atendimento.seuprovedor.com.br/api_publica.php?r=tickets/comentar');
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($dados, JSON_UNESCAPED_UNICODE),
]);
$resposta = json_decode(curl_exec($ch), true);import os, requests
resposta = requests.post(
"https://atendimento.seuprovedor.com.br/api_publica.php",
params={"r": "tickets/comentar"},
headers={"Authorization": "Bearer " + os.environ["CHATISP_CHAVE"]},
json={"numero": "OS-000128", "texto": "ONU signal back to normal: -19.8 dBm at 12:04."},
timeout=30,
).json(){
"ok": true,
"evento_id": 933,
"ticket": {
"id": 128,
"numero": "OS-000128",
"total_comentarios": 2,
"...": "other fields of the ticket object"
}
}The linked conversation, if open, receives an internal note with the beginning of the comment (up to 200 characters). The ticket_atualizado webhook goes out with mudanca equal to comentario and the comentario field with up to 2,000 characters of the text.
Attaching files to a service order is a panel action. Through the API, put the file link in the comment text.
Service order webhooks
Two events cover the service order lifecycle, whether the change comes from the panel, the AI, the Copilot or the API itself:
| Event | When | mudanca |
|---|---|---|
ticket_criado | Service order opened. | (none) |
ticket_atualizado | Stage change, reopening, assignee change or comment. | etapa, reaberto, responsavel or comentario (the latter with the comentario field). |
The body carries a stable summary of the service order in dados: ticket_id, numero, titulo, etapa_id, etapa, natureza, prioridade, origem, conversa_id, telefone, nome_cliente, departamento_id, responsavel_id, responsavel and prazo_em.
{
"evento": "ticket_atualizado",
"emitido_em": "2026-10-01T12:06:10-03:00",
"dados": {
"ticket_id": 128,
"numero": "OS-000128",
"titulo": "No signal on Rua das Palmeiras",
"etapa_id": 5,
"etapa": "Resolved",
"natureza": "resolvido",
"prioridade": "urgente",
"origem": "api",
"conversa_id": 5310,
"telefone": "5544999990000",
"nome_cliente": "Maria Souza",
"departamento_id": 3,
"responsavel_id": 7,
"responsavel": "Carlos Lima",
"prazo_em": "2026-10-01T18:15:00-03:00",
"mudanca": "etapa"
}
}Edits made in the panel (title, type, priority, deadline, customer), attachments and the AI summary do not generate webhooks; for those, use tickets/listar with atualizados_desde. The complete content is in Service order event data; signature, delivery and retries in Webhooks.
Service order route errors
In addition to the errors common to the whole API (authentication, permission, plan, quota, method), described in Errors:
error | HTTP | Routes | Cause |
|---|---|---|---|
permissao | 403 | all | The key lacks Look up service orders (read) or Manage service orders (write). The escopo field tells which one is missing. |
identificador_obrigatorio | 400 | consultar, mover, atribuir, comentar | Both ticket_id and numero are missing. |
nao_encontrado | 404 | consultar, mover, atribuir, comentar | No service order with that id or number. |
titulo_invalido | 400 | criar | Empty title or longer than 160 characters. |
conversa_invalida | 400 | criar | conversa_id does not exist. |
telefone_invalido | 400 | criar, listar | The phone is not a valid WhatsApp number. |
sem_etapas | 409 | criar | The board has no active stage to receive the service order. |
etapa_invalida | 400 | mover | Nonexistent or disabled stage. |
parametro_obrigatorio | 400 | mover, atribuir | etapa_id or responsavel_id is missing (see campo). |
tipo_invalido | 400 | criar | Nonexistent or disabled type. |
departamento_invalido | 400 | criar | Nonexistent department. |
atendente_invalido | 400 | criar, atribuir | Nonexistent or inactive user. |
texto_invalido | 400 | comentar | Empty text or over 19,000 characters. |
parametro_invalido | 400 | listar, criar | Value outside the accepted range: unknown situacao or prioridade (the list comes in aceitos), number with letters, description over 20,000 characters, limite above 100. |
data_invalida | 400 | listar, criar | Date that could not be parsed (see campo). |
{"ok": false, "error": "etapa_invalida"}