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

RouteMethodKey permissionWhat it does
tickets/etapasGETLook up service ordersBoard stages, with the nature of each one.
tickets/tiposGETLook up service ordersService order types, with default priority and deadline.
tickets/listarGETLook up service ordersPaginated list with filters.
tickets/consultarGETLook up service ordersOne service order with its entire timeline.
tickets/criarPOSTManage service ordersOpens a service order.
tickets/moverPOSTManage service ordersMoves the service order to another stage.
tickets/atribuirPOSTManage service ordersSets or removes the assignee.
tickets/comentarPOSTManage service ordersComments 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 origem equal to api and, on the timeline, gets a system event with the text API: <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_id and an empty autor; 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).

FieldTypeDescription
idintegerInternal service order identifier. Use it in calls as ticket_id.
numerotextNumber 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).
titulotextShort title, up to 160 characters.
descricaotextFree description; empty when not filled in.
tipoobject or null{"id", "nome"} of the service order type; null when the service order has no type.
etapaobject{"id", "nome", "natureza"} of the current stage. The nature tells how the system treats the stage: see Natures, priorities and deadline.
concluidobooleantrue when the current stage is final (nature resolvido or cancelado).
prioridadetextbaixa, normal, alta or urgente.
origemtextWhere 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_idinteger or nullLinked conversation, when there is one.
protocolotext or nullProtocol number of the linked conversation.
clienteobject{"telefone", "nome", "endereco"}. Phone with country code, digits only. All three may be empty in an internal service order with no customer.
departamento_id / departamentointeger or null / textTeam that owns the service order.
responsavel_id / responsavelinteger or null / textPerson in charge. null and empty when the service order is unassigned.
prazo_emdate or nullDeadline for resolution. null when the service order has no deadline.
prazo_situacaotextsem_prazo, ok, atencao, vencido or concluido. Calculated at the time of the call.
resumo_iatext or nullSummary 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_comentariosintegerNumber of comments on the timeline.
total_anexosintegerNumber of files attached by the team.
aberto_emdateWhen the service order was opened.
iniciado_emdate or nullFirst time the service order entered a stage with nature andamento.
resolvido_emdate or nullWhen it entered a resolvido stage.
fechado_emdate or nullWhen it entered a final stage (resolved or cancelled).
ultima_atividade_emdateLatest 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.
erpobject or nullThe 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[]

FieldTypeDescription
erp_id / erpinteger / textIdentifier and name of the ERP, as registered in Integrations.
tipo_erptextixc, hubsoft, sgp, gesprov or pipesoft.
idtextInternal identifier of the service order in the ERP.
numerotextService order number as the ERP shows it to the team over there.
protocolotext or nullProtocol of the occurrence or of the call, on ERPs that work with a protocol in addition to the number.
situacaotextStatus in ChatISP's common vocabulary: aberta, em_analise, agendada, em_execucao, aguardando, concluida or desconhecida (the ERP returned something ChatISP does not recognize).
situacao_erptext or nullThe raw status, as the ERP calls it.
tecnicotext or nullTechnician assigned in the ERP, when provided.
agendado_em / concluido_emdate or nullSchedule and completion read from the ERP.
reaproveitadabooleantrue 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.
acompanhabooleantrue 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_emdate or nullLast successful check of the ERP.
JSON (service order object)
{
  "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

NaturePanel labelWhat the system does
inicialInitial (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.
andamentoIn progressThe first entry into a stage like this records iniciado_em.
aguardandoWaiting on othersService order on hold waiting for something external (customer, supplier). The deadline keeps running.
resolvidoFinal: resolvedFinal stage: records resolvido_em and fechado_em, stops the deadline and notifies whoever opened the service order.
canceladoFinal: cancelledFinal 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

ValuePanel label
baixaLow
normalNormal
altaHigh
urgenteUrgent

When a service order is opened without prioridade, the type's default priority applies; without a type, normal.

Deadline status

prazo_situacaoWhen
sem_prazoOpen service order with no deadline set.
okDeadline in the future, with time to spare.
atencao2 hours or less remain, or less than 25% of the total time between opening and deadline.
vencidoThe deadline has passed and the service order is not in a final stage.
concluidoThe 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

GET/api_publica.php?r=tickets/etapas

Returns 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
idStage identifier.
nomeColumn name on the board.
naturezainicial, andamento, aguardando, resolvido or cancelado.
corColumn color in hexadecimal.
ativafalse when the stage has been disabled. A disabled stage accepts neither new service orders nor moves.
curl
curl -s -G "https://atendimento.seuprovedor.com.br/api_publica.php" \
  -H "Authorization: Bearer $CHATISP_CHAVE" \
  --data-urlencode "r=tickets/etapas"
PHP
<?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);
Python
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()
Response
{
  "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

GET/api_publica.php?r=tickets/tipos

Returns 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
idType identifier, used in tipo_id.
nomeType name.
ativofalse when disabled. A disabled type is rejected when opening (tipo_invalido).
prioridade_padraoPriority applied when the opening call does not provide one.
prazo_horasHours until the deadline, counted from opening. 0 means no automatic deadline.
departamento_idTeam that receives the service order when the opening call does not provide departamento_id; null if the type has no team.
curl
curl -s -G "https://atendimento.seuprovedor.com.br/api_publica.php" \
  -H "Authorization: Bearer $CHATISP_CHAVE" \
  --data-urlencode "r=tickets/tipos"
PHP
<?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);
Python
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()
Response
{
  "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

GET/api_publica.php?r=tickets/listar

Paginated 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.

ParameterTypeDescription
situacaotexttodos (default), abertos (non-final stage) or concluidos (resolved and cancelled).
etapa_idintegerOnly service orders in this stage.
tipo_idintegerOnly service orders of this type.
departamento_idintegerOnly service orders of this team.
conversa_idintegerOnly service orders linked to this conversation.
responsavel_idintegerOnly service orders of this person. With 0, only unassigned service orders.
prioridadetextbaixa, normal, alta or urgente.
atrasadosboolean1 (or true): only open service orders past their deadline.
telefonetextOnly 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_atedateOpening date range. A date without time in abertos_ate covers up to 23:59:59 of that day.
atualizados_desdedateOnly service orders with any timeline entry from that moment on. The basis for incremental sync.
buscatextSearches the number, title, description, customer name and address (up to 120 characters).
pagina / limiteintegerNumbered pagination: limite from 1 to 100 (default 50).
curl
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
<?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);
Python
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()
Response
{
  "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

GET/api_publica.php?r=tickets/consultar

Returns the service order and its complete timeline, from the oldest event to the most recent.

ParameterTypeRequiredDescription
ticket_idintegerone of the twoService order id (also accepted as id).
numerotextone of the twoService order number, such as OS-000128. Useful when the number was typed by a person.
curl
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
<?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);
Python
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()
Response
{
  "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
idEvent identifier (ascending).
tipoEvent type: table below.
autor_id / autorWho did it. null and empty for the system, the AI and actions taken through the API.
textoText of the comment, of the AI summary, of the comment that accompanied the stage change or of the system message.
dadosStructured event details (object); empty list when there are none.
anexosFiles attached along with the event: {"nome", "tamanho"} (size in bytes). Attachments are downloaded through the panel.
criado_emWhen it happened.

Timeline event types

tipoWhen it appearsContent of dados and texto
abertoService order opened.origem and etapa (name of the initial stage).
responsavelAssignee 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.
etapaStage 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.
reabertoExit from a final stage to a non-final stage.Same fields as etapa.
comentarioComment from the team or the API.Text in texto; the comment's files in anexos.
anexoFile 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.
tipoService order type changed in the panel.de_nome, para_nome.
prioridadePriority changed in the panel.de, para.
prazoDeadline changed in the panel.de, para, as short date and time text (the format shown on screen).
editadoOther fields edited in the panel (title, description, customer, address, team).campos (list of changed fields) and detalhe.
iaSummary of the linked conversation, generated by the AI.Summary in texto.
erpWhat 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).
sistemaAutomatic 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

POST/api_publica.php?r=tickets/criar

Opens 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.

ParameterTypeRequiredDescription
titulotextyesFrom 1 to 160 characters.
descricaotextnoUp to 20,000 characters. Put here everything the technician needs: symptoms, equipment, signal, address landmarks.
tipo_idintegernoService order type (service orders/tipos). A nonexistent or disabled type returns tipo_invalido.
prioridadetextnobaixa, normal, alta or urgente. Without it, the type's priority applies; without a type, normal.
prazo_emdatenoExplicit 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_idintegernoOriginating conversation. Links the service order to the conversation and fills in the customer's phone and name when not provided.
telefonetextnoCustomer's WhatsApp. Same phone rule as the rest of the API.
nome_clientetextnoCustomer name, up to 190 characters.
enderecotextnoService address, up to 255 characters.
departamento_idintegernoOwning team. Without it, the type's team applies. A nonexistent department returns departamento_invalido.
responsavel_idintegernoPerson in charge (atendentes/listar). Without it, the service order is created unassigned. A nonexistent or inactive user returns atendente_invalido.
erp_abrir0 or 1noOpening 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
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
<?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);
Python
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:

Response
{
  "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

  1. Number and stage: the service order receives its OS- number (older service orders keep their T- number) and enters the board's initial stage. The timeline records the aberto event (with origem equal to api) and the system event API: <key name>.
  2. Deadline and priority from the type: without prazo_em and without prioridade, the type's defaults apply. The factory Network repair type, for example, gives urgent priority and an 8-hour deadline.
  3. Assignee: with a valid responsavel_id, the timeline records the responsavel event.
  4. 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.
  5. 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 in resumo_ia and as an ia event on the service order timeline. The customer sees none of this.
  6. 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 in erp.ordens; a failure to open does not prevent the creation of the service order here and is recorded on the timeline as an erp event. Afterwards, each change in the ERP becomes an erp event and triggers ticket_atualizado; completion in the ERP moves the service order to the stage with resolvido nature.
  7. Webhook: the ticket_criado event 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

POST/api_publica.php?r=tickets/mover

Moves the service order to another board stage, with an optional comment. It is the same action as dragging the card in the panel.

ParameterTypeRequiredDescription
ticket_id or numerointeger / textyesWhich service order.
etapa_idintegeryesDestination stage (service orders/etapas). It must be active.
comentariotextnoUp 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
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
<?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);
Python
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()
Response
{
  "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 andamento stage records iniciado_em; a resolvido stage records resolvido_em and fechado_em; cancelado records fechado_em. Leaving a final stage is a reopening (reaberto event).
  • 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_atualizado goes out with mudanca equal to etapa (or reaberto).

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

POST/api_publica.php?r=tickets/atribuir

Sets, changes or removes the service order's assignee.

ParameterTypeRequiredDescription
ticket_id or numerointeger / textyesWhich service order.
responsavel_idintegeryesId of the person (atendentes/listar), who must be active. 0 removes the owner: the service order goes back to unassigned.
curl
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
<?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);
Python
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()
Response
{
  "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 responsavel event; the linked conversation, if open, gets the corresponding internal note.
  • The ticket_atualizado webhook goes out with mudanca equal to responsavel.

Comment

POST/api_publica.php?r=tickets/comentar

Adds 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.

ParameterTypeRequiredDescription
ticket_id or numerointeger / textyesWhich service order.
textotextyesFrom 1 to 19,000 characters. Saved with the prefix [API: <key name>].
curl
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
<?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);
Python
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()
Response
{
  "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:

EventWhenmudanca
ticket_criadoService order opened.(none)
ticket_atualizadoStage 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.

Webhook body (ticket_atualizado)
{
  "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:

errorHTTPRoutesCause
permissao403allThe key lacks Look up service orders (read) or Manage service orders (write). The escopo field tells which one is missing.
identificador_obrigatorio400consultar, mover, atribuir, comentarBoth ticket_id and numero are missing.
nao_encontrado404consultar, mover, atribuir, comentarNo service order with that id or number.
titulo_invalido400criarEmpty title or longer than 160 characters.
conversa_invalida400criarconversa_id does not exist.
telefone_invalido400criar, listarThe phone is not a valid WhatsApp number.
sem_etapas409criarThe board has no active stage to receive the service order.
etapa_invalida400moverNonexistent or disabled stage.
parametro_obrigatorio400mover, atribuiretapa_id or responsavel_id is missing (see campo).
tipo_invalido400criarNonexistent or disabled type.
departamento_invalido400criarNonexistent department.
atendente_invalido400criar, atribuirNonexistent or inactive user.
texto_invalido400comentarEmpty text or over 19,000 characters.
parametro_invalido400listar, criarValue 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_invalida400listar, criarDate that could not be parsed (see campo).
Error example
{"ok": false, "error": "etapa_invalida"}