Skip to main content
Glama
tiflux

TiFlux MCP Server

Official
by tiflux
README.md
# Tiflux MCP Server

Conecte **Claude**, **ChatGPT** e outros clientes de IA à sua conta Tiflux para abrir, atualizar e consultar tickets em seu nome — sem instalar nada. Servidor MCP (Model Context Protocol) hospedado em `https://mcp.tiflux.com`.

> **English:** this README is primarily in Portuguese (pt-BR). The full tool catalog ([Available Tools](#available-tools)) is in English, and local usage via `npx` is documented under [Avançado: execução local](#avançado-execução-local-sdk-via-npx).

## Pré-requisitos e modelo de uso

- **Licença API ativa** na sua conta Tiflux — é o único pré-requisito para usar o MCP.

**Como gerar sua chave de API:**

1. Acesse o Tiflux e faça login em [app.tiflux.com](https://app.tiflux.com/)
2. Clique na sua foto no canto superior direito → **Minha conta**
3. Abra a aba **Sessões**
4. Em **Sessões API**, clique em **"Gerar novo token de sessão"**
5. Copie a chave gerada — é ela que você vai usar para conectar

> [!IMPORTANT]
> Todas as ações feitas via MCP — abrir e movimentar tickets, responder, criar apontamentos etc. — são **registradas em nome do usuário dono da chave de API**. Para uso operacional no dia a dia, **cada pessoa da equipe deve ter sua própria licença API e sua própria chave**. Não compartilhe uma mesma chave entre vários usuários.

## Como conectar

O caminho recomendado é o **servidor hospedado** (`https://mcp.tiflux.com`): sempre atualizado, sem instalação e com autenticação segura via OAuth 2.0 ou chave de API.

### Claude (claude.ai — web)

1. Acesse [claude.ai](https://claude.ai) → **Configurações** → **Conectores**
2. Clique em **Adicionar conector personalizado**
3. Cole a URL do servidor:

   ```
   https://mcp.tiflux.com
   ```

   > ⚠️ Sem `/mcp` no final — o conector usa a URL raiz.

4. Clique em **Conectar** — a página de autorização do Tiflux abre automaticamente
5. Cole sua chave de API e clique em **Autorizar**

Pronto: por trás, a plataforma recebe um token OAuth 2.0 e o usa em todas as requisições seguintes — você não precisa manusear a chave novamente.

### ChatGPT (web)

1. Acesse **Configurações** → **Conectores** (ou, ao criar um GPT, **Adicionar** um conector)
2. Adicione um conector personalizado apontando para:

   ```
   https://mcp.tiflux.com
   ```

3. Conclua a autorização colando sua chave de API na página do Tiflux

> A disponibilidade de conectores MCP no ChatGPT depende do plano e do modo da sua conta.

### Claude Desktop

Mesmo fluxo do claude.ai: **Configurações** → **Conectores** → **Adicionar conector personalizado** → colar `https://mcp.tiflux.com` → **Conectar** → autorizar com a chave de API. Veja o passo a passo em [Claude (claude.ai — web)](#claude-claudeai--web).

### Claude Code

Adicione o servidor remoto com um único comando:

```bash
claude mcp add tiflux --transport http https://mcp.tiflux.com/mcp --header "x-tiflux-api-key:SUA_CHAVE" -s project
```

Ou configure manualmente em `.claude/settings.json` ou `~/.claude.json`:

```json
{
  "mcpServers": {
    "tiflux": {
      "type": "url",
      "url": "https://mcp.tiflux.com/mcp",
      "headers": {
        "x-tiflux-api-key": "SUA_CHAVE"
      }
    }
  }
}
```

> ⚠️ Aqui a URL **tem** `/mcp` no final (`https://mcp.tiflux.com/mcp`) — diferente do conector web, que usa a URL raiz.

Alternativa para cenários específicos: execução local via `npx` — veja [Avançado: execução local (SDK via npx)](#avançado-execução-local-sdk-via-npx).

### n8n

Use o node **MCP Client Tool** apontando para o servidor hospedado:

1. Adicione o node **MCP Client Tool** ao seu workflow
2. **Endpoint:** `https://mcp.tiflux.com/mcp` (transporte HTTP Streamable)
3. **Autenticação:** credencial do tipo **Header Auth** com nome `x-tiflux-api-key` e valor igual à sua chave de API

### Manus AI

1. Nas configurações do Manus, adicione um novo conector MCP — abre a tela **Configuração do MCP**
2. Preencha os campos:
   - **Nome do servidor:** `Tiflux`
   - **Tipo de transporte:** `HTTP`
   - **URL do servidor:**

     ```
     https://mcp.tiflux.com
     ```

3. Em **Cabeçalhos personalizados**, clique em **Adicionar cabeçalho personalizado** e preencha:
   - **Nome do cabeçalho:** `x-tiflux-api-key`
   - **Valor do cabeçalho:** sua chave de API
4. Clique em **Salvar** — use **Experimente** para testar a conexão

### Outros clientes MCP

Qualquer cliente MCP funciona com o servidor hospedado:

- **Cliente com transporte HTTP + headers:** URL `https://mcp.tiflux.com/mcp` + header `x-tiflux-api-key: SUA_CHAVE`
- **Cliente com suporte a OAuth 2.0 (conectores):** URL raiz `https://mcp.tiflux.com`

**Endpoints:**

| Endpoint | Método | Auth | Descrição |
|----------|--------|------|-----------|
| `/mcp` | GET | Não | Informações do servidor |
| `/mcp` | POST | Sim | Operações MCP |
| `/health` | GET | Não | Health check |
| `/.well-known/oauth-protected-resource` | GET | Não | Protected Resource Metadata (RFC 9728) — `resource` (`https://mcp.tiflux.com/mcp`), `authorization_servers` e `bearer_methods_supported`. A variante `/.well-known/oauth-protected-resource/mcp` devolve o mesmo conteúdo |
| `/.well-known/oauth-authorization-server` | GET | Não | Metadata do servidor de autorização OAuth 2.0 (RFC 8414) |

**Métodos de autenticação:**

| Método | Header | Quando usar |
|--------|--------|-------------|
| Chave de API (direta) | `x-tiflux-api-key: SUA_CHAVE` | Claude Code, n8n, Manus AI, scripts, SDK local |
| Bearer token (OAuth) | `Authorization: Bearer <token>` | Claude.ai, ChatGPT, conectores web |

**Versões do protocolo MCP:** o servidor hospedado negocia `protocolVersion` no handshake `initialize` — aceita e ecoa qualquer versão suportada pelo cliente entre `2024-11-05` e `2025-11-25` (a mais recente). Se o cliente pedir uma versão desconhecida ou não enviar `protocolVersion`, o servidor responde com a mais recente suportada; nenhuma versão de cliente é rejeitada. `MCP-Protocol-Version` é opcional em requisições subsequentes ao `initialize`.

**Comportamento HTTP do servidor hospedado:**

- **`ping`:** o método JSON-RPC `ping` é suportado e responde `result: {}` (em qualquer versão do protocolo), útil para keep-alive/health do lado do cliente.
- **Notificações** (ex: `notifications/initialized`): respondem **`202 Accepted`** sem corpo, conforme o transporte Streamable HTTP. Versões anteriores respondiam `204 No Content` — clientes que tratam qualquer `2xx` como sucesso não são afetados.
- **`401` sem credencial:** além do corpo JSON de erro, a resposta inclui o header `WWW-Authenticate: Bearer resource_metadata="https://mcp.tiflux.com/.well-known/oauth-protected-resource"`, que permite a clientes OAuth descobrirem o servidor de autorização automaticamente.

## Funcionalidades

- **Tickets**: criar, consultar, atualizar, fechar, cancelar, reabrir e listar tickets com filtros avançados — incluindo transferência de mesa, histórico de estágios e SLA; relatório de avaliações de atendimento (CSAT) com comparação de período (`get_tickets_feedback_report`)
- **Comunicações internas e respostas**: criar, listar, editar e excluir comunicações internas e respostas de tickets, com anexos (até 10 arquivos de 25MB cada); registrar resposta em nome do cliente (`create_client_answer`, só Administrador)
- **Apontamentos de horas**: criar e listar apontamentos de trabalho em tickets; listagem global por período com filtros server-side (`list_appointments_global`); relatório agregado de apoio N2 por técnico e mesa com totalizadores (`list_appointments_report`); pré-apontamentos (cronômetros em andamento) via `list_pre_appointments`
- **Chats (WhatsApp)**: listar caixa de entrada/meus/em atendimento/arquivados, ler o conteúdo/mensagens de um chat (`list_chat_messages`), transferir e vincular chats, enviar mensagens e finalizar atendimentos; relatório de avaliações de atendimento (CSAT) com comparação de período (`get_chats_feedback_report`)
- **Clientes**: CRUD completo — dados cadastrais, mesas e grupos técnicos vinculados, usuários do portal e permissões de e-mail
- **Usuários/Agentes** (admin): criar, consultar e atualizar agentes/atendentes — incluindo licenças, grupo técnico por nome e ativar/inativar (requer chave de administrador); atualizar o próprio perfil — nome, ramal e telefone (`update_my_profile`; troca de e-mail bloqueada no MCP)
- **Solicitantes**: buscar, criar, atualizar, excluir (`delete_requestor`) e gerenciar solicitantes, com resolução automática de nome/e-mail ao abrir tickets
- **Mesas e catálogo**: explorar mesas, estágios, prioridades e itens de catálogo sem sair do chat; criar mesas de serviço (`create_desk` — a API não exclui mesas)
- **Campos personalizados**: descobrir entidades, campos e opções para preencher campos customizados corretamente; criar e editar a estrutura (grupos, subcampos e opções) diretamente via IA — sem exclusão na API v2, correções via `update_*` ou pelo portal
- **Base de conhecimento**: listar e criar artigos, com busca por título/tags e filtro por pasta
- **Contratos**: listar contratos da organização (somente leitura) com filtros por cliente, tipo e status, distinguindo contratos de grupos de contrato (modalidade Compartilhado, linhas "· grupo"); detalhe de um grupo de contrato com seus contratos-membro via `get_contract_group`; consumo e saldo por ciclo de apuração de um contrato individual (`get_contract_usage`)
- **Recursos (Equipamentos)**: listar, criar e atualizar equipamentos/ativos de clientes; inventário de hardware resumido na listagem (CPU, RAM, discos, antivírus, Windows Update, com alertas ⚠️) via `include_technical_info`; exibir detalhes completos de hardware e inventário de um recurso individual (processador, memória, discos, rede, SO, fabricante, campos personalizados) via `get_equipment`; consultar softwares instalados (inventário via agente); explorar grupos e tipos de recursos para montar fluxos de inventário de TI via IA
- **Pré-Tickets**: listar e criar pré-tickets (solicitações em estágio pré-triagem, ainda não convertidas em tickets), com suporte a anexos (até 10 arquivos de 25MB cada)
- **Templates de Mensagem**: listar templates HSM aprovados para WhatsApp via Gupshup (`list_gupshup_templates`) e WhatsApp Cloud/Meta (`list_whatsapp_cloud_templates`), para alimentar o fluxo de `send_message` com `template_id`
- **Faturamentos**: consultar o histórico de faturamentos da organização com filtros por período de emissão, vencimento, cliente (por ID ou nome), NFe, ticket e situação (`get_billings_history`); exige permissão "Faturar serviços avulsos e contratos" e licença Tickets
- **Catálogo de serviços (CRUD)**: criar, listar, atualizar e remover catálogos, áreas e itens de catálogo nos três níveis da hierarquia (catálogo → área → item); remoção em cascata com contagem pre-flight informativa (não é gate: não há confirmação nem dry-run); resolução automática de nome em todos os níveis (`services_catalog_name`, `area_name`); requer role `service_catalogs_manage`
- **Estrutura da organização (somente leitura)**: consultar grupos de atendentes (mesas, clientes e usuários vinculados, expedientes e departamentos) e grupos de permissões (permissões concedidas, usuários membros com filtro por e-mail, e grupos de atendentes vinculados); requer as permissões "Gerenciar mesas de serviços" e "Gerenciar grupos de permissão"

O catálogo completo, com parâmetros e exemplos de cada ferramenta, está em [Available Tools](#available-tools) (em inglês).

## Configuração avançada

### Verbosidade das respostas

O servidor suporta dois modos de verbosidade para controlar o consumo de tokens:

| Modo | Descrição |
|------|-----------|
| `rich` | Saída completa em Markdown com emojis, rodapés e blocos de paginação detalhados (padrão) |
| `compact` | Saída enxuta, pensada para consumo por modelo: sem rodapé decorativo, sem emoji, sem negrito nem separadores de tabela; paginação em uma linha. Nas ferramentas de análise, os itens saem como linhas delimitadas por `\|` precedidas de uma linha-cabeçalho com os nomes das colunas. `get_ticket` omite SLA sub-fields, equipamento, feedback, tempo de resolução e flags de baixo valor — hierarquia vira `Pai: #N \| Filhos: #A, #B` (só números), solicitante vira `Solicitante: <nome> <email>`, checklists apenas quando bloqueiam; descrições longas são truncadas |

> **`compact` remove formatação, nunca dado.** Itens pedidos explicitamente por parâmetro
> (`include_list`, `include_valorization`) são sempre renderizados, nos dois modos.

#### Formato dos valores em `compact`

Nas ferramentas de análise, os valores saem em forma canônica — o objetivo é que o modelo leia e
agregue sem precisar converter string antes de calcular:

| Tipo | `rich` | `compact` |
|------|--------|-----------|
| Data/hora | `16/04/2026, 10:00:00` (horário de Brasília) | `2026-04-16T13:00Z` (ISO em UTC, sufixo `Z`, precisão de minuto — o mesmo instante em qualquer fuso) |
| Duração | `2:15` | `135` (minutos inteiros; a unidade é declarada no cabeçalho) |
| Valor monetário | `R$ 28.963,20` | `28963.20` (decimal, sem símbolo nem separador de milhar) |
| Booleano | `✅ Sim` / `❌ Não` | `true` / `false` |
| Campo ausente | `N/A` | `—` |

Ferramentas com saída tabular dedicada em `compact`: `get_ticket`, `list_tickets` (lista e
`group_by`), `get_tickets_comparison`, `get_ticket_stages_slas`, `list_appointments_global`,
`list_appointments_report`, `get_billings_history`, `get_tickets_feedback_report` e
`get_chats_feedback_report`.

Em `list_tickets`, a linha `compact` de cada ticket traz cliente, mesa, status, estágio,
responsável, prioridade, catálogo e o dia de criação (`criado YYYY-MM-DD`, dia em horário
de Brasília); a descrição fica fora (use `get_ticket`).

Em `compact`, a paginação é uma linha só: `[Pág N · K <unidade>/pág · X nesta pág · total: T · → offset: N+1 p/ mais]`
(ex.: `[Pág 1 · 200 tickets/pág · 200 nesta pág · total: 1305 · → offset: 2 p/ mais]`). `total` aparece
quando a API informa o total do filtro e `→ offset` só quando há próxima página. O formato anterior era
`K/<unidade>`; agora é `K <unidade>/pág` (tamanho da página, não itens por unidade).

**SDK (stdio) — variável de ambiente:**

```bash
TIFLUX_MCP_VERBOSITY=compact npx @tiflux/mcp@latest
```

**Server (HTTP/Lambda) — header por requisição:**

```
x-tiflux-verbosity: compact
```

> O padrão é `rich` nos dois modos. Integrações existentes não são afetadas a menos que a variável de ambiente ou o header seja definido — com uma exceção: o `compact` automático nas listagens grandes, descrito abaixo.

#### Orçamento de resposta: `compact` automático, teto de ~40k e rede de ~60k

Clientes MCP (Claude Code, claude.ai) recusam resultados de ferramenta acima de ~25k tokens. Para a resposta nunca chegar lá, o servidor aplica três limites, iguais no SDK e no Server:

| Limite | Quando vale | O que acontece |
|--------|-------------|----------------|
| **`compact` automático** | Página com **mais de 50 itens retornados** e **sem verbosidade explícita** (nem header `x-tiflux-verbosity`, nem env `TIFLUX_MCP_VERBOSITY`). Conta o que a API devolveu, não o `limit` pedido: `limit: 100` que devolve 9 itens segue `rich`, sem aviso | A resposta sai em `compact`, com a linha final `(formato compacto aplicado automaticamente pelo volume; envie x-tiflux-verbosity: rich (ou TIFLUX_MCP_VERBOSITY=rich no SDK) para forçar o formato completo)`. Vale para `list_tickets` (fora `group_by`), `list_appointments`, `list_appointments_global`, `get_billings_history` e a lista (`include_list`) de `get_tickets_feedback_report`/`get_chats_feedback_report`. Não vale para `list_chat_messages`, `list_inbox_chats`, `list_my_chats`, `list_in_attendance_chats`, `list_archived_chats` nem `get_ticket_histories`: o formato deles não muda com a verbosidade, então seguem no formato de sempre (sem a linha de aviso), só com o teto de ~40k. |
| **Teto de ~40k caracteres** | As mesmas listagens, em qualquer modo | A lista é cortada no fim de um item e a linha de corte substitui o bloco de paginação, com o `offset`/`limit` exatos para continuar (ver [Dicas](#dicas-para-reduzir-consumo-de-tokens)). |
| **Rede global de ~60k caracteres** | Qualquer ferramenta | O texto é cortado no último parágrafo que cabe, com o aviso `…resposta cortada em ~60k caracteres — reduza limit ou refine o recorte.` (`isError` não muda). |

- **O header ou a env forçam o modo.** `rich` explícito sai `rich` (cortado pelo teto se preciso); `compact` explícito sai `compact`, sem a linha de aviso. Um valor desconhecido (ex.: `verbose`) conta como padrão: sai `rich` e o `compact` automático continua valendo.
- **O `compact` automático só muda a formatação**, nunca a chamada à API: os mesmos filtros, `offset` e `limit` chegam ao endpoint.

### Dicas para reduzir consumo de tokens

Ao construir aplicações que chamam este servidor MCP programaticamente, o custo de tokens importa. Siga estas orientações:

- **Passe IDs quando já os tiver.** Toda ferramenta que aceita um parâmetro `_name` para auto-resolução (ex.: `desk_name`, `stage_name`, `entity_field_name`) fará uma ou mais chamadas extras à API para resolver o nome. Se você guardou o ID de uma chamada anterior, passe-o diretamente (ex.: `desk_id`, `stage_id`, `entity_field_id`) — é sempre mais rápido e barato.
- **Use verbosidade `compact`** via `TIFLUX_MCP_VERBOSITY=compact` (SDK) ou header `x-tiflux-verbosity: compact` (Server). A redução varia por família de ferramenta: ~65% em `list_tickets`, e de 20% a 62% nas ferramentas de análise. Para perguntas analíticas o ganho não é só de tamanho: com datas em ISO, durações em minutos e valores decimais, o modelo agrega direto em vez de converter texto antes de calcular.
- **Pagine deliberadamente.** `list_tickets` com um intervalo de datas amplo em uma mesa movimentada pode retornar centenas de itens. Passe `limit` e `offset` intencionalmente — quando uma página cheia retorna, o modo compact acrescenta uma dica de próxima página (`→ offset: N`) para o modelo saber que pode haver mais a buscar.
- **Linha de corte: continue com o `offset`/`limit` que ela sugere.** As listagens pesadas (`list_tickets`, `list_appointments`, `list_appointments_global`, `get_billings_history`, `list_chat_messages`, `list_inbox_chats`, `list_my_chats`, `list_in_attendance_chats`, `list_archived_chats`, `get_ticket_histories` e a lista de `get_tickets_feedback_report`/`get_chats_feedback_report`) limitam a resposta a ~40k caracteres e cortam sempre no fim de um item. Quando cortam, a linha de corte substitui o bloco de paginação:
  - `rich`: `✂️ Mostrando 98 dos 200 tickets desta página (total 1305) — resposta limitada a ~40k caracteres. Para continuar: offset 2, limit 98`
  - `compact`: `[cortado: 98/200 nesta pág (total 1305) — offset 2 limit 98]`

  Os dois números são da página pedida (98 mostrados dos 200 que a API devolveu); `(total N)` é o total geral do filtro e só aparece quando a API informa. Quando a página é cortada, o cabeçalho de toda listagem com contagem passa a contar os itens mostrados: `K de <total>, página cortada` (sem total informado pela API, `K de <tamanho da página>`) — ex.: `Tickets (98 de 1305, página cortada):` e `Apontamentos (154 de 3251, página cortada) · min · BRL` no `compact`, `**📋 Lista de Tickets** (98 de 1305 encontrados, página cortada)` no `rich`. No `list_tickets`, o aviso de volume alto deixa de dizer "NÃO pagine" e passa a apontar a linha de corte para quem precisa dos itens individuais. O mesmo vale a partir da página 2 (`offset` > 1): quem chegou lá está seguindo uma continuação, então o aviso diz para seguir a paginação.

  Chame a mesma ferramenta com esse `offset` e esse `limit` para receber exatamente o item seguinte, sem pular nem repetir (a API pagina por número de página: com `limit: 98`, a página 2 começa no item 99). Se a página cortada não era a primeira e a conta não fecha, a linha pede para refinar o recorte (mesa, período) ou refazer desde `offset 1` com o `limit` sugerido. Para análise, prefira `group_by` ou `get_tickets_comparison` a paginar.
- **Para análise comparativa, use `get_tickets_comparison`.** Em vez de chamar `list_tickets` paginada duas vezes para dois períodos (o que pode ultrapassar 100k tokens e o teto de 6 iterações do orquestrador), use `get_tickets_comparison`: uma chamada MCP, 2 requests à API, resposta de centenas de tokens com totais, Δ e buckets pareados prontos para gráfico.

## Available Tools

### get_ticket
Retrieve a specific ticket by ID with comprehensive information including hierarchy (parent ticket and child tickets/sub-tasks), requestor, checklists summary, status, priority, desk, stage, catalog, responsible, client, audit data, SLA and URLs.

**Parameters:**
- `ticket_number` (string, required): Number of the ticket to retrieve
- `show_entities` (boolean, optional): Include ALL custom fields linked to the ticket
- `include_filled_entity` (boolean, optional): Include only custom fields with filled values

**Returns:**
Comprehensive ticket information including:
- **Hierarchy**: parent ticket (`#N — title`) and child tickets (count + `#N — title` list) — shown only when a link exists
- **Requestor**: who opened the ticket (name, email, phone, extension) — distinct from the responsible (assignee)
- **Checklists summary**: total, pending, required pending, with explicit ⚠️ warning when `blocks_close` is true or required checklists are missing (lists them by name)
- Status (ID, name, open/close/canceled default flags)
- Priority (ID, name, SLA window `start_time → end_time`, order)
- Desk (ID, internal name, display name, active status, appointment type)
- Stage (ID, name, first/last stage flags, max time)
- Service Catalog (item ID, item name, area, catalog)
- Responsible (ID, name, email, type, technical group)
- Client (ID, name, social reason, CPF/CNPJ `social_revenue` when set, active status)
- Audit (created by ID, origin, created/updated dates — shown in Brasília time in `rich`)
- SLA (status, expirations — shown in Brasília time in `rich` —, **closing date/time** (`Fechado em`, from `sla_info.solved_in_time`, only when the ticket is closed or canceled — cleared again on reopen), **resolved-in-time flag** (`Resolvido no prazo: Sim/Não`, from `sla_info.attend_sla_solution`, only on closed tickets). On open tickets the API computes `attend_sla` / `attend_sla_solution` live (`now < expiration`: `true` while within the deadline, flips to `false` once it breaches, even without any ticket activity), so they are shown as the current SLA state: `SLA de atendimento` / `SLA de resolução: no prazo | estourado`. `desactivate_sla_reason` when set)
- Equipment linked to the ticket (when `equipment.id` is not null)
- Feedback/rating (when available) — rating and comment from the client's evaluation
- Additional info: followers, worked hours, total resolution time (`closed_ticket_total_spent_solving`), last answer type, reopens, URLs
- Custom fields: when present, includes field type, current value, `required` flag (shown as `(obrigatório)` suffix), and options already set for `single_select`/`checkbox` fields (with IDs for `list_entity_field_options`)

**Verbosity (`compact` mode):**
- Hierarchy: single line `Pai: #N | Filhos: #A, #B` (numbers only, no titles)
- Requestor: `Solicitante: <name> <email>`
- Checklists: shown only when `blocks_close` or `required_pending > 0`
- `Criado:` (created date/time) in UTC with `Z` suffix
- When the ticket is closed/canceled, an extra line `Fechado: <UTC 'Z'> · no prazo: sim|não · resolução: <closed_ticket_total_spent_solving>` — a missing sub-field (e.g. no `attend_sla_solution`) is simply omitted, never breaks the line
- Equipment, feedback, resolution time, SLA sub-fields, and low-value flags are omitted in compact

**New in v2.48.1:** `get_ticket` now correctly surfaces the ticket's **closing date/time** (`sla_info.solved_in_time`, previously misread as a boolean) as `Fechado em` (rich) / `Fechado:` (compact), and the real "resolved in time" boolean (`sla_info.attend_sla_solution`) as `Resolvido no prazo: Sim/Não`. `Criado em`/`Atualizado em` and SLA expirations now render with date **and** time in Brasília time in `rich` (previously ISO/raw).

**New in v2.43.0:** Hierarchy (parent/child tickets), requestor, checklists summary, equipment, SLA sub-fields, priority/desk/client sub-fields, and cleanup of phantom fields (tags, closed_at).

### create_ticket
Create a new ticket in Tiflux.

**Parameters:**
- `title` (string, required): Ticket title
- `description` (string, required): Ticket description. Accepts Markdown (bold, lists, headings, code) — the MCP automatically converts it to HTML before sending to the API.
- `client_id` (number, optional): Client (company) ID
- `client_name` (string, optional): Client (company) name for automatic search (alternative to client_id). Use only when the user says "client" or "company" explicitly; for a person, use `requestor_name`.
- `desk_id` (number, optional): Desk ID
- `desk_name` (string, optional): Desk/team name for automatic search (alternative to desk_id). Accepts partial names — e.g. `"cansados"` resolves to `"Dev - Cansados"` (see Smart Name Resolution). **Prefer this when the user references a name without qualifying the entity.**
- `priority_id` (number, optional): Priority ID
- `services_catalogs_item_id` (number, optional): Service catalog item ID
- `catalog_item_name` (string, optional): Catalog item name for automatic search (alternative to services_catalogs_item_id, requires desk_id or desk_name)
- `status_id` (number, optional): Status ID
- `requestor_id` (number, optional): Requestor ID (person who opens the ticket, must belong to the selected client). This is the **canonical link** — prefer it when you know the ID. Highest precedence: if provided, it is used directly with no resolution.
- `requestor_name` (string, optional): Requestor name. Used **only** when neither `requestor_id` nor `requestor_email` is provided. The MCP attempts to resolve it to an existing `requestor_id` (avoids creating a "ghost" requestor) via `GET /requestors`, falling back to the client-scoped `GET /clients/{id}/requestors` on 403. Multiple matches → returns a list to disambiguate; no match → sends the name as-is (the API resolves/creates the requestor).
- `requestor_email` (string, optional): Requestor email. The MCP automatically attempts to resolve it to an existing `requestor_id` by searching the client's requestors (the canonical link). **One match → uses the `requestor_id` and drops the raw email; zero matches → keeps the raw email as a fallback; multiple matches → returns a list to disambiguate.** Has precedence over `requestor_name`.
- `requestor_telephone` (string, optional): Requestor phone

> **Requestor precedence (v2.18.0):** the canonical link is `requestor_id`. The MCP resolves email and name to `requestor_id` automatically when a matching registration exists, in the order **`requestor_id` > `requestor_email` > `requestor_name`**. When email or name resolves to an ID, the individual fields (name/email/telephone) are dropped from the payload so the registered requestor is linked instead of a loose/ghost entry. A raw email is only sent when no registration matches.
- `responsible_id` (number, optional): Responsible user ID
- `responsible_name` (string, optional): Responsible user name for automatic search (alternative to responsible_id)
- `followers` (string, optional): Comma-separated follower emails
- `parent_ticket_number` (number, optional): Parent ticket number — the created ticket will be linked as a child of this ticket
- `files_base64` (array, optional): Array of base64 encoded files `[{content: "base64...", filename: "file.png"}]` (max 10 files, 25MB each)

**New in v2.4.0:** Support for base64 file upload via `files_base64`. The ticket is sent as `multipart/form-data`. **Note for Server mode (Lambda):** `files_base64` payloads are subject to the 6MB API Gateway limit.

> **Breaking change (v2.8.0):** O parametro `files` (caminhos locais) foi removido. Use a nova tool `upload_ticket_files` para enviar arquivos via base64, ou passe os arquivos diretamente via `files_base64`.

**Fixed in v2.50.1:** `Criado em` in the success response now shows the creation date/time in Brasília time (previously the raw ISO string from the API, e.g. `2026-10-01T14:50:45Z`).

### update_ticket
Update an existing ticket in Tiflux. Supports transferring a ticket to another desk — when `desk_id`/`desk_name` is provided without an explicit `stage_id`/`stage_name`, the MCP automatically resolves the first stage of the destination desk (the stage with `first_stage: true`, or the one with the lowest index as a fallback), preventing invalid-stage errors.

**Parameters:**
- `ticket_number` (string, required): Number of the ticket to update (e.g. "123", "456")
- `title` (string, optional): New ticket title
- `description` (string, optional): New ticket description. Accepts Markdown (bold, lists, headings, code) — the MCP automatically converts it to HTML before sending to the API.
- `client_id` (number, optional): New client ID. Use only when the user says "client" or "company" explicitly.
- `desk_id` (number, optional): New desk ID. Transfers the ticket to the specified desk. Stages and priorities are scoped per desk — if no stage is provided, the MCP auto-resolves the first stage of the destination desk.
- `desk_name` (string, optional): Desk name for automatic search (alternative to desk_id). Accepts partial names — e.g. `"cansados"` resolves to `"Dev - Cansados"` (see Smart Name Resolution). **Prefer this when the user references a name without qualifying the entity.**
- `stage_id` (number, optional): Stage/phase ID. Always takes precedence over auto-resolution.
- `stage_name` (string, optional): Stage name for automatic search (alternative to stage_id, requires desk_id or desk_name). Always takes precedence over auto-resolution.
- `priority_id` (number, optional): Priority ID. Priorities are scoped per desk — use `list_desk_priorities` to discover valid IDs for the destination desk. When transferring desks, provide this field (or `priority_name`) to preserve the ticket's priority (priorities are not automatically preserved across desk transfers).
- `priority_name` (string, optional): Priority name for automatic search (requires `desk_id` or `desk_name` to resolve). **Because it requires a desk, the API treats it as a transfer — so `priority_name` only works when transferring to another desk.** To change priority on the ticket's **current** desk, use `priority_id` directly (no desk).
- `priority_change_reason` (string, optional): Reason for the priority change (free text). **Required** when changing priority (`priority_id`) **outside** of a desk transfer — the API rejects `priority_id` without it (`42201`). **Not allowed** during a desk transfer — the API rejects it (`42202`); in that case it is dropped automatically and a warning is shown.
- `status_id` (number, optional): Status ID. There is no status listing endpoint in the API v2 — provide the ID directly (no `status_name`).
- `responsible_id` (number, optional): Responsible user ID (use null to unassign)
- `responsible_name` (string, optional): Responsible user name for automatic search (alternative to responsible_id)
- `requestor_id` (number, optional): New requestor (ticket opener) ID. The requestor must belong to the same client linked to the ticket. When provided together with `requestor_name`, `requestor_id` takes precedence.
- `requestor_name` (string, optional): Requestor name for automatic resolution (alternative to `requestor_id`). The MCP tries `GET /requestors` (global) and falls back to `GET /clients/{id}/requestors` on 403. Single match → uses the ID; multiple matches → lists candidates to disambiguate; no match → error suggesting `search_requestor`. If both endpoints return 403, returns a clear message suggesting `requestor_id` directly.
- `followers` (string, optional): Comma-separated follower emails. **⚠️ REPLACES the full followers list** — to add without removing, fetch current followers via `get_ticket` first and send the complete list (existing + new). Empty string `""` removes all followers.
- `services_catalogs_item_id` (number, optional): Catalog item ID for updating desk with specific item
- `catalog_item_name` (string, optional): Catalog item name for automatic search (alternative to services_catalogs_item_id, requires desk_id or desk_name)

**Note:** At least one optional field must be provided along with the `ticket_number`.

**Desk Transfer Prerequisites:**
1. **Desk relationship** — origin and destination desks must be **linked** in Tiflux settings. Without this the API rejects the transfer with a `42202` error.
2. **Catalog item for destination desk** — desks that require a service catalog reject the transfer without `services_catalogs_item_id`/`catalog_item_name` of the destination desk.
3. **Priority is not preserved** — priority is scoped per desk and is **lost** on transfer (becomes `null`). Provide `priority_name`/`priority_id` to preserve it. Status is automatically reallocated by the API.

**Priority change rules** (enforced by the API v2):
- **Same desk (no transfer):** use `priority_id` directly (no `desk_id`/`desk_name`). `priority_change_reason` is **required** (`42201` otherwise). `priority_name` does **not** work here — it requires a desk, which the API interprets as a transfer.
- **During a desk transfer:** provide `priority_id` or `priority_name` to preserve priority; do **not** send `priority_change_reason` (`42202` otherwise — it is dropped automatically with a warning).

**Error messages:** Common `42202` transfer errors (missing desk relationship, required catalog) are returned as actionable messages instead of raw API text.

### update_ticket_entities
Update custom fields (entities) of a ticket in Tiflux. Supports up to 50 fields per request. For checkbox fields with multiple named options, send one item per option with `entity_field_option_id`. Use `list_entity_field_options` to discover option IDs.

> **Tip:** Prefer `entity_field_id` (numeric) when available — it avoids extra API calls. Use the `_name` params only when you don't have the ID yet.

**Parameters:**
- `ticket_number` (string, required): Ticket number to update
- `entities` (array, required): List of custom fields to update. For multiple-choice checkbox fields, send one item per option.

**Entity Object Structure:**
- `entity_field_id` (number): Custom field ID (obtained via `get_ticket` or `list_entity_fields`). Prefer this when available.
- `entity_name` (string, optional): Entity group name for automatic `entity_field_id` resolution — alternative when the ID is unknown.
- `entity_field_name` (string, optional): Field name within the entity group for automatic `entity_field_id` resolution — use together with `entity_name`.
- `entity_field_option_name` (string, optional): Option name for automatic `entity_field_option_id` resolution (for `single_select`/`checkbox` fields).
- `value` (string, required): Field value. Accepted types:
  - `text`: string
  - `text_area`: string
  - `currency`: float as string (e.g., "150.55")
  - `phone`: numbers only (e.g., "47999999999")
  - `email`: string
  - `link`: URL starting with http/https/ftp
  - `date`: format YYYY-MM-DD
  - `single_select`: option ID as string
  - `checkbox`: boolean as string `"true"`/`"false"`
  - Use `null` to clear non-required fields
- `entity_field_option_id` (number, optional): Option ID for checkbox multiple-choice fields. Use `list_entity_field_options` to get IDs. For multiple-choice checkboxes, send one item per option with the same `entity_field_id` and different `entity_field_option_id`.
- `country_code` (string, optional): Country code (for phone fields outside Brazil)

**Example — simple text/date fields (with IDs, most efficient):**
```json
{
  "ticket_number": "123",
  "entities": [
    { "entity_field_id": 72, "value": "New value" },
    { "entity_field_id": 73, "value": "2025-01-15" }
  ]
}
```

**Example — resolving by name (when IDs are unknown):**
```json
{
  "ticket_number": "123",
  "entities": [
    {
      "entity_name": "Contrato",
      "entity_field_name": "Tipo de contrato",
      "entity_field_option_name": "Suporte Premium",
      "value": "true"
    }
  ]
}
```

**Example — checkbox with multiple named options:**
```json
{
  "ticket_number": "12345",
  "entities": [
    { "entity_field_id": 81, "entity_field_option_id": 11, "value": "true" },
    { "entity_field_id": 81, "entity_field_option_id": 12, "value": "false" },
    { "entity_field_id": 81, "entity_field_option_id": 13, "value": "true" }
  ]
}
```

### cancel_ticket
Cancel a specific ticket in Tiflux.

**Parameters:**
- `ticket_number` (string, required): Ticket number to be cancelled (e.g., "37", "123")

**Example:**
```json
{
  "ticket_number": "84429"
}
```

### list_tickets
List tickets with filtering options. Catalog and priority are automatically shown in every ticket card — no extra API calls needed (already included in `GET /tickets` response). The listing already includes both the **opening** date/time (`Criado em`, with time in `rich`) and, when the ticket is closed or cancelled, the **closing** date/time (`Fechado em` in `rich`; `| fechado ...` suffix in `compact`) — there's no need to call `get_ticket` per ticket just to get these dates.

**Parameters:**
- `desk_ids` (string, optional): Comma-separated desk IDs (e.g., "1,2,3")
- `desk_name` (string, optional): Desk/team name for automatic ID resolution. Accepts partial names — e.g. `"cansados"` resolves to `"Dev - Cansados"` (see Smart Name Resolution). **Prefer this field when the user references a name without qualifying the entity.**
- `client_ids` (string, optional): Comma-separated client (company) IDs (e.g., "1,2,3")
- `client_name` (string, optional): Client (company) name for automatic search. Use **only** when the user explicitly says "client", "company", or gives a known corporate name. For a person, prefer `requestor_email`.
- `stage_ids` (string, optional): Comma-separated stage IDs (e.g., "1,2,3")
- `stage_name` (string, optional): Stage name — use with `desk_name` or `desk_ids` (either works)
- `responsible_ids` (string, optional): Comma-separated responsible (assigned attendant) user IDs (use when you already have the ID)
- `responsible_name` (string, optional): Responsible user name for automatic resolution. Works for both admin (via `GET /users`) and non-admin users (via attendant groups fallback). Use when the user says "assigned to" / "responsible" and gives a name.
- `requestor_ids` (string, optional): Comma-separated requestor (person who opened the ticket) IDs (e.g., "1,2,3"). Use for filtering by **person** (not company). Resolve the ID via `search_requestor`.
- `requestor_email` (string, optional): Email of the requestor (person who opened the ticket). Use when the user references a **person** or provides an email directly. Avoids a round-trip to resolve the ID.
- `services_catalogs_item_ids` (string, optional): Comma-separated service catalog item IDs (e.g., "11,12,13"). Passthrough directly to the API — **max 15 IDs** (the `GET /tickets` limit; beyond that only the first 15 are applied, with a warning). Duplicates are removed. Use when you already know the IDs (via `search_catalog_item`). For name/area/catalog text search, use `catalog_query`.
- `catalog_query` (string, optional): Free-text search term to filter by service catalog. Matches partially against catalog name, area name, and item name server-side — a single term like `"security"` returns items from all areas/catalogs whose name contains that term. **Requires a desk** (`desk_id` or `desk_name`). For precise IDs, use `services_catalogs_item_ids`.
- `priority_ids` (string, optional): Comma-separated priority IDs (e.g., "17,18"). Passthrough directly to the API — **max 15 IDs** (the `GET /tickets` limit; duplicates removed). Use when you already know the IDs (via `list_desk_priorities`). For name-based search, use `priority_name`.
- `priority_name` (string, optional): Priority name for automatic fuzzy resolution (e.g., "high", "baixa"). **Requires a desk** (`desk_id` or `desk_name`). For direct IDs, use `priority_ids`.
- `offset` (number, optional): Page number (default: 1)
- `limit` (number, optional): Items per page (default: 20, max: 200)
- `is_closed` (boolean, optional): Legacy status flag. Prefer `filter_by` for more granular control. `is_closed: true` = only closed; `is_closed: false` = only open.
- `filter_by` (string, optional): Status filter with precedence over `is_closed`: "open" (open tickets only), "closed" (resolved/closed, excludes cancelled), "canceled" (cancelled only — robust with custom status names), or "all" (all statuses). Under `date_type="solved_in_time"`, "all" returns closed + cancelled together. **If omitted with `date_type="solved_in_time"`, the MCP assumes `"closed"` and announces this in the response** — use `"all"` to include cancelled too.
- `date_type` (string, optional): Date axis for filtering: "created_at" (creation date, default) or "solved_in_time" (**closing date** — also recorded on cancellation; cleared again on reopen). Accepts timezone offsets beyond Z (e.g., `-03:00`). **`date_type="solved_in_time"` + `filter_by="open"` is contradictory — the MCP returns an error immediately, without calling the API.**
- `group_by` (string, optional): Aggregates the ticket COUNT instead of returning the list. "day"/"week"/"month" group by period (combine with `date_type` + date range); "desk" groups by desk. Returns `{ group_by, date_type, total, buckets: [{period, count}] }`. Use for comparison/trend (e.g., "opened per day this week") or per-desk breakdowns. **When `start_datetime`/`end_datetime` are provided and at least 1 bucket is returned, missing periods in the window are zero-filled (e.g., "2026-02" between "2026-01" and "2026-03" appears with count 0).** The window's labels use the calendar of the offset `start_datetime` was written in (`-03:00` → Brasília; `Z`/plain date → UTC), so no out-of-range row appears at the edges.
- `sla_expiring_before` (string, optional): Filters OPEN (and non-stopped) tickets whose RESOLUTION SLA (`solve_expiration`) is due before the given ISO 8601 datetime, including already overdue. Use for "SLA at risk" (e.g., pass end-of-today). Combine with `group_by=desk` for "desks with SLA at risk".
- `start_datetime` (string, optional): Start date/time filter in ISO 8601 format (e.g., "2024-05-15T00:00:00Z"). Filters tickets with date >= start_datetime
- `end_datetime` (string, optional): End date/time filter in ISO 8601 format (e.g., "2024-05-15T23:59:59Z"). Filters tickets with date <= end_datetime
- `created_by_way_of` (string, optional): Filter tickets by creation origin. Accepted values: `"web"` (Tiflux Web), `"agent"` (Agente), `"chat_widget"` (Chat Widget), `"whatsapp"` (WhatsApp), `"email"` (E-mail), `"external_form"` (Formulario Externo), `"mobile"` (Mobile), `"api"` (API), `"chat"` (Chat), `"recurrent_activity"` (Atividade Recorrente), `"trigger"` (Gatilho), `"ticket_group"` (Grupo de Tickets), `"ai_agent"` (Agente de IA). Invalid values are rejected locally without calling the API.

**Note:** At least one filter is required (desk, client, requestor, responsible, stage, date range, SLA, catalog, priority, or `created_by_way_of`).

**Guard-rails for `date_type="solved_in_time"`:**
- If `filter_by` is **not** provided, the MCP assumes `filter_by="closed"` and announces it in the response (e.g., "Status: Fechados — assumido; use 'all' para incluir cancelados"). Use `filter_by="all"` to include cancelled tickets too.
- If `filter_by="open"` is explicitly passed with `solved_in_time`, the MCP returns an error immediately — this is a contradictory combination (a ticket can't be both "resolved" and "open").

**Common recipes:**

| User question | Recommended call |
|---|---|
| "closed per month in desk X" | `desk_name` + `date_type="solved_in_time"` + `filter_by="closed"` + `group_by="month"` |
| "opened today in desk X" | `desk_name` + `date_type="created_at"` + `filter_by="all"` + period |
| "cancelled in the period" | `filter_by="canceled"` + `date_type="solved_in_time"` + `desk_name` — the period alone would be accepted, but for cancelled tickets ask for the desk if the user did not give one |
| "João's tickets" (person who opened them) | `requestor_email` or `requestor_ids` |
| "tickets assigned to João" | `responsible_name="João"` |
| "this semester vs last" | `get_tickets_comparison` |

**Zero-result diagnostics:** When the response is empty (no tickets), the MCP automatically runs up to 2 sonda API calls to tell the AI *why* — whether it's the status filter, the date range, or a genuine zero for this scope.

**Compact line (`compact`, explicit or automatic):** one line per ticket — `#N title | client | desk | status | stage | responsible | priority | catalog | criado YYYY-MM-DD`, e.g. `#10000 Impressora não imprime em rede (0) | Cliente 00 | Suporte N2 | Aberto | Em atendimento | Técnico 00 | Alta | Infraestrutura › Impressoras › Manutenção | criado 2026-09-01`. The creation date is the day in Brasília time (`America/Sao_Paulo`), not the UTC day — a ticket created at 22:00 in Brasília stays on that day. Missing fields show `—`. The description is not included; use `get_ticket #N` for it.

When the ticket is closed or cancelled (`sla_info.solved_in_time` present), the compact line gets a `| fechado <UTC 'Z'>` suffix (e.g. `| fechado 2026-09-22T21:45Z`) and the `rich` card gets an extra `✅ **Fechado em:** <date, time — Brasília>` line right after `📅 **Criado em:**` (which now also shows the time, not just the date, in Brasília time).

**Volume guard:** When the total (`X-Total-Items`) exceeds 500 tickets in a regular listing (without `group_by`), the response appends an instruction not to paginate for analysis — use `group_by` or `get_tickets_comparison` instead. This threshold is set at 500 in `LIST_TOTAL_WARN_THRESHOLD`.

**Catalog filter note:** `catalog_query` uses server-side partial matching against catalog, area, and item names simultaneously — one term matches items from multiple areas. The MCP paginates all results and resolves to item IDs before calling `GET /tickets`. Because `GET /tickets` accepts **at most 15** `services_catalogs_item_ids` (Swagger error `42201` otherwise), if the query resolves to more than 15 items only the first 15 are applied and a warning is returned (the result may be incomplete). For surgical precision, discover IDs via `search_catalog_item` and pass a narrow set via `services_catalogs_item_ids`.

**Example — filter by catalog query:**
```json
{
  "desk_name": "Support",
  "catalog_query": "security"
}
```

**Example — filter by priority and catalog (IDs):**
```json
{
  "desk_ids": "1",
  "priority_ids": "17",
  "services_catalogs_item_ids": "11,12"
}
```

**Example — filter by requestor email:**
```json
{
  "requestor_email": "joao@empresa.com",
  "is_closed": false
}
```

**Date Filtering Examples:**
```json
// List tickets created in a specific date range (including closed ones)
{
  "desk_name": "Support",
  "date_type": "created_at",
  "start_datetime": "2024-01-01T00:00:00Z",
  "end_datetime": "2024-01-31T23:59:59Z",
  "is_closed": true
}

// List tickets resolved in a specific period
// (filter_by="closed" assumed automatically; use "all" to include cancelled)
{
  "desk_name": "Support",
  "date_type": "solved_in_time",
  "start_datetime": "2024-01-01T00:00:00Z",
  "end_datetime": "2024-01-31T23:59:59Z"
}
```

### get_tickets_comparison
Compare ticket COUNTS between two time periods in a single call. Returns totals, absolute delta (Δ), and percentage change (Δ%) for each bucket (day/week/month/desk).

**When to use vs `list_tickets`:**
- **To COUNT/COMPARE/TREND** → use `get_tickets_comparison`. One MCP call, 2 API requests, answer in hundreds of tokens.
- **To VIEW individual items** → use `list_tickets` without `group_by`.

**Default comparison period (calendar-aligned):** If `compare_start_datetime`/`compare_end_datetime` are not provided, the comparison period is the immediately preceding one with the **same number of days/weeks/months**, aligned to the calendar in the UTC offset you wrote `start_datetime` in (the offset is kept in the dates sent to the API):
- `month`: start and end both shifted back N months (`2026-04-01..2026-09-30` → `2025-10-01..2026-03-31`); the end is shifted as `(end_datetime + 1s) − N months − 1s`, so a full-month window still ends at `start_datetime − 1s` and a partial window keeps the same length and number of months (`2026-09-01..2026-10-01T00:00` → `2026-07-01..2026-08-01T00:00`, not Jul 1–Aug 31). End-of-month is clamped, without rollover (Mar 31 − 1 month = Feb 28; May 31 − 3 months = Feb 28). Never overlaps the main period.
- `week`: the whole window shifted back 7·N days (`W36–W40` → `W31–W35`), keeping the weekday even when the window starts mid-week.
- `day`: the whole window shifted back N days (7 days → the 7 days before).
- `desk`: same duration in ms (`compare_end = start_datetime − 1s`).

**Fixed in v2.50.1:** the automatic window used the same duration in ms for every `group_by`, so a month/week window started inside a partial period (e.g. `2026-04..2026-09` compared against `2025-09-30..2026-03-31`, whose first label is the partial `2025-09`) and every row was paired with the wrong month.

**Default `filter_by` by `date_type`:**
- `created_at` (default): `filter_by="all"` — historical comparisons count all statuses (open, closed, cancelled).
- `solved_in_time`: `filter_by="closed"` — comparisons by closing date assume resolved tickets; use `filter_by="all"` to include cancelled too (e.g., 358 closed + 31 cancelled = 389). This aligns with `list_tickets` so both tools return the same number for the same query.

**Periods without tickets show up as 0 on both sides.** The API simply omits a period that had no ticket (it never returns `count: 0`) — without zero-filling, a gap in the middle of either period shifts every following row by one position, making the delta (Δ) wrong for the rest of the table. This tool fills every missing `day`/`week`/`month` bucket (not `desk`, which is matched by desk name instead) inside each period's own window before pairing the two sides, so a month/week/day with zero tickets always appears explicitly with `0`, in both the current and the comparison column.

**Calendar frame of the period labels:** the expected day/week/month labels of both windows are derived in the same UTC offset you wrote `start_datetime` in — `-03:00` → Brasília calendar (the timezone the API groups by); `Z` or a plain date (`2026-01-01`, read as UTC by the API) → UTC calendar. So `2026-01-01`..`2026-03-31` (or `...Z`) gives exactly Jan/Feb/Mar paired with Oct/Nov/Dec, with no out-of-range row. If the API returns a real bucket outside the derived window (e.g., a ticket on Dec 31 at 22:00 Brasília time when the bounds are in `Z`), it is kept at the end of the table, never dropped.

**Row labels — `current ↔ previous`:** day/week/month rows are paired by position, so each row shows both labels (`2026-05 ↔ 2025-11`) in the column `Período (atual ↔ anterior)`. When one side has fewer periods, the missing side is shown as `—` (`— ↔ 2026-W36` or `2026-05 ↔ —`). With the automatic comparison period both sides always have the same number of rows; this only happens when you pass explicit `compare_start_datetime`/`compare_end_datetime` covering a different number of periods — in that case the per-row Δ of the extra row is not meaningful, and the totals compare windows of different lengths (they are only comparable if your two windows have the same length). In `compact`, rows keep the current label (`2026-05:3/1`) and a row that exists only on the previous side is prefixed with `ant.` (`ant.2026-W36:0/2`). `desk` rows are matched by desk name and keep the plain name.

**Parameters:**
- `start_datetime` (string, required): Start of the main period (ISO 8601, e.g., "2026-01-01T00:00:00Z" or "2026-01-01T00:00:00-03:00")
- `end_datetime` (string, required): End of the main period (ISO 8601, e.g., "2026-06-30T23:59:59Z")
- `compare_start_datetime` (string, optional): Start of comparison period (ISO 8601). Must be provided with `compare_end_datetime` (complete pair). If omitted, the preceding period with the same number of days/weeks/months (calendar-aligned) is used automatically; `desk` uses the same duration.
- `compare_end_datetime` (string, optional): End of comparison period (ISO 8601). Pair with `compare_start_datetime`.
- `group_by` (string, optional): Granularity — "day", "week", "month" (temporal buckets) or "desk" (per-desk breakdown, useful for "which desk grew"). Default: "month".
- `date_type` (string, optional): Time axis applied to both periods — "created_at" (creation date, default) or "solved_in_time" (closing/resolution date). Consistent across both calls automatically. Accepts timezone offsets beyond Z (e.g., `-03:00`).
- `filter_by` (string, optional): Status filter — "open", "closed", "canceled", or "all". Default depends on `date_type`: "all" for `created_at`; "closed" for `solved_in_time`. Use "all" with `solved_in_time` to include cancelled tickets in the count.
- `desk_ids` (string, optional): Comma-separated desk IDs (max 15). Alternative to `desk_name`.
- `desk_name` (string, optional): Desk/team name for automatic ID resolution. Accepts partial names.
- `client_ids` (string, optional): Comma-separated client (company) IDs (max 15).
- `client_name` (string, optional): Client (company) name for automatic resolution.
- `responsible_ids` (string, optional): Comma-separated responsible user IDs (max 15). Passthrough.
- `requestor_email` (string, optional): Requestor email filter. Passthrough.
- `priority_ids` (string, optional): Comma-separated priority IDs (max 15). Passthrough.
- `services_catalogs_item_ids` (string, optional): Comma-separated catalog item IDs (max 15). Passthrough.

**Example — compare last 6 months vs the 6 before (automatic calendar-aligned period: Jul–Dec 2025):**
```json
{
  "start_datetime": "2026-01-01T00:00:00Z",
  "end_datetime": "2026-06-30T23:59:59Z",
  "group_by": "month"
}
```

**Example — compare two explicit periods for a specific desk:**
```json
{
  "start_datetime": "2026-01-01T00:00:00Z",
  "end_datetime": "2026-06-30T23:59:59Z",
  "compare_start_datetime": "2025-07-01T00:00:00Z",
  "compare_end_datetime": "2025-12-31T23:59:59Z",
  "group_by": "month",
  "desk_name": "Support"
}
```

**Example — which desk grew the most (group_by=desk):**
```json
{
  "start_datetime": "2026-01-01T00:00:00Z",
  "end_datetime": "2026-06-30T23:59:59Z",
  "group_by": "desk",
  "filter_by": "all"
}
```

**Rich response example:**
```markdown
**📊 Comparação de tickets por mês** — por data de criação

| | Período atual | Período anterior | Δ |
|---|---|---|---|
| **Total** | **15** | **12** | **+3 (+25%)** |

| Período (atual ↔ anterior) | Atual | Anterior | Δ | Δ% |
|---|---|---|---|---|
| 2026-01 ↔ 2025-07 | 10 | 8 | +2 | +25% |
| 2026-02 ↔ 2025-08 | 5 | 4 | +1 | +25% |

*✅ Dados obtidos da API TiFlux em tempo real*
```

**Compact response example:**
```
Comparação por mês — por data de criação: atual 15 vs anterior 12 → Δ +3 (+25%)
Buckets (atual/anterior): 2026-01:10/8 · 2026-02:5/4
```

### get_tickets_feedback_report
Relatório de avaliações de atendimento (CSAT) de tickets com comparação automática de período. Retorna métricas de satisfação (média, avaliados, finalizados, taxa de resposta) com deltas entre dois períodos, e opcionalmente a lista de tickets avaliados com comentários.

**When to use vs other tools:**
- **CSAT / satisfaction ratings for TICKETS** → `get_tickets_feedback_report` (this tool)
- **CSAT / satisfaction ratings for CHATS** → `get_chats_feedback_report`
- **Ticket count / trend comparison** → `get_tickets_comparison`

**Default comparison period:** if `compare_start_date`/`compare_end_date` are not provided, the comparison period is the immediately preceding period of the same duration. Provide only `start_date` and `end_date` and the comparison window is calculated automatically.

**Single-day report:** `start_date == end_date` is accepted (e.g. "how was ticket satisfaction yesterday?"). The default comparison period in that case is the previous day.

**Dates are whole UTC days** — time and timezone are ignored by the API.

**No evaluations in a period:** when a period has zero evaluated tickets, the average rating and response rate show as `—` (not `0.0`) — no evaluation happened, it's not a real zero.

**No filter by rating in the API** — to filter by rating, use `include_list=true` and filter the returned list by `rating` client-side.

**Enrichment workflow:**
1. Run with `include_list=true` to get the list of evaluated tickets (with `rating`, `revised_in_time`, client, responsible, desk, and `comments`).
2. For full ticket details (history, SLA, replies) → use `get_ticket` or `list_ticket_answers` with the ticket ID.
3. The `comments` field (evaluation comment) is already in the list — no further enrichment needed for comments.

**Note:** The `comments` field is plural and may be an empty string `""` when the client did not leave a comment.

**Note:** This report requires **administrator/reports permission** — non-admin API keys receive 403.

**Parameters:**
- `start_date` (string, required): Start of the main period (YYYY-MM-DD, e.g., "2026-07-01")
- `end_date` (string, required): End of the main period (YYYY-MM-DD, e.g., "2026-07-31")
- `compare_start_date` (string, optional): Start of comparison period (YYYY-MM-DD). Must be provided with `compare_end_date`. If omitted, the immediately preceding period of the same duration is used.
- `compare_end_date` (string, optional): End of comparison period (YYYY-MM-DD). Pair with `compare_start_date`.
- `include_list` (boolean, optional): If true, includes the paginated list of evaluated tickets in the main period. Default: false.
- `offset` (integer, optional): Page number for the list (default: 1). Only relevant with `include_list=true`.
- `limit` (integer, optional): Items per page (default: 20, max: 200). Only relevant with `include_list=true`.
- `responsible_ids` (string, optional): Comma-separated responsible user IDs (max 15). Applied to both calls (main + comparison).
- `department_ids` (string, optional): Comma-separated department IDs (max 15). Applied to both calls.
- `technical_group_ids` (string, optional): Comma-separated technical group IDs (max 15). Applied to both calls.

**Example — compare this month vs last month:**
```json
{
  "start_date": "2026-07-01",
  "end_date": "2026-07-31"
}
```

**Example — get list of evaluated tickets with comments:**
```json
{
  "start_date": "2026-07-01",
  "end_date": "2026-07-31",
  "include_list": true,
  "limit": 50
}
```

**Rich response example:**
```markdown
**📊 Relatório de avaliações de atendimento — Tickets**

**Período principal:** 2026-07-01 a 2026-07-31
**Período de comparação:** 2026-05-31 a 2026-06-30

| Métrica | Período atual | Período anterior | Δ |
|---------|--------------|-----------------|---|
| Média de avaliação | **4.5** | 4.0 | +0.5 (+12.5%) |
| Tickets avaliados | **20** | 15 | +5 (+33.3%) |
| Tickets finalizados | **100** | 80 | +20 (+25%) |
| Clientes avaliadores | **15** | 12 | +3 (+25%) |
| Taxa de resposta (%) | **80.0** | 75.0 | +5 (+6.7%) |

*✅ Dados obtidos da API TiFlux em tempo real*
```

### close_ticket
Close a specific ticket in Tiflux.

**Parameters:**
- `ticket_number` (string, required): Ticket number to be closed (e.g., "37", "123")

**Example:**
```json
{
  "ticket_number": "84429"
}
```

**Success Response:**
```markdown
**Ticket #84429 fechado com sucesso!**

**Mensagem:** Ticket 84429 closed successfully

*Ticket fechado via API Tiflux*
```

### create_ticket_answer
Create a new answer (client communication) in a specific ticket.

**Parameters:**
- `ticket_number` (string, required): Ticket number where answer will be created
- `text` (string, required): Answer content that will be sent to the client. Accepts Markdown (bold, lists, headings, code) — the MCP automatically converts it to HTML before sending to the API.
- `with_signature` (boolean, optional): Include user signature in the answer (default: false)
- `files_base64` (array, optional): Array of base64 encoded files `[{content: "base64...", filename: "file.pdf"}]` (max 10 files, 40MB each)

**New in v1.3.0:** Support for base64 file upload via `files_base64` parameter.

> **Breaking change (v2.8.0):** O parametro `files` (caminhos locais) foi removido. Use a nova tool `upload_ticket_files` para enviar arquivos via base64, ou passe os arquivos diretamente via `files_base64`.

**Example:**
```json
{
  "ticket_number": "123",
  "text": "Hello, your issue has been resolved.",
  "with_signature": true,
  "files_base64": [{"content": "JVBERi0x...", "filename": "attachment.pdf"}]
}
```

### create_client_answer
Register an answer **on behalf of the client** in a ticket — the message is recorded as if the client had replied, with the author given in `author_name`. To answer the client as an attendant, use `create_ticket_answer`.

**Permissions:** Administrators only (403 otherwise — the error message says so).

**Audit trail:** the answer is not indistinguishable from a real client reply — the API prefixes the author with `[API]` (e.g. `[API] Maria Souza`) and records the origin as `api`, so the ticket history shows it was registered via the API.

**Parameters:**
- `ticket_number` (string, required): Ticket number
- `text` (string, required): Client's answer. Accepts Markdown — the MCP converts it to HTML before sending (`name` field).
- `author_name` (string, required): Client name shown as the author
- `files_base64` (array, optional): `[{content: "base64...", filename: "file.pdf"}]` (max 10 files, 25MB each)

**Example:**
```json
{
  "ticket_number": "123",
  "text": "Tested here and it **works** now, thanks!",
  "author_name": "Maria Souza"
}
```

**Returns:** Answer ID, author (e.g. `[API] Maria Souza`), date/time (`answer_time`), origin (`answer_origin`) and the number/names of attached files. Endpoint: `POST /tickets/{ticket_number}/client-answers` (always multipart).

### search_client
Search for clients by name (shortcut — name-only). Use `list_clients` for full filters and pagination.

**Parameters:**
- `client_name` (string, required): Client name to search (partial match supported)

### list_clients
List clients with filters and pagination. Full version of `search_client` — accepts status, name, and CPF/CNPJ filters.

**Parameters:**
- `active` (boolean, optional): Filter by status: true = active only, false = inactive only. Omit for all.
- `name` (string, optional): Filter by name (partial match)
- `social_revenue` (string, optional): Filter by CPF/CNPJ
- `offset` (number, optional): Page number (default: 1)
- `limit` (number, optional): Results per page (default: 20, max: 200)

**Example:**
```json
{
  "active": true,
  "name": "Acme",
  "limit": 10
}
```

### get_client
Get full details of a client by ID.

**Parameters:**
- `client_id` (number, required): Client ID (obtained via `search_client` or `list_clients`)
- `show_entities` (boolean, optional): Include custom fields (entities) in the response (default: false)

**Fixed in v2.50.1:** `Criado em`/`Atualizado em` now show in Brasília time (previously the raw ISO string from the API, e.g. `2026-10-01T14:50:45Z`).

**Note:** the API v2 started returning `created_at`/`updated_at` in `GET /clients/{id}` on 2026-10-06, so `Criado em`/`Atualizado em` now actually appear in the response (Brasília time in `rich`, UTC with `Z` in `compact`). `list_clients`/`search_client` don't include these dates — the listing endpoint doesn't return them.

**Example:**
```json
{
  "client_id": 42,
  "show_entities": true
}
```

### create_client
Create a new client in Tiflux. Only `name` and `social` are required; all other fields are optional and only sent if provided.

**Parameters:**
- `name` (string, required): Client trade name (nome fantasia)
- `social` (string, required): Client legal name (razão social)
- `social_revenue` (string, optional): CPF or CNPJ
- `desk_ids` (array of numbers, optional): Desk IDs to associate
- `add_all_desks` (boolean, optional): Associate all desks
- `technical_group_ids` (array of numbers, optional): Technical group IDs to associate
- `status` (boolean, optional): Active (true) or inactive (false) — default: true
- `max_agents` (number, optional): Maximum agents allowed
- `email_financial` (string, optional): Financial contact email
- `anotations` (string, optional): Internal notes
- `billing_report_type` (string, optional): `detailed_with_appointment`, `detailed`, `synthetic`, or `""` (empty)

**Example:**
```json
{
  "name": "Acme Corp",
  "social": "Acme Corporação Ltda",
  "social_revenue": "12.345.678/0001-99",
  "desk_ids": [1, 2]
}
```

### update_client
Update an existing client (partial update — only provided fields are sent).

**Parameters:**
- `client_id` (number, required): Client ID to update
- All fields from `create_client` (all optional)

**Response:** confirmation with ID, name, updated fields and `Atualizado em` (the API's `updated_at` — Brasília time in `rich`, UTC in `compact`), i.e. the client's last actual change. If the submitted values are identical to the current ones, the API doesn't touch `updated_at`, so the date stays the previous one.

**Example:**
```json
{
  "client_id": 42,
  "status": false,
  "email_financial": "novo@empresa.com"
}
```

### update_client_entities
Update custom fields (entities) for a client. Supports up to 50 fields per request. For checkbox fields with multiple options, send one item per option with `entity_field_id + entity_field_option_id + value: "true"/"false"`.

> **Tip:** Prefer `entity_field_id` (numeric) when available — it avoids extra API calls. Use the `_name` params only when you don't have the ID yet.

**Parameters:**
- `client_id` (number, required): Client ID to update
- `entities` (array, required): List of custom fields. Each item:
  - `entity_field_id` (number): Custom field ID. Prefer this when available.
  - `entity_name` (string, optional): Entity group name for automatic `entity_field_id` resolution.
  - `entity_field_name` (string, optional): Field name for automatic `entity_field_id` resolution (use with `entity_name`).
  - `entity_field_option_name` (string, optional): Option name for automatic `entity_field_option_id` resolution.
  - `value` (string, required): Field value (or null to clear)
  - `entity_field_option_id` (number, optional): Option ID for checkbox/single_select
  - `country_code` (string, optional): Country code for phone fields

**Example — with IDs (most efficient):**
```json
{
  "client_id": 42,
  "entities": [
    { "entity_field_id": 72, "value": "TI" },
    { "entity_field_id": 80, "entity_field_option_id": 12, "value": "true" }
  ]
}
```

**Example — resolving by name:**
```json
{
  "client_id": 42,
  "entities": [
    {
      "entity_name": "Dados comerciais",
      "entity_field_name": "Segmento",
      "entity_field_option_name": "Tecnologia",
      "value": "true"
    }
  ]
}
```

### get_client_desks
List desks associated with a client.

**Parameters:**
- `client_id` (number, required): Client ID
- `offset` (number, optional): Page number (default: 1)
- `limit` (number, optional): Results per page (default: 20, max: 200)

### get_client_technical_groups
List technical groups associated with a client.

**Parameters:**
- `client_id` (number, required): Client ID
- `offset` (number, optional): Page number (default: 1)
- `limit` (number, optional): Results per page (default: 20, max: 200)

### create_client_user
Create a portal user for a client. Allows the user to access the client portal.

**Parameters:**
- `client_id` (number, required): Client ID to associate the user with
- `name` (string, required): Full name of the user
- `email` (string, required): User email — used for portal login
- `extension` (string, optional): Phone extension
- `authorization_flow` (boolean, optional): Require authorization for portal access
- `telephone` (string, optional): Phone number
- `country_code` (string, optional): Country code for the phone number

**Example:**
```json
{
  "client_id": 42,
  "name": "João Silva",
  "email": "joao@empresa.com",
  "telephone": "11999999999"
}
```

### add_client_email_permission
Add an authorized domain or email to open tickets on behalf of a client.

**Parameters:**
- `client_id` (number, required): Client ID
- `address` (string, required): Domain (e.g. `@empresa.com.br`) or specific email authorized to open tickets for this client

**Example:**
```json
{
  "client_id": 42,
  "address": "@empresa.com.br"
}
```

### list_client_addresses
List the addresses registered for a client.

**Parameters:**
- `client_id` (number, required): Client ID
- `offset` (number, optional): Page number (default: 1)
- `limit` (number, optional): Results per page (default: 20, max: 200)

### get_client_address
Get details of a specific address of a client.

**Parameters:**
- `client_id` (number, required): Client ID
- `id` (number, required): Address ID

### create_client_address
Create a new address for a client.

**Parameters:**
- `client_id` (number, required): Client ID
- `cep` (string, required): ZIP code (e.g. `"89201-305"`)
- `city` (string, required): City
- `neighborhood` (string, required): Neighborhood/district
- `number` (number, required): Street number (integer)
- `state` (string, required): State — 2-letter code (e.g. `"SC"`)
- `street` (string, required): Street name
- `complement` (string, optional): Address complement (e.g. `"Sala 3"`)

**Example:**
```json
{
  "client_id": 42,
  "cep": "89201-305",
  "city": "Joinville",
  "neighborhood": "Centro",
  "number": 100,
  "state": "SC",
  "street": "Rua das Flores",
  "complement": "Sala 3"
}
```

### update_client_address
Partially update an address of a client. Only the provided fields are sent.

**Parameters:**
- `client_id` (number, required): Client ID
- `id` (number, required): Address ID to update
- `cep` (string, optional): ZIP code
- `city` (string, optional): City
- `neighborhood` (string, optional): Neighborhood
- `number` (number, optional): Street number
- `state` (string, optional): State code
- `street` (string, optional): Street name
- `complement` (string, optional): Complement

### delete_client_address
Remove an address from a client.

**Parameters:**
- `client_id` (number, required): Client ID
- `id` (number, required): Address ID to remove

### list_client_contacts
List the contacts (phone/email) registered for a client.

**Parameters:**
- `client_id` (number, required): Client ID
- `offset` (number, optional): Page number (default: 1)
- `limit` (number, optional): Results per page (default: 20, max: 200)

### get_client_contact
Get details of a specific contact of a client.

**Parameters:**
- `client_id` (number, required): Client ID
- `id` (number, required): Contact ID

### create_client_contact
Create a new contact (phone/email) for a client.

**Parameters:**
- `client_id` (number, required): Client ID
- `use` (string, required): Contact usage type (e.g. `"Personal"`, `"Commercial"`)
- `number` (string, required): Phone number (accepts BR and international formats)
- `owner` (string, required): Name of the contact owner
- `email` (string, required): Contact email
- `country` (string, optional): Country code (e.g. `"BR"`, `"US"`)

**Example:**
```json
{
  "client_id": 42,
  "use": "Commercial",
  "number": "47999990000",
  "owner": "João Silva",
  "email": "joao@empresa.com",
  "country": "BR"
}
```

### update_client_contact
Partially update a contact of a client. Only the provided fields are sent.

**Parameters:**
- `client_id` (number, required): Client ID
- `id` (number, required): Contact ID to update
- `use` (string, optional): Contact usage type
- `number` (string, optional): Phone number
- `owner` (string, optional): Contact owner name
- `email` (string, optional): Contact email
- `country` (string, optional): Country code

### delete_client_contact
Remove a contact from a client.

**Parameters:**
- `client_id` (number, required): Client ID
- `id` (number, required): Contact ID to remove

### search_user
Search for users by name to use as responsible in tickets.

**Parameters:**
- `name` (string, required): User name to search (partial match supported, searches in name and email)
- `type` (string, optional): User type filter (client, attendant, admin)
- `active` (boolean, optional): Filter active (true) or inactive (false) users
- `limit` (number, optional): Results per page (default: 20, max: 200)
- `offset` (number, optional): Page number (default: 1)

**Non-admin support (fallback via attendant groups):**
If the API key belongs to a non-admin user, `GET /users` returns 403. In this case, the tool automatically falls back to enumerating attendant groups (`GET /technical-groups`) and their members (`GET /technical-groups/{id}/users`), then applies fuzzy matching by name. The result is identical to the admin path — no parameter change needed. A note is added to the output when the fallback was used.

**Implementation Note:**
For admin users, the Tiflux API does not support name-based filtering in the `/users` endpoint — the tool fetches up to 200 users and filters client-side. For non-admin users, the tool uses the technical-groups chain and deduplicates users that appear in multiple groups.

**Example:**
```json
{
  "name": "John",
  "type": "attendant",
  "active": true
}
```

### search_technical_user
Search for technical attendants (users who can be assigned as responsible) in Tiflux by name, email, desk, or client. Uses the `GET /technical-users` endpoint — **does not require user management permission** (works for both admin and non-admin attendants). Use the returned `id` as `responsible_id` when creating or updating a ticket.

**Note on `responsible_name` auto-resolve:** When `responsible_name` is passed to `create_ticket`, `update_ticket`, or `list_tickets`, the MCP now uses `GET /technical-users` as the **primary** resolution path (fast, 1 round-trip, works for all profiles). The old fallback via `GET /technical-groups` is only triggered if the primary path returns an unexpected error (404/403), preserving compatibility with orgs where the endpoint may not be available.

**Parameters:**
- `name` (string, optional): Attendant name to search (partial match, case-insensitive, server-side)
- `email` (string, optional): Attendant email (partial match, case-insensitive, server-side)
- `desk_id` (number, optional): Filter attendants who serve this desk
- `client_id` (number, optional): Filter attendants who serve this client
- `limit` (number, optional): Results per page (default: 20, max: 200)
- `offset` (number, optional): Page number (default: 1)

**Example:**
```json
{
  "name": "Ana",
  "desk_id": 123
}
```

**Returns:** List of attendants with `id`, `name`, and `email`. Use the `id` in `responsible_id`.

### create_user
Create a new user/agent in Tiflux (**requires administrator permission** — returns 403 for non-admin keys). Required fields: `name`, `email`, and either `technical_group_id` or `technical_group_name`. License fields are optional and only sent if provided.

> **Admin-only:** This tool uses `POST /users`, which the API restricts to admin keys. Non-admin API keys will receive a clear error message indicating administrator permission is required.

**Parameters:**
- `name` (string, required): User name
- `email` (string, required): User email
- `technical_group_id` (number, optional): ID of the technical group to assign the user to (required if `technical_group_name` not provided)
- `technical_group_name` (string, optional): Name of the technical group — fuzzy match; if ambiguous, lists available groups and asks for clarification
- `whatsapp_license` (boolean, optional): WhatsApp license
- `tickets_license` (boolean, optional): Tickets license
- `remote_access_license` (boolean, optional): Remote access license
- `api_license` (boolean, optional): API license
- `splashtop_license` (boolean, optional): Splashtop license

**`technical_group_name` semantics:** If the name resolves to exactly 1 group, it proceeds. If 0 or N groups match (or no name is provided), the tool lists all available groups (`id — name`) and asks you to specify — no silent failure.

**Example:**
```json
{
  "name": "Ana Silva",
  "email": "ana.silva@empresa.com",
  "technical_group_name": "Suporte",
  "tickets_license": true
}
```

**Returns:** Confirmation with the created user's ID and name. Use the ID as `responsible_id` in tickets.

### get_user
Retrieve full details of a user/agent by ID (**requires administrator permission**).

> **Admin-only:** This tool uses `GET /users/{id}`, which the API restricts to admin keys.

**Parameters:**
- `id` (number, required): User ID

**Example:**
```json
{
  "id": 42
}
```

**Returns:** User details including: `id`, `name`, `email`, type (`_type`), active status, `technical_group_id`, `telephone`, `extension`, `client_ids`, `last_login_at`, Google Auth status (`gauth_enabled`), and `signature`.

### update_user
Update an existing user/agent (partial update — **requires administrator permission**). The `id` field is required; at least one data field must be provided.

> **Admin-only:** This tool uses `PUT /users/{id}`, which the API restricts to admin keys.

**Parameters:**
- `id` (number, required): User ID
- `name` (string, optional): New user name
- `email` (string, optional): New user email
- `technical_group_id` (number, optional): New technical group ID
- `technical_group_name` (string, optional): New technical group name — fuzzy match (see `create_user` semantics above)
- `active` (boolean, optional): Activate (`true`) or deactivate (`false`) the user
- `extension` (string, optional): User extension
- `telephone` (string, optional): User telephone
- `client_ids` (array of numbers, optional): Client IDs linked to this user
- `country_code` (string, optional): Country code
- `whatsapp_license` (boolean, optional): WhatsApp license
- `tickets_license` (boolean, optional): Tickets license
- `remote_access_license` (boolean, optional): Remote access license
- `api_license` (boolean, optional): API license
- `splashtop_license` (boolean, optional): Splashtop license

**Example:**
```json
{
  "id": 42,
  "active": false,
  "technical_group_name": "Dev"
}
```

**Returns:** Confirmation with user ID, updated name, and list of updated fields.

### update_my_profile
Update the profile of the **authenticated user itself** — the owner of the API key or of the OAuth login used by the MCP (same behavior in Server and SDK modes). To edit another user, use `update_user`.

**Parameters** (all optional, at least one required — an empty call returns "Nenhum campo para atualizar" without calling the API; unknown fields are ignored):
- `name` (string): New name
- `extension` (integer): Extension — an integer here (unlike `update_user`, where it is a string). Digit-only strings (`"500"`) are coerced; anything else is rejected before the API call.
- `telephone` (string): Telephone
- `country_code` (string): ISO 3166-1 alpha-2 country of the telephone (e.g. `"BR"`, `"US"`; upper-cased automatically). The API assumes `BR` when omitted.

**E-mail change is blocked by the MCP.** The API accepts `email`, but an agent manipulated by prompt injection could point the account to an attacker's address (the confirmation link would go there → account takeover). `email` is not in the schema; if a client sends it anyway, the whole call is refused before reaching the API ("Troca de e-mail não permitida pelo MCP") and nothing is changed. Change the e-mail through the TiFlux portal.

**Example:**
```json
{ "telephone": "(51) 99999-9999", "country_code": "BR" }
```

**Returns:** Updated profile fields (name, e-mail, extension, telephone) and the list of fields sent. Endpoint: `PUT /users/profile`.

### search_requestor
Search for requestors (ticket openers) in Tiflux by name, email, or telephone. Uses the dedicated `GET /requestors` endpoint with server-side filtering — no client-side limit.

**Automatic fallback chain (triggers on `403` OR on zero results — no questions asked):** the tool tries each source in order and returns the first that finds someone, so it also works for non-admin attendants and for searches where the term exists only as a user (not as a registered requestor):

1. `GET /requestors` — global requestors (admin/global permission). → use the `id` as `requestor_id`.
2. `GET /clients/{client_id}/requestors` — client-scoped requestors (only if `client_id` is provided; clients are already filtered by the attendant's permission). → `requestor_id`.
3. `GET /users` — users matching the name/email. Users are **not** requestors, but their **email** can be used as `requestor_email` when creating the ticket.
4. `GET /users/me` — the current user. Suggests opening the ticket as yourself, using your own **email** as `requestor_email`.

A non-`403` hard error (e.g. 5xx) on the primary endpoint is surfaced instead of being masked by the chain. The calling LLM decides the next step from the suggestion.

**Parameters:**
- `name` (string, optional): Requestor name to search (partial match, server-side)
- `email` (string, optional): Requestor email to search
- `telephone` (string, optional): Requestor phone number (no country code, no symbols)
- `can_open_ticket` (boolean, optional): Filter requestors who can (true) or cannot (false) open tickets by email
- `client_id` (number, optional): Client ID to scope the search. Enables the automatic `GET /clients/{id}/requestors` fallback when the global endpoint returns 403.
- `limit` (number, optional): Results per page (default: 20, max: 200)
- `offset` (number, optional): Page number (default: 1)

**Note:** At least one filter parameter must be provided.

**Returns:** When found via levels 1–2, a list of requestors with id, name, email, telephone, client.name, and can_open_ticket (use the `id` as `requestor_id`). When found only via level 3 (users) or level 4 (yourself), a suggestion to use the matched **email** as `requestor_email`.

**Example:**
```json
{
  "name": "João Silva"
}
```

**Example (by email):**
```json
{
  "email": "joao@empresa.com"
}
```

### list_requestors
List the requestors of a specific client (canonical per-client listing — `GET /clients/{id}/requestors`, aligned with `list_clients`). Use `search_requestor` when you need a cross-client search with the fallback chain; use `list_requestors` for the requestor catalog of a known client.

**Parameters:**
- `client_id` (number, required): Client whose requestors will be listed
- `name` (string, optional): Filter by name (partial match)
- `email` (string, optional): Filter by email
- `telephone` (string, optional): Filter by phone (digits only, no country code)
- `extension` (string, optional): Filter by extension
- `can_open_ticket` (boolean, optional): Filter requestors who can (true) or cannot (false) open tickets by email
- `include_entity_fields` (boolean, optional): Include each requestor's custom fields in the response (default: false). When true, each field shows type, `required` flag, and — for `single_select`/`checkbox` types — the marked options with title and IDs for `list_entity_field_options`.
- `offset` (number, optional): Page number (default: 1)
- `limit` (number, optional): Requestors per page (default: 20, max: 200)

**Returns:** A paginated list of requestors with id, name, email, telephone, extension, can_open_ticket, and (when `include_entity_fields=true`) custom field details including marked option titles and IDs.

**Example:**
```json
{
  "client_id": 123,
  "include_entity_fields": true
}
```

### get_requestor
Get full details of a requestor of a client by ID (`GET /clients/{id}/requestors/{requestor_id}`). Returns registration data and optional custom fields (entities, `applied_in: "solicitant"`).

**Parameters:**
- `client_id` (number, required): Client the requestor belongs to
- `requestor_id` (number, required): Requestor ID (obtained via `list_requestors` or `search_requestor`)
- `show_entities` (boolean, optional): Include custom fields (entities) in the response (default: false). When true, each field shows type, `required` flag (suffix `(obrigatório)`), `entity_field_id`, and — for `single_select`/`checkbox` types — the marked options with IDs and a hint to call `list_entity_field_options` for all available options.

**Fixed in v2.50.1:** `Criado em`/`Atualizado em` now show in Brasília time (previously the raw ISO string from the API, e.g. `2026-10-01T14:50:45Z`).

**Example:**
```json
{
  "client_id": 123,
  "requestor_id": 555,
  "show_entities": true
}
```

### create_requestor
Create a new requestor in a client (`POST /clients/{id}/requestors`). Required fields: `client_id`, `name`, `email`. Other fields are optional and only sent if provided.

> **Note:** `telephone` is optional here. When it comes to ticket creation, field requirements may differ — they depend on the desk's `required_fields` (see `GET /desks/{id}`), not on the requestor registration endpoint.

**Parameters:**
- `client_id` (number, required): Client to link the requestor to
- `name` (string, required): Requestor name
- `email` (string, required): Requestor email
- `telephone` (string, optional): Requestor phone. If provided, must be a valid number — do not send an empty string (causes 422)
- `can_open_ticket` (boolean, optional): Whether the requestor can open tickets by email
- `extension` (string, optional): Requestor extension
- `country` (string, optional): Requestor country

**Returns:** The created requestor's id, name, email, and telephone (if provided).

**Example:**
```json
{
  "client_id": 123,
  "name": "João Silva",
  "email": "joao@empresa.com"
}
```

### update_requestor
Update an existing requestor (`PUT /clients/{id}/requestors/{requestor_id}`). Partial update — only the provided fields are sent. Use `get_requestor` to see the current state before updating.

**Parameters:**
- `client_id` (number, required): Client the requestor belongs to
- `requestor_id` (number, required): Requestor ID to update
- `name` (string, optional): Requestor name
- `telephone` (string, optional): Requestor phone (digits only)
- `email` (string, optional): Requestor email
- `can_open_ticket` (boolean, optional): Whether the requestor can open tickets by email
- `extension` (string, optional): Requestor extension

If no updatable field is provided, returns a friendly error.

**Example:**
```json
{
  "client_id": 123,
  "requestor_id": 555,
  "can_open_ticket": true
}
```

### update_requestor_entities
Update a requestor's custom fields (entities) (`PUT /clients/{id}/requestors/{requestor_id}/entities`). Supports up to 50 fields per request. For checkbox fields with multiple options, send one item per option with `entity_field_id` + `entity_field_option_id` + `value: "true"/"false"`.

**Prefer IDs when known** to avoid resolution round-trips. Use `entity_name`/`entity_field_name`/`entity_field_option_name` for automatic resolution when you don't have the IDs — resolution is scoped to requestor entities (`applied_in: "solicitant"`).

**Parameters:**
- `client_id` (number, required): Client the requestor belongs to
- `requestor_id` (number, required): Requestor ID to update
- `entities` (array, required): List of custom fields (see `update_client_entities` for the item shape)

**Example:**
```json
{
  "client_id": 123,
  "requestor_id": 555,
  "entities": [
    { "entity_field_id": 88, "value": "Premium" }
  ]
}
```

### delete_requestor
Delete a requestor of a client. **Irreversible through the API.** The requestor must belong to the given `client_id`.

**Permissions:** Requires "Gerenciar clientes e grupos de recursos" (403 otherwise).

**Parameters:**
- `client_id` (number, required): Client the requestor belongs to
- `requestor_id` (number, required): Requestor ID to delete (use `list_requestors`/`search_requestor` to find it)

**Example:**
```json
{ "client_id": 724, "requestor_id": 555 }
```

**Behavior observed on 2026-10-06:** the API answers `204` and the requestor disappears from listings (`list_requestors`/`search_requestor`), but `get_requestor` by ID still returns it and repeating the DELETE answers `204` again (soft delete, with no restore endpoint).

**Returns:** `✅ Solicitante #555 (Name) removido` — the name comes from a best-effort lookup before the deletion (if it fails, the delete still runs and the message omits the name). 404 → the requestor does not exist or belongs to another client. Endpoint: `DELETE /clients/{client_id}/requestors/{id}`.

### search_stage
Search for stages of a specific desk to use in ticket updates.

**Parameters:**
- `desk_id` (number, optional): Desk ID to search stages
- `desk_name` (string, optional): Desk name for automatic search (alternative to desk_id). Accepts partial names — e.g. `"cansados"` resolves to `"Dev - Cansados"` (see Smart Name Resolution)
- `limit` (number, optional): Results per page (default: 20, max: 200)
- `offset` (number, optional): Page number (default: 1)

**Note:** At least one parameter (desk_id or desk_name) must be provided.

**Example:**
```json
{
  "desk_name": "Suporte",
  "limit": 20
}
```

### search_catalog_item
Search for service catalog items by free-text term or by name/filter within a specific desk. Use `search` to explore items by keyword (server-side, matches catalog name, area name, or item name). Use `catalog_item_name` to locate a specific item by name (client-side, collapses to single detail when exactly 1 match).

> **Disambiguation:** this tool is desk-scoped (items selectable in tickets of a specific desk). To list items for a specific catalog **area** across the whole organization (CRUD management), use `list_services_catalog_items`. To list catalogs org-wide, use `list_services_catalogs`.

**Parameters:**
- `desk_id` (number, optional): Desk ID to search catalog items
- `desk_name` (string, optional): Desk name for automatic search (alternative to desk_id). Accepts partial names — e.g. `"cansados"` resolves to `"Dev - Cansados"` (see Smart Name Resolution)
- `search` (string, optional): Free-text term for server-side search across catalog name, area name, and item name. Partial match, case-insensitive, accent-insensitive. Returns a listing with full hierarchy (catalog → area → item). Combinable with `area_id`/`catalog_id` to narrow scope.
- `catalog_item_name` (string, optional): Catalog item name to search (client-side partial match on item name only). 1 match → detailed view; multiple → error with list. Use `search` for broader exploration.
- `area_id` (number, optional): Service area ID to filter results
- `catalog_id` (number, optional): Service catalog ID to filter results
- `limit` (number, optional): Results per page (default: 20, max: 200)
- `offset` (number, optional): Page number (default: 1)

**Note:** At least one of `desk_id` or `desk_name` must be provided, along with at least one of `search`, `catalog_item_name`, `area_id`, or `catalog_id`.

**Example — free-text search (recommended for exploration):**
```json
{
  "desk_name": "Support",
  "search": "infra",
  "limit": 10
}
```

**Example — locate a specific item by name:**
```json
{
  "desk_name": "Support",
  "catalog_item_name": "Installation",
  "limit": 10
}
```

## Internal Communications

### create_internal_communication
Create a new internal communication in a ticket.

**Parameters:**
- `ticket_number` (string, required): Ticket number where communication will be created
- `text` (string, required): Communication content. Accepts Markdown (bold, lists, headings, code) — the MCP automatically converts it to HTML before sending to the API.
- `files_base64` (array, optional): Array of base64 encoded files `[{content: "base64...", filename: "file.pdf"}]` (max 10 files, 25MB each)

**New in v1.3.0:** Support for base64 file upload via `files_base64` parameter.

> **Breaking change (v2.8.0):** O parametro `files` (caminhos locais) foi removido. Use a nova tool `upload_ticket_files` para enviar arquivos via base64, ou passe os arquivos diretamente via `files_base64`.

**Fixed in v2.50.1:** `Criada em` in the success response now shows the creation date/time in Brasília time (previously the raw ISO string from the API, e.g. `2026-10-01T14:50:45Z`).

**Example:**
```json
{
  "ticket_number": "123",
  "text": "Internal communication content",
  "files_base64": [{"content": "base64...", "filename": "relatorio.pdf"}]
}
```

### list_internal_communications
List internal communications for a ticket.

**Parameters:**
- `ticket_number` (string, required): Ticket number to list communications
- `offset` (number, optional): Page number (default: 1)
- `limit` (number, optional): Communications per page (default: 20, max: 200)

**Fixed in v2.50.1:** Each communication's date/time now shows in Brasília time (previously used the host process's timezone, showing the wrong day for events between 21h–0h on a Lambda running in UTC).

### get_ticket_files
Get all files attached to a specific ticket.

**Parameters:**
- `ticket_number` (string, required): Ticket number to retrieve files from

**Returns:**
List of files with details including:
- File ID, name, content type
- File size (formatted as KB/MB/GB)
- URL for download
- Created date and creator information

**Example:**
```json
{
  "ticket_number": "123"
}
```

### upload_ticket_files
Upload files to an existing ticket in Tiflux. Files must be provided as base64-encoded content.

**Parameters:**
- `ticket_number` (string, required): Ticket number where files will be attached (e.g., "123", "456")
- `files_base64` (array, required): Array of base64 encoded files `[{content: "base64...", filename: "file.pdf"}]` (max 10 files, 25MB each)

**Example:**
```json
{
  "ticket_number": "123",
  "files_base64": [
    {"content": "base64encodedcontent...", "filename": "relatorio.md"},
    {"content": "base64encodedcontent...", "filename": "screenshot.png"}
  ]
}
```

**Returns:** Confirmation with list of uploaded files.

**Note:** Uploaded text files (`.md`, `.txt`, `.csv`, `.json`) are sent with the appropriate `charset=utf-8` content type, which prevents character encoding issues (mojibake) in the Tiflux portal.

### delete_ticket_file
Remove a file attached to a ticket in Tiflux.

**Parameters:**
- `ticket_number` (string, required): Ticket number from which the file will be removed (e.g., "123", "456")
- `file_id` (string, required): ID of the file to remove (obtained via `get_ticket_files`)

**Example:**
```json
{
  "ticket_number": "123",
  "file_id": "456"
}
```

**Returns:** Confirmation that the file was removed.

### get_ticket_stages_slas
List the full history of a ticket as it moved through the desk's stages, with the SLA outcome for each stage. Useful for SLA audits, escalation reviews, and bottleneck analysis.

**Parameters:**
- `ticket_number` (string, required): Ticket number (e.g., "123", "456")
- `offset` (number, optional): Page number (default: 1)
- `limit` (number, optional): Records per page (default: 20, max: 200)

**Returns:**
For each stage transition, the formatted output includes:
- Stage name and desk
- Time spent in expedient (`HH:MM`)
- Whether the SLA was met (`✅ Sim` / `❌ Não`)
- SLA expiration timestamp
- When the ticket entered the stage and who moved it
- When (if) the ticket was attended in this stage and by whom

**Empty result:** Tickets on desks **without an active SLA** return an empty list with an explanatory message — this is expected behavior, not an error.

**Example:**
```json
{
  "ticket_number": "32",
  "offset": 1,
  "limit": 20
}
```

### get_ticket_service_types
List the service types available for billing/valorization of an appointment on a ticket. Returns the active contract riders (with add-on number, validity, and ID) and the loose services (with ID and name) applicable on the given date. Useful for discovering which services or contracts can be referenced when creating a valued appointment.

**Parameters:**
- `ticket_number` (string, required): Ticket number (e.g., "123", "456")
- `date` (string, optional): Reference date in ISO format `YYYY-MM-DD` (default: today). Cannot be a future date.

**Returns:**
Two sections in the formatted output:
- **📄 Contratos / Adendos vigentes** — for each `contract_rider`: contract name, add-on number (`rider_number`), validity period (`start_date` → `cancel_date`), and the add-on ID.
- **🔧 Serviços avulsos** — for each `loose_service`: `id` + `name`.

Each list can be independently empty (shows "none found" for that section); **both empty** returns an explanatory message (no contracts/services applicable for that ticket on the given date).

**Example:**
```json
{
  "ticket_number": "123",
  "date": "2026-08-04"
}
```

### get_ticket_shifts
List the displacements (travel/visit) available for valorization of an appointment on a ticket. Displacement is a valorization component representing the travel cost to the client. Useful for discovering which displacements can be referenced when creating a valued appointment. Sister tool of `get_ticket_service_types`.

**Parameters:**
- `ticket_number` (string, required): Ticket number (e.g., "98875", "123")
- `contract_id` (number, optional): Contract ID (positive integer) to filter displacements linked to a specific contract. Omit to return all displacements applicable to the ticket. A non-integer value fails locally with a clear message, without calling the API.

**Returns:**
List of available displacements. Each displacement includes:
- `name` and `id`
- `reference` scope label (All = generic for all clients/contracts; Client = exclusive to the client; Contract = exclusive to the contract; Shared = group of contracts)
- Linked `contract` or `client` when present (not shown for generic "All" items)

Empty result returns a friendly explanatory message.

**Example:**
```json
{
  "ticket_number": "98875",
  "contract_id": 88558
}
```

### get_ticket_checklists
List the checklists (forms) of a ticket, with all fields and their fill state. Useful for understanding which fields are pending and **why a ticket cannot be closed** — a checklist with `pending: true` means there is a required unfilled field blocking closure. Each field shows its `index` (the only way to reference it for writing), type, whether it is required, fill state, and the value or options depending on the field type. **Option ids for `checkbox` and `radio` fields are exposed in the output** so they can be used directly in `update_ticket_checklist_item`.

**Parameters:**
- `ticket_number` (string, required): Ticket number (e.g., "98875", "123")
- `offset` (number, optional): Page number (default: 1)
- `limit` (number, optional): Checklists per page (default: 20, max: 200)

**Returns:**
Per checklist:
- Name, description, `required` (informational — whether the checklist is required for the client/catalog item), `pending` (whether a required field is missing — this blocks closure), creation and update dates (**fixed in v2.50.1:** now shown in Brasília time — previously used the host process's timezone, showing the wrong day for events between 21h–0h on a Lambda running in UTC).

Per field (`fields[]`):
- `index`: the positional reference for the field (the only identifier — not persistent across changes)
- `title`, `type` (`text`, `textarea`, `value`, `radio`, `checkbox`)
- Whether it is required and whether it is filled
- Visual marker for required-but-unfilled fields (blocks ticket closure)
- Value/options by type:
  - `text` / `textarea` / `value`: shows the filled value or "— vazio —"
  - `radio`: shows the chosen option and lists all available options with their ids (e.g., `[id: 2] Média`) for use in `update_ticket_checklist_item`
  - `checkbox`: lists all options with their ids and marks which are checked (☑ `[id: a] E-mail`) and which are not (☐ `[id: b] VPN`); `id: null` is also displayed (it is addressable via the API)

**Example:**
```json
{
  "ticket_number": "98875",
  "offset": 1,
  "limit": 20
}
```

Empty result (no checklists) returns a friendly explanatory message. A ticket without checklists can be closed normally.

### update_ticket_checklist_item
Fill or clear a single checklist field for a ticket. One field per call (1:1 with the API). Use `get_ticket_checklists` first to obtain `checklist_id`, `index`, and the option ids for `checkbox`/`radio` fields.

**Important constraints:**
- Checklists without an `id` (not originated from a template) cannot be updated.
- Fields of a closed ticket cannot be updated.
- The payload is mutually exclusive: send either `value` OR `options`, never both.

**Parameters:**
- `ticket_number` (string, required): Ticket number (e.g., "98875")
- `checklist_id` (number, required): Checklist id as returned by `get_ticket_checklists`
- `index` (number, required): Field position within the checklist (from `get_ticket_checklists`)
- `value` (string | number | null, optional): Value to fill. Use for `text`, `textarea`, `value` fields (any string/number) and `radio` (the option id). Send `null` to clear any field type. Mutually exclusive with `options`.
- `options` (array, optional): For `checkbox` fields only. Array of `{ id, checked }` — send only the options you want to change; others remain unchanged. Use the ids from `get_ticket_checklists`. Mutually exclusive with `value`.

**Payload by field type:**

| Field type | Payload |
|------------|---------|
| `text` / `textarea` / `value` | `{ "value": "text content" }` |
| `radio` | `{ "value": "<option_id>" }` (id from `get_ticket_checklists`) |
| `checkbox` | `{ "options": [{ "id": "<option_id>", "checked": true }] }` |
| Clear any field | `{ "value": null }` |

**Examples:**
```json
// Fill a text field
{ "ticket_number": "98875", "checklist_id": 2, "index": 0, "value": "Client notified" }

// Select a radio option
{ "ticket_number": "98875", "checklist_id": 2, "index": 3, "value": "2" }

// Mark checkbox options
{ "ticket_number": "98875", "checklist_id": 2, "index": 4, "options": [{ "id": "a", "checked": true }, { "id": "b", "checked": false }] }

// Clear any field
{ "ticket_number": "98875", "checklist_id": 2, "index": 1, "value": null }
```

**Returns:** The updated checklist with all fields and their new state, plus the `pending` status of the checklist (whether it still blocks ticket closure).

**Errors:**
- `404`: Ticket or checklist not found
- `422`: Attribute incompatible with field type (e.g., `value` sent to a `checkbox` field — use `options` instead)
- `403`: No permission or no Tickets license

### list_ticket_answers
List answers (communications with the client) of a specific ticket, paginated.

**Parameters:**
- `ticket_number` (integer, required): Ticket number to list answers from
- `offset` (number, optional): Page number (default: 1)
- `limit` (number, optional): Answers per page (default: 20, max: 200)

**Returns:**
Each answer includes:
- Author name, date/time (**fixed in v2.50.1:** Brasília time — previously used the host process's timezone), origin (`agent`, `client`, etc.)
- File count indicator
- Preview of the content (first 200 characters)
- Pagination info with hint for next page

**Example:**
```json
{
  "ticket_number": 123,
  "offset": 1,
  "limit": 20
}
```

### get_ticket_answer
Get the full detail of a specific answer from a ticket, including attached files.

**Parameters:**
- `ticket_number` (integer, required): Ticket number
- `answer_id` (integer, required): ID of the answer to retrieve

**Returns:**
- Full answer content (untruncated), author, date (**fixed in v2.50.1:** Brasília time — previously used the host process's timezone), origin
- Complete list of attached files with name, type, size and download URL

**Example:**
```json
{
  "ticket_number": 123,
  "answer_id": 501
}
```

### delete_ticket_answer
Remove an answer (client communication) from a ticket in Tiflux.

**Parameters:**
- `ticket_number` (string, required): Ticket number from which the answer will be removed (e.g., "123", "456")
- `answer_id` (string, required): ID of the answer to remove (obtained via `list_ticket_answers` or `get_ticket_answer`)

**Example:**
```json
{
  "ticket_number": "123",
  "answer_id": "501"
}
```

**Returns:** Confirmation that the answer was removed.

### delete_ticket_answer_file
Remove a file attached to a specific ticket answer in Tiflux.

**Parameters:**
- `answer_id` (string, required): ID of the answer from which the file will be removed (obtained via `list_ticket_answers` or `get_ticket_answer`)
- `file_id` (string, required): ID of the file to remove (obtained via `get_ticket_answer`, field `files[].id`)

**Example:**
```json
{
  "answer_id": "501",
  "file_id": "1"
}
```

**Returns:** Confirmation that the file was removed.

### get_ticket_histories
List the event history (timeline) of a ticket, showing field changes, stage transitions, and other events. Paginated.

**Parameters:**
- `ticket_number` (integer, required): Ticket number to retrieve history for
- `history_of` (integer, required): History area to query — `0` = stage history, `1` = appointment history
- `offset` (number, optional): Page number (default: 1)
- `limit` (number, optional): Events per page (default: 20, max: 200)
- `type_id_attr` (integer, optional): Filter by attribute type
- `operation` (string, optional): Filter by operation type (`"created"`, `"updated"`, `"deleted"`) — only considered when `history_of=1`

**Returns:**
For each event:
- Action description, user, date/time (**fixed in v2.50.1:** Brasília time — previously used the host process's timezone, showing the wrong day for events between 21h–0h on a Lambda running in UTC), event type and operation
- Diff of changed fields with old → new values. **Fixed in v2.50.1:** date-time values with a timezone (e.g. `changed_first_stage_at: "2026-10-01T15:28:17Z"`, or any column saved as `...000Z`) are shown in Brasília time (`01/10/2026, 12:28:17`; `compact`: short UTC ISO) instead of raw ISO — detected by the value, not the field name. Appointment time-of-day values (`init_time`/`end_time`) are `HH:MM` strings already in the organization's timezone and are shown as-is. Plain dates and other values are unchanged.

**Example:**
```json
{
  "ticket_number": 123,
  "history_of": 0
}
```

### reopen_ticket
Reopen a closed or canceled ticket. Tickets that have been billed cannot be reopened.

**Parameters:**
- `ticket_number` (integer, required): Ticket number to reopen
- `disapproval_reason` (string, optional): Required when reopening a ticket pending review (disapproval-based reopening)

**Business Rules:**
- Tickets that have been **billed** cannot be reopened (API returns 422)
- `disapproval_reason` is mandatory when reopening a ticket that is pending review/approval

**Example:**
```json
{
  "ticket_number": 123,
  "disapproval_reason": "The solution did not resolve the issue"
}
```

### get_internal_communication
Get a specific internal communication with full content.

**Parameters:**
- `ticket_number` (string, required): Ticket number containing the communication
- `communication_id` (string, required): ID of the internal communication to retrieve

### update_internal_communication
Update the text of an existing internal communication in a ticket. Only the author of the communication can edit it.

**Parameters:**
- `ticket_number` (string, required): Ticket number where the communication exists (e.g., "123", "456")
- `communication_id` (string, required): ID of the internal communication to update (obtained via `list_internal_communications` or `get_internal_communication`)
- `text` (string, required): New content of the internal communication. Accepts Markdown (bold, lists, headings, code) — the MCP automatically converts it to HTML before sending to the API.

**Example:**
```json
{
  "ticket_number": "123",
  "communication_id": "101",
  "text": "Updated communication content with **important** details."
}
```

**Returns:** Confirmation with updated communication content.

**Note:** The Tiflux API only allows the author of the communication to edit it. A 403 error will be returned if the authenticated user did not create the communication.

### delete_internal_communication
Remove an internal communication from a ticket in Tiflux.

**Parameters:**
- `ticket_number` (string, required): Ticket number from which the communication will be removed (e.g., "123", "456")
- `communication_id` (string, required): ID of the internal communication to remove (obtained via `list_internal_communications`)

**Example:**
```json
{
  "ticket_number": "123",
  "communication_id": "101"
}
```

**Returns:** Confirmation that the internal communication was removed.

## Appointments (Time Tracking)

### create_appointment
Create a new appointment (work-hour record) on a specific ticket. Supports both non-valued (simple) and valued appointments (attended externally/remotely/internally, with contract or loose service billing, optional travel shift, guarantee flag, and manual value).

**Parameters (required):**
- `ticket_number` (string, required): Ticket number where the appointment will be created
- `date` (string, required): Appointment date in `YYYY-MM-DD` format. Future dates are not allowed.
- `init_time` (string, required): Start time in `HH:MM` format (e.g. `"09:00"`, `"14:30"`)
- `end_time` (string, required): End time in `HH:MM` format. Must be greater than or equal to `init_time`.
- `description` (string, required): Description of the work performed

**Parameters (valorization — required for desks with valorization enabled):**
- `attendance` (integer, optional): Attendance type: `1` = External (presencial), `2` = Remote, `3` = Internal. Required on desks with valorization.
- `attendance_kind` (integer, optional): Service type: `1` = Loose (avulso), `2` = Contract. Required on desks with valorization.
- `contract_rider_id` (integer, optional): Contract add-on ID **valid (in-period) on the appointment's `date`**. Required when `attendance_kind=2`. Use `contract_name` to resolve by name. **Caution:** `get_ticket_service_types` may list riders outside their validity period even when the `date=` filter is used — always verify `start_date`/`cancel_date` of the chosen rider before using this field.
- `loose_service_id` (integer, optional): Loose service ID. Required when `attendance_kind=1`. Use `loose_service_name` to resolve by name.
- `shift_id` (integer, optional): Travel/displacement ID (visit cost). Only with `attendance=1`. Exclusive with `shift_owner_ticket_number`. Use `shift_name` to resolve by name.
- `shift_owner_ticket_number` (integer, optional): Ticket number of another open ticket from the same client that already has the travel cost charged (carona). Only with `attendance=1`. Exclusive with `shift_id`.
- `guarantee` (boolean, optional): Guarantee appointment — value forced to zero, does not bill. Cannot be used together with `value`.
- `value` (number, optional): Manual value (0–9999999.99). Only with `attendance_kind=1`. If omitted, calculated automatically. Cannot be used with `attendance_kind=2` or `guarantee=true`.
- `external_user_name` (string, optional): Name of the executor in an external tool (max 255 chars, no `<` or `>`). Valid on any desk type.

**Name resolution parameters (Smart Name Resolution):**
- `shift_name` (string, optional): Partial shift/displacement name — resolves to `shift_id`. Preference: `shift_id` wins if both given. The term must match the start of a word in the name (a loose substring in the middle no longer resolves); a tie between candidates only resolves when exactly one is an exact name match, otherwise it's an ambiguity error. Candidates are filtered by the rider's contract (`contract_rider_id`/`contract_name`) when known — shifts from other contracts are discarded, mirroring the API's own validation. This matters because the same client can have multiple contract renewals sharing the exact same shift name (e.g. three "Joinville" shifts under three different contract IDs) — only the contract filter disambiguates them.
- `loose_service_name` (string, optional): Partial loose service name — resolves to `loose_service_id`. Preference: `loose_service_id` wins if both given. Same matching rule as `shift_name` (start-of-word match, exact-name tiebreak).
- `contract_name` (string, optional): Partial contract name — resolves to `contract_rider_id`. Preference: `contract_rider_id` wins if both given. Same matching rule as `shift_name` (start-of-word match, exact-name tiebreak) — commonly ambiguous when a client has renewed the same contract (identical name, different `contract.id`); use `contract_rider_id` directly in that case.

**Cross-field rules (validated locally before API call):**
- `attendance_kind=1` (Loose) requires `loose_service_id`; rejects `contract_rider_id`
- `attendance_kind=2` (Contract) requires `contract_rider_id`; rejects `loose_service_id` and `value`
- `shift_id` and `shift_owner_ticket_number` are mutually exclusive (never both)
- `shift_id` or `shift_owner_ticket_number` requires `attendance=1` (External)
- `value` requires an explicit `attendance_kind=1` (rejected when `attendance_kind` is omitted)
- `guarantee=true` rejects `value`
- `external_user_name` max 255 chars, no `<` or `>`

> **Contract rider out of validity period (item 5):** If the chosen `contract_rider_id` is cancelled or outside its validity period on the appointment's `date`, the API returns a `422`. The tool detects this and returns a descriptive error, reminding you to check `start_date`/`cancel_date` of the rider — and that `get_ticket_service_types` may include expired riders even with the `date=` filter active.

> **Desks with valorization enabled:** When the ticket's desk requires valorization, calling `create_appointment` without `attendance` and `attendance_kind` results in a `422` from the API. The tool catches this and returns a guided error message:
>
> ```
> ❌ Esta mesa exige informações de valorização
>
> A mesa do ticket #X está configurada com valorização de apontamentos, então
> `attendance` e `attendance_kind` são obrigatórios.
>
> • `attendance`: 1 = Externo (presencial), 2 = Remoto, 3 = Interno
> • `attendance_kind`: 1 = Avulso (exige `loose_service_id`), 2 = Contrato (exige `contract_rider_id`)
> ```
>
> Note: `attendance` and `attendance_kind` remain optional in the schema because desks _without_ valorization reject them at the API level — the tool cannot know the desk's configuration before the API call.

**Returns:** Confirmation of the created appointment (ID, date, time range, description, attendant). When resolved by ID or by name, the confirmation also echoes back the contract/loose service/shift that was actually used — e.g. `**Contrato:** Contrato de Licença de Uso (#88558, aditivo 266123)` when resolved by name, or `**Contrato:** aditivo 266123` when passed directly by ID. A field that wasn't used for the appointment shows no line at all.

**Example (valued appointment — loose service):**
```json
{
  "ticket_number": "258",
  "date": "2026-08-17",
  "init_time": "09:00",
  "end_time": "11:00",
  "description": "On-site support — network configuration",
  "attendance": 1,
  "attendance_kind": 1,
  "loose_service_name": "Suporte TI Basico",
  "shift_name": "Deslocamento Joinville"
}
```

### list_appointments
List appointments (work-hour records) of a specific ticket with optional filters. When available, each appointment card includes valorization details (attendance type, contract or loose service, travel shift, value) and geolocation entries.

**Parameters:**
- `ticket_number` (string, required): Ticket number to list appointments from
- `user_id` (number, optional): Filter by the ID of the user who made the appointment
- `start_date` (string, optional): Return appointments from this date (`YYYY-MM-DD`)
- `end_date` (string, optional): Return appointments up to this date (`YYYY-MM-DD`)
- `offset` (number, optional): Page number (default: 1)
- `limit` (number, optional): Appointments per page (default: 20, max: 200)

**Returns:**
Each appointment card shows date, time range, attendant, client (when available), and the full description (never truncated). When `external_user_name` is present it is shown as a separate line outside the valorization block (valid on any desk type). When the desk has valorization enabled, the card also includes:
- Attendance type: External (Externo), Remote (Remoto), or Internal (Interno)
- Service type: Loose (Avulso) with loose service name, or Contract with contract name
- Travel shift name and value (`shift`) when applicable — or "Deslocamento de: #N — Title" when `shift_owner_ticket` is set (carona)
- Guarantee and manual-value flags (shown only when `true`)
- Monetary value formatted as `R$ X,XX`

When `valorization` is `null` (desks configured without valorization), none of the above fields are shown.

In `compact` (explicit or automatic) the valorization is kept as one line: `valorizacao: atend=<attendance> · tipo=<kind>[:<name>] · valor=<decimal>[ · desloc=<name> <decimal>][ · flags=garantia;manual;deslocamento#N]`.

Geolocation lines (`📍 Localização: lat, lon`) are rendered when the API returns `locations` for the appointment.

**Example:**
```json
{
  "ticket_number": "123",
  "start_date": "2025-01-01",
  "end_date": "2025-01-31",
  "limit": 50
}
```

### list_appointments_global
List all appointments across all tickets for a date range with optional filters by technician, desk, client and contract. Requires permission to access the global appointments endpoint. Use `list_appointments_report` for an aggregated summary by technician (N2 support report).

> **Permission note:** Requires access to the `GET /appointments` endpoint. Users without the "Visualizar relatórios dos técnicos" (view_users_manage) permission may have their `user_ids` filter silently ignored by the API, receiving only their own appointments. When `user_ids` or `user_names` is provided and the API returns data, the tool emits an advisory note about this behavior. Non-admin users may receive `403` if the route itself is blocked at the permission level.

**Parameters:**
- `start_date` (string, required): Start date of the period (`YYYY-MM-DD`)
- `end_date` (string, required): End date of the period (`YYYY-MM-DD`)
- `user_ids` (string, optional): Comma-separated technician IDs (max 15). Use `user_names` for name-based resolution.
- `user_names` (string, optional): Comma-separated technician names for automatic resolution (alternative to `user_ids`). Ambiguity returns a disambiguation list.
- `desk_ids` (string, optional): Comma-separated desk IDs (max 15). Use `desk_names` for name-based resolution.
- `desk_names` (string, optional): Comma-separated desk names for automatic resolution (alternative to `desk_ids`).
- `client_ids` (string, optional): Comma-separated client IDs (max 15). Use `client_names` for name-based resolution. **Note (A2):** when this filter is active, appointments without a contract are excluded from results. **Note (A1 — Shared contracts):** in Shared contract mode, the `contract.id` returned may differ from the id you filtered on — the API expands the group to its member, which is expected behavior; do NOT filter client-side by `contract.id` equality.
- `client_names` (string, optional): Comma-separated client names for automatic resolution (alternative to `client_ids`). Max 15 resolved clients — more than that is rejected with an explicit error (never silently truncated, same rule as `client_ids`). `client_ids` takes precedence when both are provided.
- `contract_ids` (string, optional): Comma-separated contract IDs (max 15). Also accepts a contract **group** ID (see `get_contract_group`) — the API only treats the ID as a group when **no contract** shares that same number; on collision, the API filters by the contract, so use the group's **member contract IDs** (from `get_contract_group`) instead of the group ID itself. **Note (A2):** appointments without a contract are excluded when this filter is active. **Note (A1):** in Shared contracts, `contract.id` on returned items may differ from the filtered id (API expands group→member) — this is NOT an error; do NOT re-filter results client-side by `contract.id`.
- `include_valorization` (boolean, optional): Include valorization data (attendance type, value). Default: `false`.
- `offset` (number, optional): Page number (default: 1)
- `limit` (number, optional): Results per page (default: 20, max: 200)

**Returns:**
Paginated list of appointments. Each item shows: appointment ID, date, time range, technician name, client, contract name (or "sem contrato" when none), desk, ticket number and title, full description (never truncated, in both `rich` and `compact` — the API has no single-appointment endpoint, so a cut description could not be recovered). When present, `external_user_name` is shown as a separate line. Valorization summary appears when `include_valorization=true`: attendance type, monetary value, `🛡️ Garantia` (when `guarantee=true`), `✋ Valor manual` (when `manual_value=true`), and `shift_owner_ticket` when set. The `✋ Valor manual` flag indicates the value was entered manually by the user (bypassing the contract rate), as opposed to being calculated from the contract tariff — critical signal for billing analysis. When `contract_ids` is active, a footer warns that appointments without a contract are not shown.

**Example:**
```json
{
  "start_date": "2026-07-01",
  "end_date": "2026-07-31",
  "client_names": "Acme Corp",
  "contract_ids": "55082",
  "limit": 50
}
```

### list_appointments_report
Generate an aggregated N2 support report: count and total hours per technician for a date range, with optional desk sub-breakdown and grand totals. Supports filtering by client and contract. Ideal for recurring N2 support metrics (how many times and how many hours each N2 technician assisted others in a period).

> **Permission note:** Same as `list_appointments_global`. Non-admin users without route-level permission receive a `403` error.

**Parameters:**
- `start_date` (string, required): Start date of the period (`YYYY-MM-DD`)
- `end_date` (string, required): End date of the period (`YYYY-MM-DD`)
- `user_ids` (string, optional): Comma-separated N2 technician IDs (max 15). Use `user_names` for name-based resolution.
- `user_names` (string, optional): Comma-separated N2 technician names for automatic resolution.
- `desk_ids` (string, optional): Comma-separated desk IDs (max 15) to enable desk sub-breakdown in the report. Use `desk_names` for name-based resolution.
- `desk_names` (string, optional): Comma-separated desk names for automatic resolution (enables desk sub-breakdown).
- `client_ids` (string, optional): Comma-separated client IDs (max 15). Use `client_names` for name-based resolution. See caveats A1/A2 in `list_appointments_global`.
- `client_names` (string, optional): Comma-separated client names for automatic resolution (alternative to `client_ids`). Max 15 resolved clients — more than that is rejected with an explicit error (never silently truncated).
- `contract_ids` (string, optional): Comma-separated contract IDs (max 15). Also accepts a contract group ID — see the note in `list_appointments_global` about the ID collision caveat (group vs. contract) and `get_contract_group`. Appointments without a contract are excluded when active. See caveat A1 in `list_appointments_global`.
- `include_valorization` (boolean, optional): Include total value per technician (and per desk when breakdown enabled). Default: `false`.

**Returns:**
Markdown report with:
- Global totals: total appointments count, total hours, total value with manual sub-total in parentheses when applicable (e.g. `**Valor total:** R$ 1.200,00 (R$ 400,00 em valor manual)`)
- Per-technician section (sorted by appointment count desc): appointment count, total hours, total value — with `(R$ X manual)` suffix when any manual-value appointment exists for that technician
- Desk sub-breakdown per technician (shown only when `desk_ids`/`desk_names` provided): count + hours + value per desk, with manual suffix when applicable
- Warning footer when `contract_ids` is active: appointments without a contract are excluded from the aggregation (same notice as `list_appointments_global` — relevant for billing audits, since totals may be under-reported)
- Footer with real-time data notice

The `manual_value` breakdown (when `include_valorization=true`) makes it immediately clear how much of the billed total came from manually-entered values vs. contract-calculated rates — important for billing audits.

The report paginates through all available data automatically (no `offset`/`limit` needed — all pages are fetched internally before aggregating).

**Example:**
```json
{
  "start_date": "2026-07-01",
  "end_date": "2026-07-31",
  "user_names": "Fernando N2, Peterson N2",
  "desk_names": "Suporte - Dúvidas"
}
```

### list_pre_appointments
List open (in-progress) time records for a specific ticket. A pre-appointment represents a running stopwatch: `init_time` is filled in and `end_time` is `null` (meaning the timer is still running). Once stopped, it becomes a consolidated appointment (see `list_appointments` for closed records).

> **Permission note:** Requires the "Criar e editar apontamentos" (create and edit appointments) permission and a Tickets license. If your API key returns a `403`, check these permissions or use an admin key.

**Parameters:**
- `ticket_number` (string, required): Ticket number to list pre-appointments from
- `offset` (number, optional): Page number (default: 1)
- `limit` (number, optional): Pre-appointments per page (default: 20, max: 200)

**Returns:**
Markdown table with 5 columns: ID, Data (date), Inicio (start time), Fim (end time), Usuario (user name). When `end_time` is `null` (or an empty string), the cell shows **"em andamento"** — this is the normal state of an open pre-appointment, not missing data. Null fields in `date`, `init_time`, or `user.name` are shown as `—`. Cell values are escaped (`|` and line breaks) so API data cannot break the table layout. Footer includes pagination info with total count from the `X-Total-Items` response header.

When no pre-appointments exist for the ticket, a friendly message is returned (not an error).

**Example:**
```json
{
  "ticket_number": "258"
}
```

**Typical output:**
```
**Pre-apontamentos do Ticket #258 (1 de 1)**

| ID | Data | Inicio | Fim | Usuario |
|---|---|---|---|---|
| 3 | 2026-07-07 | 14:35 | em andamento | Velda Windler |
```

## Chats

### get_chat
Exibir detalhes de um chat específico pelo id. Retorna card com status, cliente, contato/solicitante, departamento, origem, avaliação e datas.

**Parameters:**
- `id` (number, required): Numeric ID of the chat (also accepts numeric string — the handler calls parseInt)

**Returns:**
Card with all relevant chat fields including:
- Status (archived, canceled, online)
- `Pendente p/ atendente:` — `Sim` when there is a pending client message for the attendant, `Não (aguardando cliente)` when the attendant already answered/read **or** the client never answered (the API's `waiting_answer` flag is `null` in that case — semantically equivalent to `false`)
- Client name
- Contact/requestor: `Solicitante: <name> (ID <id>)` when the chat has a registered requestor, or `Contato: <name> (sem solicitante vinculado)` when it's a free-text contact with no requestor record. Shows the requestor's email when available.
- Department and responsible attendant
- Origin channel (the API returns `origin` as an object `{ integration_id, _type, fields }`; the card displays `_type`, e.g. `whatsapp`, `site_widget`, `chat`) and room
- Linked ticket (number + title, e.g. `#127 — Erro no login`); title is normalized to a single line and truncated at 150 chars; shows `Sem ticket vinculado` when no ticket is linked
- Assessment rating (1–5) if available
- `Última msg do cliente em:` — timestamp of the last client message (this field is a date, not message text)
- Timestamps (created, updated, assumed) in Brazil (São Paulo) time in `rich` verbosity, short UTC in `compact`

**Example:**
```json
{
  "id": 42
}
```

### list_inbox_chats
Listar chats na caixa de entrada (chats não assumidos) em modo atendente, com filtros opcionais de departamento, cliente, origem, data e paginação. Chats em triagem (bot/departamento, sem responsável) não aparecem aqui — use `list_in_attendance_chats` com `status=triage`.

**Parameters:**
- `offset` (number, optional): Page number (default: 1, minimum: 1)
- `limit` (number, optional): Chats per page (default: 20, max: 200)
- `department_id` (number, optional): Filter by department ID. To discover the ID from a name, use `list_departments` (e.g. `list_departments name:"financeiro"`)
- `client_id` (number, optional): Filter by client ID
- `requestor_id` (number, optional): Filter by requestor ID — must be greater than 0
- `number` (number, optional): Filter by WhatsApp contact number — **requires WhatsApp license**
- `origins` (string, optional): Comma-separated origin channels: `chat`, `site_widget`, `campaign`, `whatsapp`, `whatsapp_web`, `gupshup`, `whatsapp_cloud`
- `started_by` (string, optional, enum `Client`/`Attendant`/`Campaign`/`API`): Chat initiator type — the API is case-sensitive and returns 422 for other casing
- `created_at_start` (string, optional): Filter chats created on or after this datetime. Pure date (`YYYY-MM-DD`) = start of day in Brazil time (normalized to `T00:00:00-03:00`); with time, use an explicit offset (e.g. `-03:00`) or `Z`
- `created_at_end` (string, optional): Filter chats created on or before this datetime. Pure date (`YYYY-MM-DD`) = end of day in Brazil time (normalized to `T23:59:59-03:00`). Must be >= `created_at_start`

**Returns:**
Paginated list of chats. Each item includes origin (displayed as `_type`, e.g. `chat`, `whatsapp`), online status, `Pendente p/ atendente:` (`Sim`/`Não (aguardando cliente)` — `Não` also covers chats the client never answered), client, contact/requestor (`Solicitante: <name> (ID <id>)` or `Contato: <name> (sem solicitante vinculado)`), department, `Última msg do cliente em:` (timestamp, Brazil time in `rich`), creation date, and — when the chat has a linked ticket — `Ticket: #<number> — <title>` (title normalized to a single line and truncated at 150 chars; the whole line is omitted when no ticket is linked). `Assumido em:` is omitted for inbox chats because they are not yet assumed. Shows `Total:` when the API returns the total item count; long pages are cut at the end of an item (~40k chars) with an exact continuation `offset`/`limit`.

**Example:**
```json
{
  "origins": "whatsapp",
  "created_at_start": "2026-06-01",
  "created_at_end": "2026-06-30",
  "limit": 10
}
```

### list_my_chats
Listar chats assumidos pelo usuário autenticado (dono da API key) com filtros opcionais e paginação.

**Parameters:**
- `offset` (number, optional): Page number (default: 1, minimum: 1)
- `limit` (number, optional): Chats per page (default: 20, max: 200)
- `department_id` (number, optional): Filter by department ID. To discover the ID from a name, use `list_departments` (e.g. `list_departments name:"financeiro"`)
- `client_id` (number, optional): Filter by client ID
- `requestor_id` (number, optional): Filter by requestor ID — must be greater than 0
- `number` (number, optional): Filter by WhatsApp contact number — **requires WhatsApp license**
- `origins` (string, optional): Comma-separated origin channels (see list_inbox_chats)
- `started_by` (string, optional, enum `Client`/`Attendant`/`Campaign`/`API`): Chat initiator type (see list_inbox_chats)
- `created_at_start` (string, optional): Filter chats created on or after this datetime. Pure date (`YYYY-MM-DD`) = start of day in Brazil time (normalized to `T00:00:00-03:00`); with time, use an explicit offset or `Z`
- `created_at_end` (string, optional): Filter chats created on or before this datetime. Pure date (`YYYY-MM-DD`) = end of day in Brazil time (normalized to `T23:59:59-03:00`). Must be >= `created_at_start`

**Returns:**
Paginated list of chats assumed by the authenticated user. Each item includes origin (displayed as `_type`, e.g. `whatsapp`, `chat`), online status, `Pendente p/ atendente:` (see list_inbox_chats), client, contact/requestor (`Solicitante:`/`Contato:`, see list_inbox_chats), department, `Última msg do cliente em:` timestamp, creation date, `Assumido em:` timestamp (Brazil time in `rich`), and — when the chat has a linked ticket — `Ticket: #<number> — <title>` (title normalized to a single line and truncated at 150 chars; the whole line is omitted when no ticket is linked). Shows `Total:` when the API returns the total item count.

**Example:**
```json
{
  "department_id": 3,
  "created_at_start": "2026-06-01",
  "limit": 20
}
```

### list_in_attendance_chats
Listar todos os chats **abertos** da organização: inclui os não assumidos da caixa de entrada e os em triagem (bot/departamento, sem responsável), além dos já assumidos. Use `user_id` para ver só os chats de um atendente específico.

**Parameters:**
- `offset` (number, optional): Page number (default: 1, minimum: 1)
- `limit` (number, optional): Chats per page (default: 20, max: 200)
- `department_id` (number, optional): Filter by department ID. To discover the ID from a name, use `list_departments` (e.g. `list_departments name:"financeiro"`)
- `client_id` (number, optional): Filter by client ID
- `requestor_id` (number, optional): Filter by requestor ID — must be greater than 0
- `number` (number, optional): Filter by WhatsApp contact number — **requires WhatsApp license**
- `origins` (string, optional): Comma-separated origin channels (see list_inbox_chats)
- `started_by` (string, optional, enum `Client`/`Attendant`/`Campaign`/`API`): Chat initiator type (see list_inbox_chats)
- `user_id` (number, optional): Filter by responsible attendant ID
- `status` (string, optional, enum `waiting_client`/`waiting_attendance`/`triage`): Attendance status. `waiting_client` = there's a pending client message for the attendant. `waiting_attendance` = the attendant already answered/read and is waiting on the client — does **NOT** include chats the client never answered (omit `status` to see those too). `triage` = no responsible yet, in bot/department queue.
- `created_at_start` (string, optional): Filter chats created on or after this datetime. Pure date (`YYYY-MM-DD`) = start of day in Brazil time (normalized to `T00:00:00-03:00`); with time, use an explicit offset or `Z`
- `created_at_end` (string, optional): Filter chats created on or before this datetime. Pure date (`YYYY-MM-DD`) = end of day in Brazil time (normalized to `T23:59:59-03:00`). Must be >= `created_at_start`

**Returns:**
Paginated list of all open chats in the organization. Each item includes origin (displayed as `_type`, e.g. `whatsapp`, `site_widget`), `Atendente: <name> (ID <id>)` or `Atendente: — (não assumido)` when unassigned, `Pendente p/ atendente:` (see list_inbox_chats), client, contact/requestor (`Solicitante:`/`Contato:`, see list_inbox_chats), department, `Última msg do cliente em:` timestamp, creation date, `Assumido em:` timestamp (Brazil time in `rich`), and — when the chat has a linked ticket — `Ticket: #<number> — <title>` (title normalized to a single line and truncated at 150 chars; the whole line is omitted when no ticket is linked). Shows `Total:` when the API returns the total item count.

**Example:**
```json
{
  "user_id": 7,
  "status": "triage",
  "created_at_start": "2026-06-15"
}
```

### list_archived_chats
Listar chats arquivados (finalizados ou cancelados) com filtros opcionais de data de criação e finalização. Exibe avaliação do atendimento e status de cancelamento.

**Parameters:**
- `offset` (number, optional): Page number (default: 1, minimum: 1)
- `limit` (number, optional): Chats per page (default: 20, max: 200)
- `department_id` (number, optional): Filter by department ID. To discover the ID from a name, use `list_departments` (e.g. `list_departments name:"financeiro"`)
- `client_id` (number, optional): Filter by client ID
- `requestor_id` (number, optional): Filter by requestor ID — must be greater than 0
- `number` (number, optional): Filter by WhatsApp contact number — **requires WhatsApp license**
- `origins` (string, optional): Comma-separated origin channels (see list_inbox_chats)
- `started_by` (string, optional, enum `Client`/`Attendant`/`Campaign`/`API`): Chat initiator type (see list_inbox_chats)
- `canceled` (boolean, optional): `true` = only canceled chats, `false` = only normally finished, omitted = all archived chats
- `created_at_start` (string, optional): Filter chats created on or after this datetime. Pure date (`YYYY-MM-DD`) = start of day in Brazil time (normalized to `T00:00:00-03:00`); with time, use an explicit offset or `Z`
- `created_at_end` (string, optional): Filter chats created on or before this datetime. Pure date (`YYYY-MM-DD`) = end of day in Brazil time (normalized to `T23:59:59-03:00`). Must be >= `created_at_start`
- `finished_at_start` (string, optional): Filter chats finished on or after this datetime. Same pure-date normalization as `created_at_start`. Only applicable to archived chats
- `finished_at_end` (string, optional): Filter chats finished on or before this datetime. Same pure-date normalization as `created_at_end`. Must be >= `finished_at_start`. Only applicable to archived chats

**Returns:**
Paginated list of archived chats. Each item includes origin (displayed as `_type`, e.g. `whatsapp`, `chat`), canceled/assessment status, client, contact/requestor (`Solicitante:`/`Contato:`, see list_inbox_chats), department, `Última msg do cliente em:` timestamp, creation date, `Assumido em:` timestamp (Brazil time in `rich`), and — when the chat has a linked ticket — `Ticket: #<number> — <title>` (title normalized to a single line and truncated at 150 chars; the whole line is omitted when no ticket is linked). Shows `Total:` when the API returns the total item count.

**Example:**
```json
{
  "canceled": false,
  "created_at_start": "2026-06-01",
  "created_at_end": "2026-06-30",
  "finished_at_start": "2026-06-15",
  "limit": 50
}
```

### list_chat_messages
Listar as mensagens de um chat em ordem cronológica (transcrição da conversa). Retorna autor, horário, texto ou referência de anexo, reply citado e status de entrega quando disponível. Suporta paginação offset/limit.

**Parameters:**
- `id` (number, required): Numeric ID of the chat (also accepts numeric string — the handler calls parseInt)
- `offset` (number, optional): Page number (default: 1, minimum: 1)
- `limit` (number, optional): Messages per page (default: 20, max: 200)

**Returns:**
Conversation transcript in Markdown. Each message includes:
- Author + role in Portuguese (client → Cliente, attendant → Atendente, system → Sistema, ai → IA), including the name when available
- Timestamp (`created_at`)
- **Full message text, never truncated** (the only cap is the overall response budget, which cuts at the end of a message with an exact continuation `offset`/`limit`) **or** a media reference when `media ≠ null`:
  - Image → `[imagem: <url>]`
  - Voice note (`media.is_voice`) → `[áudio de voz: <url>]`
  - Other audio → `[áudio: <url>]`
  - Any other file → `[documento <content_type>: <url>]`
  - Caption (when present) is shown on the next line, in full
  - No `url` in the payload → falls back to `[anexo: <content_type>]`
- `↩ resposta a <author_name>: "<text>"` when `quoted_message` is present (author name included when available; full quoted text, not truncated)
- Delivery status when relevant (lido / entregue / ⚠ falhou)
- Shows `Total:` when the API returns the total message count

Errors: 404 when the chat does not exist, 403 when the account lacks permission or WhatsApp license.

**Example:**
```json
{
  "id": 42,
  "offset": 1,
  "limit": 50
}
```

### update_chat
Atualizar um chat existente: transferir o atendente (`user_id`), transferir o departamento (`department_id`) e/ou vincular o chat a um ticket (`ticket_number`). Só é possível atualizar um chat que **não esteja cancelado ou encerrado**.

**Parameters:**
- `id` (number, required): Chat ID (accepts numeric string — handler runs `parseInt`)
- `user_id` (number, optional): Attendant the chat will be transferred to
- `user_name` (string, optional): Attendant name for automatic lookup (alternative to `user_id`; `user_id` takes precedence). **Caveat (BL-007):** requires an admin API key — `GET /users` returns 403 for non-admin accounts; in that case use `user_id` directly.
- `department_id` (number, optional): Department the chat will be transferred to. **No `department_name`** — to find the ID from a name, use `list_departments` first (e.g. `list_departments name:"financeiro"`).
- `ticket_number` (number, optional): Ticket number to link to the chat

**Note:** At least one of `user_id` / `user_name` / `department_id` / `ticket_number` is required. If none is provided, the tool returns a friendly warning without calling the API.

**Example:**
```json
{
  "id": 37,
  "user_id": 1,
  "ticket_number": 127
}
```

**Returns:** Markdown confirmation — `Atualização do chat #<id> enfileirada` — with the list of requested changes. **The API processes the update asynchronously (queued job)**; the response does not include an `Atualizado em:` timestamp (the `updated_at` field in this response is the processing clock, not a persisted completion time).

### send_message
Enviar uma mensagem por WhatsApp, **criando o chat no envio**. Use mensagem livre (`message`) **ou** modelo HSM / modelo de chat (`template_id`), nunca os dois juntos.

> **`message` é texto plano (NÃO Markdown/HTML).** O WhatsApp usa marcação própria (asterisco para negrito, underscore para itálico); HTML apareceria literal. Por isso, diferentemente de `description`/`answer` de ticket, o conteúdo **não é convertido** para HTML.

**Parameters:**
- `number` (number, required): Destination phone number. Validated as Brazilian by default; for another country also pass `country_code`.
- `integration_id` (number, required): WhatsApp integration ID. Accepted types: `gupshup`, `whatsapp_cloud`.
- `message` (string, optional): Free-text message (plain text). Use `message` OR `template_id`.
- `template_id` (number, optional): HSM / chat template ID. Use `parameters`/`header_parameters` for variables.
- `country_code` (string, optional): ISO 3166-1 alpha-2 country code (e.g. `US`). Default `BR`.
- `name` (string, optional): Requester name.
- `department_id` (number, optional): Link the created chat to a department.
- `ticket_number` (number, optional): Link the created chat to a ticket. **Requires `client_id`** — the API only links the ticket when the chat has an associated client; without it, the API silently ignores the link. Other conditions: the ticket must be open, belong to the same client, and be on a desk where the authenticated user can open tickets.
- `client_id` (number, optional): Link the created chat to a client.
- `parameters` (string[], optional): Values for the HSM body variables (`template_id`).
- `header_parameters` (string[], optional): Values for the HSM header variables — `whatsapp_cloud` only.
- `archive` (boolean, optional): Default `false`. `true` = create and send straight to the finished/archived box.

**Note:** Besides `number` + `integration_id`, at least one of `message` / `template_id` is required (validated locally). `ticket_number` without `client_id` is also rejected locally, before calling the API. Success status from the API: **201**, returned as a **queued job** (`status: 'enqueued'`, `job_id`) — not a guarantee of delivery. Confirm actual delivery with `list_chat_messages`.

**Example (free message):**
```json
{
  "number": 5568976728276,
  "integration_id": 1,
  "message": "Olá, tudo bem?"
}
```

**Example (HSM template with parameters):**
```json
{
  "number": 5519993017428,
  "integration_id": 1,
  "template_id": 1,
  "parameters": ["Valor 1", "Valor 2"]
}
```

**Returns:** Markdown confirmation — `**📨 Envio enfileirado**` (never "enviada com sucesso") — with the send details (type, number, integration, links) and the `job_id` when returned by the API.

### archive_chat
Finalizar (encerrar) um chat. A API responde **202 (Accepted)** — o encerramento é processado de forma assíncrona (job enfileirado); o tool trata 202 como sucesso.

**Parameters:**
- `id` (number, required): Chat ID to finish (accepts numeric string — handler runs `parseInt`)
- `services_catalogs_item_id` (number, optional): Service catalog item ID. **Conditional:** required **only** when the organization is configured to "Usar catálogo de serviços no chat" — otherwise the API returns 422. **No `catalog_item_name`**: catalog item search requires a `desk_id`, which the chat does not provide; pass the ID directly when needed.

**Note:** If the organization requires a linked ticket to finish a chat, link one first with `update_chat` (`ticket_number`) — otherwise the closing is silently discarded during processing.

**Example:**
```json
{
  "id": 37,
  "services_catalogs_item_id": 1
}
```

**Returns:** Markdown confirmation that the chat was finished (202 Accepted, processed asynchronously).

### get_chats_feedback_report
Relatório de avaliações de atendimento (CSAT) de chats com comparação automática de período. Retorna métricas de satisfação (média, avaliados, finalizados, taxa de resposta) com deltas entre dois períodos, e opcionalmente a lista de chats avaliados.

**When to use vs other tools:**
- **CSAT / satisfaction ratings for CHATS** → `get_chats_feedback_report` (this tool)
- **CSAT / satisfaction ratings for TICKETS** → `get_tickets_feedback_report`
- **Ticket count / trend comparison** → `get_tickets_comparison`

**Default comparison period:** if `compare_start_date`/`compare_end_date` are not provided, the comparison period is the immediately preceding period of the same duration. Provide only `start_date` and `end_date` and the comparison window is calculated automatically.

**Single-day report:** `start_date == end_date` is accepted (e.g. "how was chat satisfaction yesterday?"). The default comparison period in that case is the previous day.

**Dates are whole UTC days** — time and timezone are ignored by the API.

**No evaluations in a period:** when a period has zero evaluated chats, the average rating and response rate show as `—` (not `0.0`) — no evaluation happened, it's not a real zero.

**No filter by rating in the API** — to filter by rating, use `include_list=true` and filter the returned list by `rating` client-side (e.g., "show only chats with rating < 3").

**Note:** The `comments` field (evaluation comment) is **not available** in the chats report — only in the tickets report.

**Enrichment workflow:**
1. Run with `include_list=true` to get the list of evaluated chats (with `rating`, `rating_time`, client, responsible, linked ticket number).
2. For full chat details (requestor, department, service catalog, origin, timestamps) → use `get_chat` with the `id` returned.

**Note:** This report requires **administrator/reports permission** — non-admin API keys receive 403.

**Parameters:**
- `start_date` (string, required): Start of the main period (YYYY-MM-DD, e.g., "2026-07-01")
- `end_date` (string, required): End of the main period (YYYY-MM-DD, e.g., "2026-07-31")
- `compare_start_date` (string, optional): Start of comparison period (YYYY-MM-DD). Must be provided with `compare_end_date`. If omitted, the immediately preceding period of the same duration is used.
- `compare_end_date` (string, optional): End of comparison period (YYYY-MM-DD). Pair with `compare_start_date`.
- `include_list` (boolean, optional): If true, includes the paginated list of evaluated chats in the main period. Default: false.
- `offset` (integer, optional): Page number for the list (default: 1). Only relevant with `include_list=true`.
- `limit` (integer, optional): Items per page (default: 20, max: 200). Only relevant with `include_list=true`.
- `responsible_ids` (string, optional): Comma-separated responsible user IDs (max 15). Applied to both calls (main + comparison).
- `department_ids` (string, optional): Comma-separated department IDs (max 15). Applied to both calls.
- `technical_group_ids` (string, optional): Comma-separated technical group IDs (max 15). Applied to both calls.

**Example — compare this week vs last week:**
```json
{
  "start_date": "2026-07-07",
  "end_date": "2026-07-13"
}
```

**Example — get list of evaluated chats to filter by rating client-side:**
```json
{
  "start_date": "2026-07-01",
  "end_date": "2026-07-31",
  "include_list": true,
  "limit": 100
}
```

**Rich response example:**
```markdown
**📊 Relatório de avaliações de atendimento — Chats**

**Período principal:** 2026-07-01 a 2026-07-31
**Período de comparação:** 2026-05-31 a 2026-06-30

| Métrica | Período atual | Período anterior | Δ |
|---------|--------------|-----------------|---|
| Média de avaliação | **4.5** | 4.0 | +0.5 (+12.5%) |
| Chats avaliados | **20** | 15 | +5 (+33.3%) |
| Chats finalizados | **100** | 80 | +20 (+25%) |
| Clientes avaliadores | **15** | 12 | +3 (+25%) |
| Taxa de resposta (%) | **80.0** | 75.0 | +5 (+6.7%) |

*✅ Dados obtidos da API TiFlux em tempo real*
```

## Desk Tools

Explore and inspect desks (service queues) without leaving the chat. Use `list_desks` to discover available desks, `get_desk` to inspect full configuration, `list_desk_priorities` to discover priority IDs before creating tickets, and `list_desk_services_catalogs` to list service catalog containers linked to a desk.

### list_desks
Listar mesas (desks) disponiveis no tenant para descoberta e exploracao. Retorna tabela com id, nome, display name, status ativo e tipo de atendimento. Use antes de criar tickets ou para explorar quais mesas existem. Para localizar uma mesa por nome (parcial/fuzzy), use `get_desk` com `desk_name`.

**Parameters:**
- `active` (boolean, optional): Filter active (`true`) or inactive (`false`) desks. Default: `true` (active only)
- `limit` (number, optional): Results per page (default: 20, max: 200)
- `offset` (number, optional): Page number (default: 1)

**Returns:** Markdown table with `id`, `name`, `display_name`, active status and `appointment_type`.

**Example:**
```json
{
  "active": true,
  "limit": 50
}
```

### get_desk
Retornar configuracoes completas de uma mesa (30+ campos) agrupadas em secoes: Identificacao, Atendimento, SLA, Comportamento de tickets e Campos obrigatorios no formulario. Campos vazios ou falsy sao omitidos automaticamente.

Accepts `desk_id` (direct) **or** `desk_name` (fuzzy, uses the same Smart Name Resolution mechanism). If both are provided, `desk_id` takes precedence.

**Parameters:**
- `desk_id` (number, optional): Numeric desk ID. If provided, used directly without name resolution
- `desk_name` (string, optional): Partial, full or multi-word desk name (tokens in any order) — e.g. `"cansados"` or `"dev cansados"` resolve to `"Dev - Cansados"`; `"dev experimentos"` resolves to `"DEV - Experimentos"` (see Smart Name Resolution). Alternative to desk_id

**Note:** At least one of `desk_id` or `desk_name` is required.

**Returns:** Markdown with sections covering:
- **Identificacao**: name, display name, description, active status, internal desk flag, receiving new tickets
- **Atendimento**: appointment type, attendance type, permissions, cancelable tickets, feedback, e-mail settings, desk exchange
- **SLA**: SLA active flag, SLA goal, can stop SLA, SLA time tracking
- **Comportamento de tickets**: ticket review settings, reopening rules, time limits, billing behavior
- **Campos obrigatorios no formulario**: service catalog requirements and the desk's `required_fields` — fields required on ticket opening **and** closing. The API returns a flat object of booleans; only the `true` keys are listed, with Portuguese labels: `requestor_name` → Nome do solicitante, `requestor_email` → E-mail do solicitante, `requestor_telephone` → Telefone do solicitante, `requestor_ramal` → Ramal do solicitante, `equipment_id` → Grupo de recurso, `attachment_file` → Anexo (unknown/legacy keys, e.g. `services_catalog_id`, appear with the raw name). No `true` key → `Nenhum`; `null` → line omitted. **Fixed in v2.50.1:** previously shown as `[object Object]`.

**Example:**
```json
{
  "desk_id": 3
}
```

Or using fuzzy name resolution:
```json
{
  "desk_name": "cansados"
}
```

### create_desk
Create a service desk. **The API has no way to delete desks** — a created desk can only be deactivated (in the portal, or created with `active: false`).

**Permissions:** "Gerenciar mesas de serviços" (403 `40301`) + Tickets License (403 `40304`) — each case has its own message.

**Parameters:**
- `name` (string, required): Internal name
- `display_name` (string, required): Display name
- `description` (string, required): Description
- `appointment_type` (string, optional): `"Without Appointments"`, `"Appointments with Valorization"` or `"Appointments with no Valorization"`
- `attendance_type` (string, optional): `"All technical groups"` or `"Only selected technical groups"`
- `sla_goal` (integer, optional): SLA goal (%)
- Booleans (optional): `active`, `receiving_new_tickets`, `internal_desk`, `cancelable_tickets`, `desk_exchange`, `require_service_catalog_open_ticket`, `desk_with_sla`, `ticket_review`, `add_ticket_feedback`, `can_reopen_revised_tickets`, `user_without_access_create_ticket`
- `required_fields` (object, optional): booleans `requestor_name`, `requestor_email`, `requestor_telephone`, `requestor_ramal`, `equipment_id`, `attachment_file` (true = required on the ticket form)

Review, reopening, reminder and summary settings (`review_type`, `behavior_billed_tickets`, `behavior_not_billed_tickets`, `default_revised`, `time_limit_to_reopening`, `ticket_with_sla_time`, `can_stop_sla`, `reminder`, `summary`, `services_catalog_item`) are not exposed in this version — adjust them in the portal. Invalid enums, non-boolean flags and unknown `required_fields` keys are rejected before the API call; only the informed keys are sent. When omitted, the API applies its own defaults (observed on 2026-10-06: `Appointments with Valorization`, `Only selected technical groups`, SLA active).

**Example:**
```json
{
  "name": "suporte-n2",
  "display_name": "Suporte N2",
  "description": "Segundo nível de suporte",
  "appointment_type": "Without Appointments",
  "required_fields": { "requestor_email": true }
}
```

**Returns:** The created desk in the same format as `get_desk` (sections Identificacao, Atendimento, SLA, ...). 422 (`42202`) lists the message per field (e.g. `description: can't be blank`). Endpoint: `POST /desks`.

### list_desk_priorities
Listar prioridades configuradas em uma mesa do Tiflux. Use para descobrir os IDs de prioridade antes de criar ou atualizar tickets (ex: "alta prioridade" → `priority_id`). O filtro `priority_name` e feito client-side com fuzzy match apos buscar os registros da API.

Accepts `desk_id` (direct) **or** `desk_name` (fuzzy). If both are provided, `desk_id` takes precedence.

**Parameters:**

| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| `desk_id` | number | one of | — | Numeric desk ID. Used directly, no name lookup |
| `desk_name` | string | one of | — | Partial or exact desk name (fuzzy resolved). Alternative to `desk_id` |
| `priority_name` | string | no | — | Optional fuzzy filter on priority name (client-side). E.g. `"alta"` |
| `limit` | number | no | 20 | Results per page (max: 200) |
| `offset` | number | no | 1 | Page number |

**Returns:** Markdown table with `id`, `name`, `order`, `start_time`, `end_time`.

**Example — list all priorities by desk_id:**
```json
{
  "desk_id": 3
}
```

**Example — filter by priority name using desk_name:**
```json
{
  "desk_name": "suporte",
  "priority_name": "alta"
}
```

**Example response:**
```
| ID | Nome | Ordem | Inicio | Fim |
|---|---|---|---|---|
| 17 | High | 1 | 04:00 | 24:00 |
| 18 | Low | 2 | 10:00 | 48:00 |
```

### list_desk_services_catalogs
Listar catalogos de servicos vinculados a uma mesa do Tiflux. Catalogos sao os containers pai — diferentes dos itens de catalogo (use `search_catalog_item` para itens selecionaveis em tickets). O filtro `catalog_name` e feito client-side com fuzzy match.

> **Disambiguation:** this tool is **desk-scoped** (catalogs linked to a specific desk, operations view). To list, create, update or delete catalogs org-wide (configuration/management view), use `list_services_catalogs` and the other `*_services_catalog*` tools.

Accepts `desk_id` (direct) **or** `desk_name` (fuzzy). If both are provided, `desk_id` takes precedence.

**Parameters:**

| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| `desk_id` | number | one of | — | Numeric desk ID. Used directly, no name lookup |
| `desk_name` | string | one of | — | Partial or exact desk name (fuzzy resolved). Alternative to `desk_id` |
| `catalog_name` | string | no | — | Optional fuzzy filter on catalog name (client-side). E.g. `"infra"` |
| `limit` | number | no | 20 | Results per page (max: 200) |
| `offset` | number | no | 1 | Page number |

**Returns:** Markdown table with `id`, `name`.

**Example — list all catalogs:**
```json
{
  "desk_name": "suporte"
}
```

**Example — filter by catalog name:**
```json
{
  "desk_id": 3,
  "catalog_name": "infra"
}
```

**Example response:**
```
| ID | Nome |
|---|---|
| 6 | Catalogo 1 |
| 7 | Catalogo 2 |
```

## Department Tools

Discover department IDs by name — required for filtering chats by department. The two-step flow: `list_departments(name:"financeiro")` → get `id` → `list_my_chats(department_id:...)`.

### list_departments
Listar departamentos da organização com filtro opcional de busca parcial por nome. Use para descobrir o `department_id` a partir de um nome antes de filtrar chats. Retorna tabela `ID | Nome`.

**Permissions:** Admin API keys return all active departments. Technical (non-admin) keys return only departments linked to their attendant group.

**Parameters:**
- `name` (string, optional): Partial name search, case-insensitive (e.g. `"financeiro"`, `"suporte"`). Max 255 characters.
- `limit` (number, optional): Results per page (default: 20, max: 200)
- `offset` (number, optional): Page number (default: 1)

**Returns:** Markdown table with `ID` and `Nome` columns.

**Example:**
```json
{
  "name": "financeiro"
}
```

**Example response:**
```
| ID | Nome |
|---|---|
| 3 | Financeiro |
```

Use the `id` as `department_id` in `list_inbox_chats`, `list_my_chats`, `list_in_attendance_chats`, or `list_archived_chats`.

## Knowledge Base Tools

Search and manage the organization's knowledge base articles. Without the "Gerenciar base de conhecimento" permission, only public articles and those from the user's attendant group are returned.

**API limitations (v2):** There is no endpoint for uploading images or attachments; images must be embedded by URL (Markdown `![alt](url)`). To edit an existing article, use `update_knowledge`. To archive an article (soft-delete: content and versions are kept, but it can only be restored in the TiFlux app), use `delete_knowledge`. There is no versions endpoint — the API only exposes `created_at`/`updated_at`; sending `description` via `update_knowledge` creates a new version, but past versions cannot be listed, compared, or restored via MCP. Folders can be created via `create_knowledge_folder` and edited via `update_knowledge_folder`, but cannot be deleted via MCP (`DELETE /knowledge-folders/{id}` exists in the API but is intentionally out of scope: it is a **hard delete** — the folder is removed for good, articles that were only in it are archived, and it is unlinked from any AI Agents that used it as a knowledge source; only the organization's default folder is protected).

### list_knowledges
List knowledge base articles with optional search and folder filters. Returns a Markdown table with ID, title, visibility, folders, tags, and last updated date.

**Permissions:** Without "Gerenciar base de conhecimento" — only public articles and those from the user's attendant group. With the permission — all articles.

**Note:** The `description` field returned by the API is truncated at 300 characters (preview only — partial content).

**Parameters:**
- `search` (string, optional): Search by title, tags, or beginning of the description (case-insensitive).
- `knowledge_folder_ids` (array of numbers, optional): Filter by folder IDs. Example: `[1, 2]`.
- `limit` (number, optional): Results per page (default: 20, max: 200).
- `offset` (number, optional): Page number (default: 1).

**Returns:** Markdown table with columns `ID | Titulo | Privado | Pastas | Tags | Atualizado` (**fixed in v2.50.1:** `Atualizado` now shows the Brasília-time date — previously used the host process's timezone).

**Example:**
```json
{
  "search": "VPN",
  "knowledge_folder_ids": [1],
  "limit": 10
}
```

**Example response:**
```
| ID | Titulo | Privado | Pastas | Tags | Atualizado |
|---|---|---|---|---|---|
| 101 | Como configurar VPN | Sim | 1, 2 | VPN, acesso remoto | 01/06/2026 |
```

*A descricao e exibida truncada em ate 300 caracteres pela API (preview parcial).*

### get_knowledge
Fetch the full detail of a knowledge base article by ID. The `description` body is returned in **Markdown** (converted from HTML by the MCP — no raw HTML noise in context).

**Important:** To edit an existing article, use `update_knowledge`. To archive (soft-delete) an article, use `delete_knowledge`.

**Parameters:**
- `knowledge_id` (number, required): ID of the knowledge article (obtained from `list_knowledges`).

**Returns:** Article detail with title, visibility, tags, creation/update dates (**fixed in v2.50.1:** Brasília-time dates — previously used the host process's timezone), and the full body in Markdown.

**Note on links:** the HTML→Markdown conversion is not a general-purpose HTML sanitizer (final sanitization is the MCP client renderer's responsibility), but it does neutralize the one vector that survives conversion: links and images with an executable URI scheme (`javascript:`, `vbscript:`, and `data:` for links) lose the URL and keep only the visible text/alt. Regular `http(s)` links and inline `data:image/...` images are preserved.

**Example:**
```json
{ "knowledge_id": 101 }
```

**Example response:**
```
**Conhecimento: Como configurar VPN**

**ID:** 101
**Visibilidade:** Privado
**Tags:** VPN, acesso remoto
**Criado em:** 10/01/2026
**Atualizado em:** 01/06/2026

---

## Introducao

Este guia cobre a configuracao de VPN...
```

### delete_knowledge
Archive (soft-delete) a knowledge base article by ID. Requires the "Gerenciar conhecimento" permission.

**⚠ Warning:** This is a soft delete — the content and version history are kept — but it **cannot be undone via the API**: the archived article disappears from reads (`get_knowledge`/`update_knowledge` return 404) and there is no restore endpoint. It can only be restored in the TiFlux app, where its folders must be linked again, because the article is unlinked from **all folders** where it was published. To edit the article before archiving, use `update_knowledge`.

A pre-flight lookup fetches the article title before the DELETE (for the confirmation message). If the pre-flight fails, the DELETE proceeds anyway.

**Parameters:**
- `knowledge_id` (number, required): ID of the knowledge article to archive (obtained from `list_knowledges` or `get_knowledge`).

**Returns:** Confirmation message with the article ID and title, listing the effects (archived + unlinked from folders).

**Example:**
```json
{ "knowledge_id": 101 }
```

**Errors:**
- `404`: Article not found or not visible to the user. Without the "Gerenciar base de conhecimento" permission, only public articles and those in the user's attendant groups are accessible.
- `403`: Missing the "Gerenciar base de conhecimento" permission.

### get_knowledge_folder
Fetch the full detail of a knowledge base folder by ID, including the list of articles published in it.

**Parameters:**
- `folder_id` (number, required): ID of the knowledge folder (obtained from `list_knowledge_folders`).

**Returns:** Folder detail with title, description, icon, tags, total article count, and a table of visible articles (`ID | Titulo`). If `qty_knowledges` is greater than the number of listed articles, the user lacks permission to see all of them — both numbers are shown.

**Example:**
```json
{ "folder_id": 1 }
```

**Example response:**
```
**Pasta de Conhecimento: Rede e VPN**

**ID:** 1
**Icone:** —
**Tags:** rede, VPN
**Descricao:** Artigos sobre redes corporativas.
**Artigos:** 5

| ID | Titulo |
|---|---|
| 101 | Como configurar VPN |
| 102 | Politica de Senhas |
```

**Errors:**
- `404`: Folder not found or not visible to the user. Without the "Gerenciar base de conhecimento" permission, only folders containing public articles or articles from the user's attendant groups are returned.

### update_knowledge_folder
Partially update an existing knowledge base folder (title, description, icon, tags). Only the fields provided are sent — omitted fields remain unchanged. Requires the "Gerenciar conhecimento" permission. This endpoint does **not** change the folder's published articles — use `get_knowledge_folder` to see them.

**Required fields:**
- `folder_id` (number): ID of the folder to update (obtained from `list_knowledge_folders` or `get_knowledge_folder`).

**Optional fields (at least one must be provided):**
- `title` (string): New folder title (max 255 chars; empty string rejected by the API).
- `description` (string): New folder description. **Plain text** — unlike `update_knowledge`, this field does not accept Markdown/HTML conversion.
- `icon` (string or `null`): New icon — a single emoji (e.g. `"🚀"`). **Sending `icon: null` explicitly removes the current icon** — this is different from omitting the field, which preserves the existing icon. An empty string (`""`) or more than one emoji is rejected by the API (422).
- `tags` (array of strings): Replaces all current tags. Tags must not contain commas; the combined length (including separator commas) must not exceed 255 chars. An empty array (`[]`) removes all tags.

**Returns:** Confirmation with the folder ID, title, icon, tags, description, article count (`qty_knowledges`), and the list of changed fields. Does **not** re-print the folder's article list — the endpoint doesn't alter it, and the list can be large.

**Example:**
```json
{ "folder_id": 1, "icon": "🚀", "tags": ["rede", "VPN", "infra"] }
```

**Example response:**
```
**Pasta de conhecimento #1 atualizada**

**ID:** 1
**Titulo:** Rede e VPN
**Icone:** 🚀
**Tags:** rede, VPN, infra
**Descricao:** Artigos sobre configuracao de redes corporativas e VPN.
**Artigos:** 3
**Campos alterados:** icon, tags

*Tags substituidas.*

*Use get_knowledge_folder para ver os artigos publicados nesta pasta.*
```

**Example — removing the icon:**
```json
{ "folder_id": 1, "icon": null }
```

**Errors:**
- `403`: Missing the "Gerenciar conhecimento" permission.
- `404`: Folder not found, or belongs to another organization.
- `422`: Validation error (empty/too-long title, non-emoji icon, tag with comma, tags total over 255 chars) — the API's error detail is shown in the response.

### create_knowledge_folder
Create a new knowledge base folder. Requires the "Gerenciar conhecimento" permission. The folder always starts empty — use `create_knowledge` with `knowledge_folder_ids` to publish articles into it.

**Required fields:**
- `title` (string): Folder title (max 255 chars; empty string rejected by the API).

**Optional fields:**
- `description` (string): Folder description. **Plain text** — unlike `create_knowledge`, this field does not accept Markdown/HTML conversion.
- `icon` (string): A single emoji (e.g. `"🚀"`). Empty string (`""`) or more than one emoji is rejected by the API (422).
- `tags` (array of strings): Tags must not contain commas; the combined length (including separator commas) must not exceed 255 chars.

**Returns:** Confirmation with the new folder's ID, title, icon, tags, description, and article count (always `0` — a new folder is always empty).

**Example:**
```json
{ "title": "Rede e VPN", "icon": "🚀", "tags": ["rede", "VPN"] }
```

**Example response:**
```
**Pasta de conhecimento #15 criada**

**ID:** 15
**Titulo:** Rede e VPN
**Icone:** 🚀
**Tags:** rede, VPN
**Descricao:** —
**Artigos:** 0

*Pasta criada vazia — use create_knowledge com knowledge_folder_ids: [15] para publicar artigos nela.*
```

**Errors:**
- `403`: Missing the "Gerenciar conhecimento" permission.
- `422`: Validation error (empty/too-long title, non-emoji icon, tag with comma, tags total over 255 chars) — the API's error detail is shown in the response.

### list_knowledge_folders
List knowledge base folders with optional search and pagination. Returns a Markdown table with ID, title, description (truncated), icon, article count, and tags.

**Parameters:**
- `search` (string, optional): Search by folder title (case-insensitive).
- `limit` (number, optional): Results per page (default: 20, min: 1, max: 200). Values outside the range are clamped; a non-integer value (`"abc"`, `"0x10"`, `1.5`, negative) is rejected with a validation error instead of being silently coerced.
- `offset` (number, optional): Page number, 1-based (default: 1, min: 1). Same strict validation as `limit`.

**Returns:** Markdown table with columns `ID | Titulo | Descricao | Icone | Qtd | Tags`.

**Example:**
```json
{ "search": "rede", "limit": 10 }
```

**Example response:**
```
| ID | Titulo | Descricao | Icone | Qtd | Tags |
|---|---|---|---|---|---|
| 1 | Rede e VPN | Artigos sobre redes... | — | 5 | rede, VPN |
```

### create_knowledge
Create a new knowledge base article. Requires the "Gerenciar conhecimento" permission.

**Note:** The `description` field accepts **Markdown** (the MCP converts it to HTML before sending). HTML is also accepted (idempotent). Images must be embedded by URL: `![alt](https://url)` — base64 and local files are not supported by the API.

**Required fields:**
- `title` (string): Article title.
- `description` (string): Article body in Markdown or HTML. Example: `"## Section\n\n- Step 1\n- Step 2"`.
- `knowledge_folder_ids` (array of numbers, min 1): IDs of the folders where the article will be published. Example: `[12, 34]`.

**Optional fields:**
- `tags` (array of strings): Tags for the article. Tags must not contain commas. Example: `["VPN", "remote access"]`.
- `private` (boolean): Whether the article is private (default: `true`). If `false`, the article is public.
- `client_ids` (array of numbers): Client IDs with access — only relevant when `private: true`. Example: `[100, 200]`.
- `technical_group_ids` (array of numbers): Technical group IDs with access — only relevant when `private: true`. Example: `[5, 10]`.
- `services_catalogs_item_ids` (array of numbers): Related service catalog item IDs. Example: `[301, 302]`.

**Returns:** Confirmation with the created article's ID, title, visibility, folders, tags, and related IDs.

**Example:**
```json
{
  "title": "How to configure VPN",
  "description": "<p>Step-by-step VPN setup guide for remote access.</p>",
  "knowledge_folder_ids": [1, 2],
  "tags": ["VPN", "remote access"],
  "private": true,
  "technical_group_ids": [5]
}
```

**Example response:**
```
Conhecimento criado com sucesso!

**ID:** 201
**Titulo:** How to configure VPN
**Visibilidade:** Privado
**Pastas:** 1, 2
**Tags:** VPN, remote access
**Grupos tecnicos vinculados:** 5
```

### update_knowledge
Partially update an existing knowledge base article. Only the fields provided are sent — omitted fields remain unchanged. Requires the "Gerenciar conhecimento" permission.

**Required fields:**
- `knowledge_id` (number): ID of the knowledge article to update (obtained from `list_knowledges` or `get_knowledge`).

**Optional fields (at least one must be provided):**
- `title` (string): New article title (max 255 chars; empty string rejected by API).
- `description` (string): New article body in Markdown or HTML. **⚠ Sending this field creates a new version** — per the API contract, only the 10 latest versions are retained (retention is not observable through the public API). Empty string is rejected by the API (422).
- `private` (boolean): If `false`, makes the article public and automatically clears `client_ids` and `technical_group_ids`. Do not send non-empty `client_ids`/`technical_group_ids` together with `private: false`.
- `tags` (array of strings): Replaces current tags. Tags must not contain commas.
- `client_ids` (array of numbers): Replaces current client access list. Empty array removes all. Only applicable to private articles.
- `technical_group_ids` (array of numbers): Replaces current technical group access list. Empty array removes all. Only applicable to private articles.
- `knowledge_folder_ids` (array of numbers): Replaces current folder associations. **Empty array is rejected by the API** — the article must belong to at least one folder.
- `services_catalogs_item_ids` (array of numbers): Replaces current service catalog item associations. Empty array removes all.

**Returns:** Confirmation with the article ID, title, visibility, tags, and updated fields. Includes a notice when a new version was created or when `private: false` zeroed the access bindings.

**Contract validated against the live API (2026-09-01, test org, disposable article created and archived):** `PUT /knowledges/{id}` responds `200`; all 8 updatable field names are accepted (an unknown field returns `400`); a partial update sending only `title` preserves `description`; `knowledge_folder_ids: []`, `title: ""` and `description: ""` each return `422 can't be blank`; `private: false` alone clears `client_ids` and `technical_group_ids`; `private: false` together with non-empty bindings returns `422` (the MCP guard pre-empts this); a non-existent ID returns `404`. The 10-version retention is the only claim that could not be observed (there is no versions endpoint) and is therefore stated conditionally.

**Example:**
```json
{
  "knowledge_id": 4,
  "title": "VPN Setup Guide (revised)",
  "tags": ["VPN", "remote access", "updated"]
}
```

**Example response:**
```
Conhecimento #4 atualizado com sucesso!

**ID:** 4
**Titulo:** VPN Setup Guide (revised)
**Visibilidade:** Privado
**Tags:** VPN, remote access, updated
**Campos alterados:** title, tags
```

### list_contracts
List the organization's contracts (read-only). Returns a Markdown table with 9 columns: ID, name, client, contract type, modality, status (`Ativo`/`Pendente de reajuste`/`Expirado`/`Cancelado`, with a `(cancelamento agendado)` suffix when the contract is still active but already has a scheduled cancellation), expiration date, readjustment date, and total value.

**Contract groups (Shared modality):** a row with `kind: "contract_group"` is a **contract group**, not an individual contract — its ID column renders as `<id> · grupo` (e.g. `5 · grupo`). Contracts and contract groups come from **separate tables with independent IDs**: the same number can appear twice in the listing, once as a plain contract and once as a group. When the current page has at least one group, a footer explains this and points to `get_contract_group` for the group's detail and member contracts. A missing `kind` field (older API/mock) is treated as a plain `"contract"`.

**Known API bug — duplicate rows:** when a contract group has two riders with the same number/version, the API's `GET /contracts` UNION joins both and the row comes back duplicated (reported to the API team). The tool de-duplicates by `kind+id` **within the current page** (keeping the first occurrence) and appends a note when it happens; the `X-Total-Items` count itself can still be inflated by the duplicate, since the tool doesn't recompute it. A duplicate that spans two pages isn't detectable without fetching everything, and is out of scope.

**Note:** Inactive records (`active: false`) **never** appear in this listing, regardless of the `status` filter. Member contracts of a Shared contract group **never** appear as their own row either — only the group row shows up; use `get_contract_group` to see its members. For the detail of an individual contract, use `get_contract` (a contract ID that is a group member returns `404` there — use `get_contract_group` in that case). To discover `contract_type_ids` values, use `list_contract_types` (recommended); `include_details: true` remains a useful alternative, surfacing `client.id`/`contract_type.id` alongside the contract row without an extra round-trip.

**Permissions:** The monetary fields (`rider_tax`, `rider_value`) are only available in the details block when using `include_details: true`, and only for users with the "Visualizar valores dos tickets" permission. Without it, the API returns `"--"` for those fields (rendered as-is). `total_value` is shown in the default table column.

**Parameters (all optional):**
- `include_details` (boolean, default `false`): When `true`, appends a detail block after the table with one line per contract showing `client.id` (useful in `client_ids`), `contract_type.id` (useful in `contract_type_ids`), `duration`, `readjust_duration`, and `rider_value`/`rider_tax`. **Not sent to the API — rendering-only.**
- `client_ids` (string, CSV): Filter by clients, IDs separated by commas (e.g. `"982,2,1024"`).
- `contract_type_ids` (string, CSV): Filter by contract types, IDs separated by commas (e.g. `"3,27"`).
- `status` (string, CSV): Filter by status — `actives`, `readjust`, `expired`, separated by commas (e.g. `"actives,expired"`). **By default the API lists only `actives`.**
- `limit` (number, optional): Results per page (default: 20, max: 200). Strictly validated — a non-integer value (e.g. `"abc"`, `0`) raises a validation error instead of silently falling back to the default; values above 200 are clamped.
- `offset` (number, optional): Page number (default: 1).

**Returns:** Markdown table with columns `ID | Nome | Cliente | Tipo | Modalidade | Situação | Expiração | Reajuste | Valor total`. Modality and status are translated to PT-BR (unknown enum values fall back to the raw API value). Dates are rendered as ISO `YYYY-MM-DD`. Monetary values are formatted as `R$ 1.234,56` (with thousand separator). Total contract count from `X-Total-Items` header is shown in the pagination footer when available. With `include_details: true`, a `**Detalhes**` section follows the table; when the combined table + details output would exceed the response budget (e.g. `limit: 200` with `include_details: true`), both sections are cut at the same contract, with a continuation line showing the exact `offset`/`limit` to resume. When the requested page comes back empty but the filter has results on other pages (`X-Total-Items > 0`), the tool says so explicitly ("página N está além do fim...") instead of the generic "no contracts found" message.

**Example:**
```json
{
  "client_ids": "44",
  "status": "actives,expired",
  "include_details": true,
  "limit": 10
}
```

**Example response:**
```
| ID | Nome | Cliente | Tipo | Modalidade | Situação | Expiração | Reajuste | Valor total |
|---|---|---|---|---|---|---|---|---|
| 87508 | Contrato de licença de uso | 2V Sistemas | Contrato Tiflux | SaaS/Produto | Ativo | — | 2027-05-25 | R$ 974,30 |
| 5 · grupo | Contrato compartilhado XPTO | Acme Corp | Contrato compartilhado | Compartilhado | Ativo | — | — | R$ 5.000,00 |
| 103 | Contrato Expirado | Initech | Suporte | Horas | Cancelado | 2024-12-31 | 2024-01-01 | R$ 28.963,20 |

> ℹ️ Esta página contém grupo(s) de contrato (linhas "· grupo"). IDs de grupo e de contrato são independentes — o mesmo número pode aparecer duas vezes na lista. Veja o detalhe e os membros de um grupo com `get_contract_group`.
>
> *1 linha(s) duplicada(s) pela API foram omitidas; o total informado pela API pode estar inflado.* (exibido só quando a API devolveu uma linha de grupo duplicada)

**Detalhes**
- **#87508** · cliente ID 2274047 · tipo ID 178 · duracao: — · reajuste a cada 12 meses · adicional: R$ 974,30 (taxa R$ 0,00)
```

### list_contract_types
List the contract types registered in the organization: ID, name, and modality (humanized text, e.g. "Horas", "Compartilhado", "Crédito"). No search parameter — only pagination (`offset`/`limit`). The IDs returned are the ones accepted by the `contract_type_ids` filter of `list_contracts`: use this tool to discover those IDs instead of running `list_contracts` with `include_details: true`.

**Permissions:** Requires "Visualizar contratos" permission.

**Parameters (all optional):**
- `offset` (number): Page number (default: 1).
- `limit` (number): Results per page (default: 20, max: 200).

**Returns:** Markdown table `ID | Nome | Modalidade`. A missing/untranslated `modality` from the API (blueprint has a `rescue ""` fallback) renders as `—` instead of an empty cell. Total count from `X-Total-Items` header shown in the pagination footer when available.

**Example:**
```json
{ "limit": 50 }
```

**Example response:**
```
| ID | Nome | Modalidade |
|---|---|---|
| 1 | Contrato Horas | Horas |
| 2 | Contrato Compartilhado | Compartilhado |
```

### get_contract_group
Get the detail of a contract group (Shared modality) by ID: name, client, contract type, status (`Ativo`/`Inativo`/`Expirado`/`Cancelado`, derived from the expiration date and the record — this endpoint returns `200` even for an inactive or expired group, which `list_contracts` hides), expiration, duration, automatic renewal, billing configuration (automatic with N days before due date, or batch), consumption reminder (percent + e-mails), last rider (number, release/version, due day, monthly value, tax, discount, description), and the group's member contracts.

**Status precedence:** `last_rider.cancelled` → `Cancelado` (defensive — today's view doesn't expose this field, so this only fires if the API starts sending it) > `expiration_date` in the past (America/Sao_Paulo) → `Expirado` > `active: false` → `Inativo` > otherwise `Ativo`.

**Note:** the `contract_group_id` must come from a `· grupo` row of `list_contracts` — contracts and contract groups have **independent IDs**, the same number can exist in both, and a plain contract ID does not work here (404). The last rider shown **can be a cancelled one** (the API takes the highest rider/release regardless of cancellation) — the label is always "Último aditivo" ("last rider"), never "current/active rider".

**Permissions:** requires "Visualizar contratos" + Tickets License. Monetary fields of the rider (`value`, `discount_value`, `tax`) come as `"--"` without the "Visualizar valores dos tickets" permission — same rule as `list_contracts`/`get_contract`.

**Parameters:**
- `contract_group_id` (number, required): ID of the contract group (obtained from a `· grupo` row in `list_contracts`). Must be a positive integer.

**Returns (rich):** header with name and `#id`; client (name + id); contract type (name + id); status; expiration; duration; automatic renewal; billing; consumption reminder; last rider section (number, release, due day, monthly value, tax, discount — in `R$` when `discount_type: "currency"`, in `%` when `"percentage"` (or the legacy `"percent"`), raw otherwise — and description); `**Contratos do grupo (N)**` list with `#id name` per member (cut with a "+N membros não exibidos" notice if the group has too many members); technical observations (truncated at 800 chars).

**Returns (compact):** header, client, status, monthly value, and members on a single line (`#1 nome; #2 nome; ...`). Consumption reminder and technical observations are omitted in compact mode.

**Errors:** `404` (`error_code 40401`) includes a hint that the ID must come from a `· grupo` row of `list_contracts`. `403` (`40301`/`40304`) includes a hint about the "Visualizar contratos" permission and the Tickets License.

**Example:**
```json
{ "contract_group_id": 5 }
```

### get_contract
Get the detail of an individual contract (not Shared modality) by ID: name, client, contract type (name + modality), status (active/expired/inactive/cancelled/cancellation scheduled, billed, with appointments), expiration, duration, automatic renewal, permits appointments pending readjustment, billing (automatic with N days before due date, or batch), consumption reminder, technical observations, created/updated timestamps, and the last rider — number, release/version, due day, monthly value, discount, description, closing period, periodicity and billing timing (translated to PT-BR), and, **when present in the payload** (decided by field presence, never by modality), the fields specific to each modality: hour allowance + surplus hour value (Horas/Horas cumulativas), accumulation cycle (Horas cumulativas), ticket allowance + surplus ticket value (Por atendimento), external/internal/remote value (Crédito), equipment limit + additional equipment value (Livre), and who created the rider.

**Note:** a contract that is a **member of a contract group** (Shared modality) returns `404` here even though its ID exists — member contracts are only queried through their group. The error message explains both possibilities (non-existent ID or group member) and points to `get_contract_group`. The `kind` field from `list_contracts` tells you which route to use: `"contract"` → `get_contract`, `"contract_group"` → `get_contract_group`. IDs of contracts and contract groups are independent and can collide. For how much of the contract has been consumed and the balance per billing cycle, use `get_contract_usage`.

**Permissions:** Requires "Visualizar contratos" + Tickets License. Monetary fields of the rider come as `"--"` without the "Visualizar valores dos tickets" permission — the MCP renders what the API returns, without masking on its own.

**Parameters:**
- `contract_id` (number, required): ID of the contract (from a `list_contracts` row without the `· grupo` suffix). Must be a positive integer.

**Returns (rich):** header with name and `#id`; client (name + id); contract type (name + id + modality); status (with extra flags in parentheses when `billed`/`has_appointments`/`readjust` are true); expiration; duration; automatic renewal; permits appointments pending readjustment; billing; consumption reminder; created/updated timestamps; last rider section (number, release, due day, monthly value, discount — `R$` for `discount_type: "currency"`, `%` for `"percentage"` (or the legacy `"percent"`), raw otherwise — start date, cancellation date when cancelled, periodicity, billing timing, closing period, per-modality fields listed above, description, who created the rider, and an item list when the rider has `items`); technical observations (truncated at 800 chars).

**Returns (compact):** header, client, status, and monthly value on a single line.

**Errors:** `404` (`error_code 40401`) explains the contract may not exist OR may be a group member, and suggests `get_contract_group`. `403` (`40301`/`40304`) includes a hint about the "Visualizar contratos" permission and the Tickets License.

**Example:**
```json
{ "contract_id": 82657 }
```

### get_contract_usage
Get the consumption and balance per billing cycle of an individual contract (not a group) by ID: up to 3 cycles (most recent first, started on or before `base_date`), with contracted/used/balance/% used/surplus in the modality's unit — hours for Horas/Horas cumulativas, R$ for Crédito, ticket count for Por atendimento. New endpoint (`GET /contracts/{id}/usage`, added 2026-10-06).

**No cycle of consumption:** contracts in Livre, SaaS/Produto, and Compartilhado (group) modalities never have a billing cycle — the tool says so explicitly (`cycles: []` with a modality-specific message) instead of returning an empty list silently. For other modalities, an empty `cycles` list happens when the contract is expired, pending readjustment, cancelled, has no rider, or `base_date` is past the contract's validity — the tool lists these causes and suggests `get_contract` or an earlier `base_date`.

**Note:** does **not** work for a contract **group** (there is no `/contract-groups/{id}/usage` in the API v2 — use `get_contract_group` for the group's detail instead) nor for a free date range (only a single `base_date`, a point in time). A contract ID that is a group member returns `404` here, same caveat as `get_contract`.

**Permissions:** requires "Visualizar contratos" (atendente) or "Visualizar contrato" (usuário-cliente) + Tickets License. In Crédito contracts, `surplus` is masked (`"--"`) without "Visualizar valores dos tickets" (atendente) / "Visualizar informações de valorização" (usuário-cliente); `contracted`/`used`/`balance`/`percent_used` are masked too, but **only in Crédito** — these are plain numbers (hours, ticket counts) in every other modality, so there's nothing to mask.

**Parameters:**
- `contract_id` (number, required): ID of the individual contract (not a group). Must be a positive integer.
- `base_date` (string, optional): `YYYY-MM-DD`. Default is today (or tomorrow, when today is day 1 of the month). The returned cycles are the ones started on or before this date — not a free interval.

**Returns (rich):** header with contract name, `#id`, modality, `base_date` (or "hoje"), and the last consumption-reminder date when present; one `### Ciclo <previous_billing_date> → <billing_date> (aberto|fechado · <período>)` section per cycle (with a `(cancelado)` suffix when the cycle's rider was cancelled), each with Contratado/Usado/Saldo/% usado/Excedente, "Deslocamentos usados" (omitted when `null` — always `null` in Crédito), and, in Crédito only, Atendimento externo/remoto/interno in `h:mm` (converted from minutes). A note is added when the modality is Horas cumulativas (contracted includes previous cycles' accumulated credit) and when an open cycle shows a negative balance with `surplus: "0.00"` (the surplus only counts reviewed tickets yet — the final value comes out at closing).

**Returns (compact):** one line per cycle (`<início>→<fim> (aberto|fechado): usado X de Y (P%) · saldo S · excedente E`), no sections, no attendance breakdown.

**Errors:** `404` (`error_code 40401`) uses the same text as `get_contract` (non-existent ID or group member) plus a note that there is no group-level consumption endpoint in the API v2. `422` (`42201`) shows the `detail.base_date` message. `403` (`40304`) is the license hint; `403` (`40301`) cites both permissions (atendente/usuário-cliente).

**Example:**
```json
{ "contract_id": 82657, "base_date": "2026-10-06" }
```

**Example response (rich, Horas modality):**
```
## Consumo — Contrato Horas Teste (#82657)

**Modalidade:** Horas
**Data-base:** 2026-10-06

### Ciclo 2026-10-01 → 2026-10-31 (aberto · Mensal)
- Contratado: 30,00 h
- Usado: 0,03 h
- Saldo: 29,97 h
- % usado: 0,11%
- Excedente: R$ 0,00
- Deslocamentos usados: 1
```

### list_equipments
List equipments/resources of the organization. Returns a Markdown table with ID, name, client, type, group, online status, and IP address for each resource. Optional blocks for manufacturer (`manufacturer`), OS (`system`) and a summarized hardware inventory (`include_technical_info`) can be requested via flags — only populated for machines with the TiFlux agent installed.

**Note:** Agent-specific fields (`online`, `ipv4`, `last_seen`, `agent`) are only present for machines with the TiFlux agent. Manual resources (no agent) show `—` in those columns.

**Permissions:** Requires "Visualizar recursos" permission + Tickets License.

**Parameters:**

| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| `client_id` | number | no | — | Filter resources of a specific client (client ID) |
| `include_manufacturer` | boolean | no | false | Include manufacturer info (make, model, serial/asset tag). No processor/memory — use `include_technical_info` for that. Only populated for agent-machines |
| `include_system` | boolean | no | false | Include OS info (name, version, kernel). Only populated for agent-machines |
| `include_technical_info` | boolean | no | false | Include a one-line hardware inventory summary per resource: CPU, RAM, disks (size and % used), antivirus and pending Windows Updates. In compact mode, only CPU, RAM and the fullest disk. Network, motherboard, video, S.M.A.R.T., sound and printers stay in `get_equipment`. Only populated for agent-machines |
| `limit` | number | no | 20 | Results per page (max: 200) |
| `offset` | number | no | 1 | Page number |

**Returns:** Markdown table `ID | Nome | Cliente | Tipo | Grupo | Online | IP`. When `include_manufacturer`/`include_system`/`include_technical_info` are requested and present, additional sections are appended below the table. Pagination footer with total count when available.

**Inventory line** (`include_technical_info: true`), e.g.:
`• **NOTE-01** (#11) — CPU: Intel i5-8250U | RAM: 8 GB | Discos: C: 238.4 GB 91% ⚠️ | Antivirus: Windows Defender (ativo, atualizado) | Windows Update: 3 pendente(s) ⚠️ criticas pendentes`
- ⚠️ flags a disk at ≥ 90% usage, an inactive/outdated antivirus and pending critical updates.
- Empty blocks (`[]`, `null`, `{pending_count: 0}`) take no space; a resource with no inventory gets no line.

**Response budget:** the page is cut at ~40k characters at a resource boundary (table and sections show the same resources), with a continuation line (`offset`/`limit` to resume). With more than 50 resources and no explicit verbosity, the list switches to compact automatically.

**Example:**
```json
{ "client_id": 724, "include_manufacturer": true, "limit": 50 }
```

```json
{ "client_id": 724, "include_technical_info": true }
```

**Typical flows:**
- "Machines of client X online?" → `list_equipments` with `client_id` → filter `online`.
- "Manufacturer/asset tag of a machine?" → `list_equipments` with `include_manufacturer: true`.
- "Which machines have a nearly full disk or outdated antivirus?" → `list_equipments` with `include_technical_info: true` → look for ⚠️; then `get_equipment` for the full inventory.

---

### get_equipment
Get full details of a single equipment/resource by ID. Returns a comprehensive Markdown report including hardware inventory (processor, memory, disks, network adapters, sound, video, OS, manufacturer, antivirus, etc.), client, type, group, last contact date, acquisition/warranty dates (when set), and optional custom fields.

**Note:** The detail endpoint always includes hardware inventory when the TiFlux agent is installed — no extra flags needed. Fields like `agent_email`/`agent_user` may be `null` on some machines and populated on others. The `network` adapter `ipv4` field is a comma-separated string that may contain multiple IPv4 and IPv6 addresses.

**Inventory fields returned by the API** (verified against real payloads on 2026-08-07): `processor.name`; `memory.total_gb` (number, GB); `motherboard.{manufacturer,model,bios}`; `disks[].{name,size_gb,use_percent}`; `disksmart[].{model,status}`; `network[].{name,ipv4,mac}`; `printer[].{name,port,default}`; `sound[].name`; `vga[].{name,vram_mb}`; `operating_system.{name,version,kernel,service_pack}` (`kernel` carries the architecture, e.g. `"64 bits"`); `windows_update.{pending_count,has_critical_pending}`; `manufacturer.{name,model,serial}`; `antivirus[].{name,active,up_to_date}`; plus `current_user`.

**Permissions:** Requires "Visualizar recursos" permission + Tickets License.

**Parameters:**

| Parameter | Type | Required | Description |
|---|---|---|---|
| `equipment_id` | number | yes | Resource ID — use `list_equipments` to discover |
| `show_entities` | boolean | no | Include custom fields (entities) linked to the resource (default: false) |

**Returns:** Full Markdown report with all available inventory sections. Sections for empty/null inventory blocks are omitted automatically.

**Example:**
```json
{ "equipment_id": 385053 }
```

```json
{ "equipment_id": 385053, "show_entities": true }
```

---

### create_equipment
Create a new equipment/resource in TiFlux.

**Permissions:** Requires "Gerenciar recursos" permission + Tickets License.

**Parameters:**

| Parameter | Type | Required | Description |
|---|---|---|---|
| `name` | string | yes | Resource name (max 255 chars) |
| `client_id` | number | yes | Client ID the resource belongs to |
| `equipment_type_id` | number | yes | Resource type ID — use `list_equipment_types` to discover |
| `equipment_group_id` | number | no | Resource group ID — use `list_equipment_groups` with `client_id` to discover. If omitted, API auto-assigns to the first group of the client |
| `acquisition_date` | string | no | Acquisition date (YYYY-MM-DD) |
| `warranty_date` | string | no | Warranty end date (YYYY-MM-DD; must be >= `acquisition_date`) |

**Returns:** Confirmation text with resource ID, name, client, type, and group.

**Example:**
```json
{
  "name": "Notebook do João",
  "client_id": 724,
  "equipment_type_id": 1,
  "equipment_group_id": 5,
  "acquisition_date": "2024-03-15",
  "warranty_date": "2027-03-15"
}
```

**Tip:** Types and groups are organization-specific — run `list_equipment_types` and `list_equipment_groups` (with `client_id`) before creating to get the correct IDs.

---

### update_equipment
Update an existing equipment/resource in TiFlux. Only provided fields are sent in the update.

**Note:** `client_id` cannot be changed via update — to change the client, create a new resource.

**Permissions:** Requires "Gerenciar recursos" permission + Tickets License.

**Parameters:**

| Parameter | Type | Required | Description |
|---|---|---|---|
| `equipment_id` | number | yes | Resource ID to update |
| `name` | string | no | New name (max 255 chars) |
| `equipment_type_id` | number | no | New type ID |
| `equipment_group_id` | number | no | New group ID — must belong to the same client as the resource |
| `acquisition_date` | string | no | New acquisition date (YYYY-MM-DD) |
| `warranty_date` | string | no | New warranty end date (YYYY-MM-DD; must be >= `acquisition_date`) |

**Returns:** Confirmation text with resource ID, name, and updated fields.

---

### list_equipment_softwares
List software installed on a resource (collected by the TiFlux agent). Returns a Markdown table with name, version, and vendor of each software.

**Note:** Only resources with the TiFlux agent installed have a software inventory. Manual resources (no agent) return an empty list — the response message clarifies this.

**Permissions:** Requires "Visualizar recursos" permission + Tickets License.

**Parameters:**

| Parameter | Type | Required | Description |
|---|---|---|---|
| `equipment_id` | number | yes | Resource ID — use `list_equipments` to discover |

**Returns:** Markdown table `Nome | Versao | Fabricante`. Empty list message explains that inventory is only available for agent-machines.

**Example:**
```json
{ "equipment_id": 11 }
```

---

### list_equipment_groups
List equipment/resource groups. Returns a Markdown table with ID, name, and client for each group.

**Note:** Groups are client-scoped — each client can have different groups. Use `client_id` to filter groups before creating a resource.

**Permissions:** Requires "Visualizar recursos" permission + Tickets License.

**Parameters:**

| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| `client_id` | number | no | — | Filter groups of a specific client — recommended when creating resources |
| `limit` | number | no | 20 | Results per page (max: 200) |
| `offset` | number | no | 1 | Page number |

**Returns:** Markdown table `ID | Nome | Cliente` with pagination footer.

**Example:**
```json
{ "client_id": 724, "limit": 50 }
```

---

### list_equipment_types
List equipment/resource types of the organization. Returns a Markdown table with ID and name.

**Note:** Types are organization-specific. Common defaults: "Estação", "Hardware", "Software". Use this to get the correct `equipment_type_id` before creating a resource.

**Permissions:** Requires "Visualizar recursos" permission + Tickets License.

**Parameters:**

| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| `name` | string | no | — | Filter by name (partial, case-insensitive, max 255 chars). E.g. `"esta"` matches "Estação" |
| `limit` | number | no | 20 | Results per page (max: 200) |
| `offset` | number | no | 1 | Page number |

**Returns:** Markdown table `ID | Nome` with pagination footer.

**Example:**
```json
{ "name": "esta" }
```

---

## Pre-Ticket Tools

### list_pre_tickets
List pre-tickets of the organization. Pre-tickets are service requests in a pre-triage stage — they carry requestor data, title, description, and optional client/equipment links, but have not yet been converted into tickets.

**Permissions:** Requires Tickets license + "Gerenciar pré-tickets" permission.

**Parameters:**

| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| `archived` | boolean | no | false | If `true`, returns only archived pre-tickets; if `false`, returns only active ones |
| `client_id` | number | no | — | Filter by client ID. Example: `724` |
| `created_after` | string | no | — | Return pre-tickets created on or after this date (YYYY-MM-DD). Example: `"2026-07-01"` |
| `created_before` | string | no | — | Return pre-tickets created on or before this date (YYYY-MM-DD). Example: `"2026-07-31"` |
| `include_description` | boolean | no | false | If `true`, includes the `description` field in the response (omitted by default to reduce payload) |
| `limit` | number | no | 20 | Results per page (max: 200) |
| `offset` | number | no | 1 | Page number |

**Returns:** Markdown table `ID | Título | Cliente | Solicitante | Criado em` with pagination footer. Empty list returns a message with guidance. **Fixed in v2.50.1:** `Criado em` now shows the Brasília-time date — previously used the host process's timezone.

**Example:**
```json
{ "client_id": 724, "created_after": "2026-07-01", "limit": 50 }
```

---

### create_pre_ticket
Create a new pre-ticket in TiFlux. Pre-tickets represent incoming service requests before they are assigned and triaged into formal tickets. Supports file attachments.

**Permissions:** Requires Tickets license + "Gerenciar pré-tickets" permission.

**Parameters:**

| Parameter | Type | Required | Description |
|---|---|---|---|
| `title` | string | yes | Pre-ticket title |
| `description` | string | yes | Description/details of the request |
| `requestor_name` | string | yes | Requestor full name |
| `requestor_email` | string | yes | Requestor e-mail address |
| `requestor_telephone` | string | yes | Requestor telephone number |
| `requestor_ramal` | number | no | Requestor extension (ramal) |
| `requestor_country` | string | no | Requestor country (ISO 2-letter code). Example: `"BR"`, `"US"` |
| `client_id` | number | no | Client ID to associate with the pre-ticket. Example: `724` |
| `files` | array | no | Attachments. Each item: `{ "content": "<base64>", "filename": "file.pdf" }`. Max 10 files, 25MB each |

**Returns:** Confirmation text with pre-ticket ID, title, client, and requestor info.

**Example (minimal):**
```json
{
  "title": "Sistema fora do ar",
  "description": "Não consigo acessar o sistema desde as 8h.",
  "requestor_name": "João Silva",
  "requestor_email": "joao.silva@empresa.com",
  "requestor_telephone": "11999990001"
}
```

**Example (with client and attachment):**
```json
{
  "title": "Erro na impressão",
  "description": "A impressora da recepção não imprime documentos PDF.",
  "requestor_name": "Maria Souza",
  "requestor_email": "maria.souza@empresa.com",
  "requestor_telephone": "11999990002",
  "client_id": 724,
  "files": [{ "content": "<base64>", "filename": "screenshot.png" }]
}
```

---

### list_gupshup_templates
List HSM templates from the Gupshup WhatsApp integration available in the organization. Returns the name, approval status, category, content (with variables like `{{1}}`), description, and integration ID of each template. Use to discover the approved template IDs and names that feed `send_message` (origin: `gupshup`).

**Permissions:** Requires "Gerenciar Modelos" permission.

**Parameters:**

| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| `integration_id` | number | no | — | Filter by Gupshup integration ID. Example: `2` |
| `offset` | number | no | 1 | Page number |
| `limit` | number | no | 20 | Results per page (max 200) |

**Returns:** Compact list — one line per template with name, status, category, integration ID, and a short preview (~70 chars) of the HSM content (variables like `{{1}}` preserved). The one-line format keeps the payload bounded even on a full 200-item page. To read a template's full content, narrow the list (e.g. by `integration_id`) and use a small page.

```
**Templates Gupshup (200)**

- **first_contact_with_org** (ID 15174) · APPROVED · UTILITY · int 1867 — _Olá, tudo bem? aqui é o {{1}} da {{2}}. Podemos conversar sobre o seu…_
- **hsm_template** (ID 1) · APPROVED · ALERT_UPDATE · int 1 — _You are in department {{1}}_
```

**Example:**
```json
{
  "integration_id": 2,
  "limit": 20
}
```

---

### list_whatsapp_cloud_templates
List templates from the WhatsApp Cloud (Meta) integration available in the organization. Returns name, approval status, language, category, Meta template ID, and a short preview of the body text. Use to discover templates that feed `send_message` (origin: `whatsapp_cloud`). Filter by `status: "APPROVED"` to see only templates ready to use.

**Permissions:** Requires "Gerenciar Modelos" permission.

**Parameters:**

| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| `integration_id` | number | no | — | Filter by WhatsApp Cloud integration ID. Example: `4` |
| `status` | string | no | — | Filter by template status: `APPROVED`, `MISSING_VARS`, `REJECTED`, or `PENDING` |
| `offset` | number | no | 1 | Page number |
| `limit` | number | no | 20 | Results per page (max 200) |

**Returns:** Compact list — one line per template with name, status, language, category, Meta template ID, and a short preview (~70 chars) of the body text. `header`/`footer` are omitted from the list row to keep it scannable and the payload bounded on full pages.

```
**Templates WhatsApp Cloud (2)**

- **integracao_teste** (ID 1) · APPROVED · pt_BR · CATEGORY · tpl `bvwzyawdpyrtibavxvnrzcvswxqbltst` — _teste idioma_
```

**Example:**
```json
{
  "status": "APPROVED",
  "limit": 50
}
```

---

### list_entities
List custom field groups (entities) available in the Tiflux organization. Use to discover which custom field groups exist, which applications they apply to (`ticket`, `client`, etc.), and their IDs — required for `list_entity_fields`.

**Parameters:**

| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| `active` | boolean | no | — | Filter active (`true`) or inactive (`false`) entities |
| `applied_in` | string | no | — | Filter by application: `"ticket"`, `"client"`, `"solicitant"`, `"services_catalog"`, `"services_catalogs_area"`, `"services_catalogs_item"`, `"equipment"` |
| `name` | string | no | — | Filter by entity name (partial match) |
| `limit` | number | no | 20 | Results per page (max: 200) |
| `offset` | number | no | 1 | Page number |

**Returns:** Markdown table with `id`, `name`, `applied_in`, `active`.

**Example:**
```json
{
  "applied_in": "ticket"
}
```

**Example response:**
```
| ID | Nome | Applied In | Ativa |
|---|---|---|---|
| 10 | Classificação do Chamado | ticket | Sim |
| 11 | Dados do Atendimento | ticket | Sim |
```

### list_entity_fields
List subfields (entity_fields) of a custom field group in Tiflux. Returns name, type, required status, and indicates which fields have selectable options (single_select/checkbox) — use `list_entity_field_options` in those cases.

**Parameters:**

| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| `entity_id` | number | yes | — | ID of the custom field group (entity). Obtain via `list_entities` |
| `field_type` | string | no | — | Filter by type: `"text"`, `"text_area"`, `"currency"`, `"phone"`, `"email"`, `"link"`, `"date"`, `"single_select"`, `"checkbox"` |
| `required` | boolean | no | — | Filter required (`true`) or optional (`false`) fields |
| `name` | string | no | — | Filter by field name |
| `limit` | number | no | 20 | Results per page (max: 200) |
| `offset` | number | no | 1 | Page number |

**Returns:** Markdown table with `id`, `name`, `field_type`, `required`, and `options` hint for single_select/checkbox fields.

**Example:**
```json
{
  "entity_id": 10,
  "field_type": "checkbox"
}
```

**Example response:**
```
| ID | Nome | Tipo | Obrigatorio | Opcoes |
|---|---|---|---|---|
| 81 | Categoria do Impacto | checkbox | Nao | Sim (use list_entity_field_options) |
```

### list_entity_field_options
List options of a custom subfield (entity_field) of type `single_select` or `checkbox`. Use to get option IDs (`entity_field_option_id`) required when filling multiple-choice fields via `update_ticket_entities`.

**Parameters:**

| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| `entity_field_id` | number | yes | — | ID of the subfield (entity_field). Obtain via `list_entity_fields` |
| `value` | string | no | — | Filter options by value/text (partial match) |
| `limit` | number | no | 20 | Results per page (max: 200) |
| `offset` | number | no | 1 | Page number |

**Returns:** Markdown table with option `id`, `value`, `null_option`.

**Example:**
```json
{
  "entity_field_id": 81
}
```

**Example response:**
```
| ID | Valor | Opcao nula |
|---|---|---|
| 11 | Hardware | Nao |
| 12 | Software | Nao |
| 13 | Rede | Nao |
| 14 | (nenhuma) | Sim |
```

### Writing custom fields (create/update)

The 6 tools below write to the organization's custom field structure (entities, entity_fields and their options). Read this before using any of them:

- **Prerequisites:** all 6 require the **`manage_entities`** role on the API key's user permission group (403 otherwise). `create_entity` with `applied_in` in `ticket`, `equipment`, `services_catalog`, `services_catalogs_area` or `services_catalogs_item` additionally requires a **Tickets license** on the user (403 with a message citing `applied_in` otherwise). Organizations without the newer catalog custom-fields format enabled get an error citing "cannot create entities applied in catalogs" — there's no way to enable this via API; contact TiFlux support.
- **Confirm before executing:** all 6 tools change structure that affects ticket/catalog/client forms for the entire organization, not a single record — the assistant should confirm with the user before calling them, summarizing what will be created/changed. Extra confirmation is warranted for `update_entity` with `active: false`, `update_entity_field` with `required: true`, and `create_entity` targeting `services_catalog`/`services_catalogs_area` (broader reach than an item).
- **No delete in the API v2:** there is no DELETE for entity, entity_field or entity_field_option at any level. Corrections go through the `update_*` tools (rename, `required`, `active`) or the portal. The reversible way to "undo" a group created by mistake is `update_entity { active: false }` (re-enable with `active: true`).
- **Immutable after creation:** `applied_in`, the catalog link (`services_catalog_id`/`services_catalogs_area_id`/`services_catalogs_item_id`) and `field_type` cannot be changed after creation (the API returns 400 `found unpermitted parameter` if sent to an update tool). To change any of these, create a new entity/field and inactivate/replace the old one.

#### create_entity
Create a custom field group (entity) in the organization, optionally linked to a services catalog, area or item, to desks (ticket) or to an equipment type. First step of building a custom field — follow with `create_entity_field`.

**Parameters:**

| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| `name` | string | yes | — | Name of the custom field group |
| `applied_in` | string | yes | — | `"ticket"`, `"equipment"`, `"client"`, `"solicitant"`, `"services_catalog"`, `"services_catalogs_area"`, `"services_catalogs_item"` |
| `description` | string | no | — | Description of the group |
| `menu_item` | boolean | no | — | Whether the field is a menu item. Forced to `false` by the API when `applied_in` is `solicitant` or any catalog level |
| `desk_ids` | number[] | no | all desks | Only valid with `applied_in="ticket"` (local error otherwise) |
| `equipment_type_id` | number | no | all types | Only valid with `applied_in="equipment"` (local error otherwise) |
| `services_catalog_id` / `services_catalog_name` | number / string | conditional | — | Required (one of them) when `applied_in="services_catalog"`. ID takes precedence over name |
| `services_catalogs_area_id` / `area_name` | number / string | conditional | — | Required (one of them, `area_name` needs a catalog too) when `applied_in="services_catalogs_area"` |
| `services_catalogs_item_id` | number | conditional | — | Required when `applied_in="services_catalogs_item"`. Obtain via `search_catalog_item` or `list_services_catalog_items` — name resolution is not supported for items |

Link parameters passed with an incompatible `applied_in` are rejected locally (no API call): `services_catalog_id`/`services_catalog_name` only with `services_catalog` or `services_catalogs_area` (as context for `area_name`), `services_catalogs_area_id`/`area_name` only with `services_catalogs_area`, `services_catalogs_item_id` only with `services_catalogs_item`.

Only 1 active entity can exist per catalog/area/item — a second attempt returns 422 on the link field; use `list_entities applied_in=<...>` to find the existing one.

**Example:**
```json
{
  "name": "Dados do pedido",
  "applied_in": "services_catalogs_item",
  "services_catalogs_item_id": 123
}
```

**Returns:** ID, name, `applied_in`, resolved link (catalog/area/item names), desk IDs, and a hint to use `create_entity_field` next.

#### update_entity
Partially update an existing custom field group (entity) — only the informed fields are changed. Also used to activate/inactivate the group (`active`).

**Parameters:**

| Parameter | Type | Required | Description |
|---|---|---|---|
| `id` | number | yes | ID of the entity to update |
| `name` | string | no | New name |
| `description` | string | no | New description |
| `active` | boolean | no | Activate (`true`) or inactivate (`false`) the group. `false` hides all its subfields from forms |
| `menu_item` | boolean | no | Whether the field is a menu item. Cannot be changed on entities with `applied_in="solicitant"` or a catalog `applied_in` (API returns 422) |
| `desk_ids` | number[] | no | Only meaningful on entities with `applied_in="ticket"` |
| `equipment_type_id` | number | no | Only meaningful on entities with `applied_in="equipment"` |

At least one updatable field besides `id` is required. `applied_in` and the catalog link cannot be changed (see note above).

**Example:**
```json
{ "id": 10, "active": false }
```

**Returns:** confirmation with the updated fields and the current state (name, `active`, desk IDs). When `active: false` is sent, the response warns that subfields disappear from forms and shows how to reactivate.

#### create_entity_field
Create a subfield (entity_field) inside an existing custom field group (entity). For `field_type` `single_select`/`checkbox`, accepts inline `options[]` — recommended, since it avoids the "Padrão" placeholder and returns the option IDs in the same response.

**Parameters:**

| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| `entity_id` | number | yes | — | ID of the parent entity |
| `name` | string | yes | — | Name of the subfield |
| `field_type` | string | yes | — | `"email"`, `"date"`, `"currency"`, `"phone"`, `"checkbox"`, `"text"`, `"text_area"`, `"single_select"`, `"link"`. Immutable after creation |
| `required` | boolean | no | `false` | Whether filling it in is mandatory |
| `options` | array of `{value}` | no | — | Only for `field_type` `"single_select"`/`"checkbox"` (local error otherwise). Duplicate values (case-insensitive, trimmed) are rejected locally before calling the API |

If `field_type` is `single_select`/`checkbox` and `options` is omitted, the API auto-creates a single "Padrão" option (`null_option: true`, no real value) — use `create_entity_field_option` afterwards to add real options.

**Example:**
```json
{
  "entity_id": 10,
  "name": "Novo campo do tipo seleção única",
  "field_type": "single_select",
  "options": [{ "value": "Opção 1" }, { "value": "Opção 2" }]
}
```

**Returns:** ID, name, `field_type`, `required`, and (for single_select/checkbox) the created `options[]` with their IDs — flags when only the "Padrão" placeholder exists.

#### update_entity_field
Partially update an existing subfield (entity_field) — rename it and/or change `required`. `field_type` is immutable (create a new subfield to change the type).

**Parameters:**

| Parameter | Type | Required | Description |
|---|---|---|---|
| `entity_id` | number | yes | ID of the parent entity |
| `id` | number | yes | ID of the subfield to update |
| `name` | string | no | New name |
| `required` | boolean | no | `true` blocks opening/closing tickets for whoever uses the linked desk/catalog without filling it in |

At least one of `name`/`required` is required.

**Example:**
```json
{ "entity_id": 10, "id": 44101, "required": true }
```

**Returns:** confirmation with the updated fields and current state (name, `field_type`, `required`, `index`). Warns about the impact when `required` becomes `true`.

#### create_entity_field_option
Add an option to an existing `single_select`/`checkbox` subfield (entity_field) — e.g. to replace the "Padrão" placeholder or add more choices.

**Parameters:**

| Parameter | Type | Required | Description |
|---|---|---|---|
| `entity_field_id` | number | yes | ID of the subfield where the option is created |
| `value` | string | yes | Value of the option |

**Example:**
```json
{ "entity_field_id": 44101, "value": "Opção C" }
```

**Returns:** the created option's `id` and `value`.

#### update_entity_field_option
Rename (update the value of) an existing option of a `single_select`/`checkbox` subfield.

**Parameters:**

| Parameter | Type | Required | Description |
|---|---|---|---|
| `entity_field_id` | number | yes | ID of the subfield that owns the option |
| `id` | number | yes | ID of the option to update |
| `value` | string | yes | New value |

**Example:**
```json
{ "entity_field_id": 44101, "id": 501, "value": "Novo valor da opção" }
```

**Returns:** the updated option's `id` and `value`.

## Search Heuristics — Mesa-First

When a user references a name without explicitly qualifying the entity type, the following priority applies:

| User input | Filter to use | Reason |
|---|---|---|
| "tickets do tuitui" / "do Suporte" (name that matches a desk) | `desk_name="tuitui"` | Desk/team name = desk |
| "tickets da mesa X" / "equipe Y" | `desk_name` | "mesa" / "equipe" = desk |
| "tickets do cliente Z" / "empresa ACME" | `client_name` | "cliente" / "empresa" = company |
| "tickets do João" / "abertos pelo João" (person name) | `requestor_email` or `requestor_ids` | Person = requestor |
| "tickets atribuídos ao João" / "com o João" | `responsible_name="João"` (or `responsible_ids` if you have the ID) | "atribuído a" = responsible — `responsible_name` resolves automatically for both admin and non-admin |
| "tickets aberto por joao@empresa.com" | `requestor_email` | Email = requestor |
| Ambiguous / uncertain (desk or person?) | Ask the user | Visible failure > filtering by wrong entity |
| (create_ticket) "solicitante Fulano" | `requestor_name="Fulano"` — MCP auto-resolves to `requestor_id` | Avoids ghost requestor duplicate |

This heuristic is embedded in the `description` fields of `list_tickets`, `create_ticket`, and `update_ticket` schemas. The LLM reads these on every tool call decision.

## Smart Name Resolution

When using `desk_name` in any tool, the MCP server performs a two-step lookup:

1. **Direct search:** `GET /desks?active=true&name={desk_name}` — fast, uses the API's built-in filter.
2. **Fuzzy fallback (automatic):** If the direct search returns no results, the server fetches **all** active desks (paginated, up to 200 per page) and applies client-side fuzzy matching with tokenization and normalization (trim, lowercase, accent-insensitive). Works correctly regardless of how many desks the organization has. This handles common patterns like:
   - **Partial name:** `"cansados"` resolves to `"Dev - Cansados"`
   - **Accent-insensitive:** `"comunicacao"` resolves to `"Comunicação"`
   - **Token match:** `"premium"` resolves to `"Dev - Premium"`
   - **Multi-word (tokens in any order, separator-insensitive):** `"dev experimentos"` resolves to `"DEV - Experimentos"`

The fallback returns only the **highest-scoring group** of matches — so single-match terms resolve immediately, while ambiguous terms (multiple desks at equal score) return a disambiguation list.

**Behavior:**
- If exactly **1 desk** matches → auto-resolved, request proceeds normally.
- If **multiple desks** match at the same score → returns a list so you can be more specific or use `desk_id` directly.
- If **no match** → returns a clear error message.

**Fixed in v2.50.1 — exact name wins over a noisier partial match:** the direct search (step 1 above) matches `name` **or** `display_name` as a substring (e.g. `desk_name="Suporte"` also matches a desk named `"Suporte N2"`), which used to make a perfectly exact name ambiguous whenever a similarly-named desk existed. Now, when the direct search returns more than 1 desk, the MCP checks for an exact match (case/accent-insensitive) against `name` or `display_name`: if **exactly one** candidate matches exactly, that one is used directly — no disambiguation needed. If 0 or 2+ candidates match exactly, the behavior is unchanged (the full candidate list is returned for disambiguation).

This applies to: `create_ticket`, `update_ticket`, `list_tickets`, `search_stage`, `search_catalog_item`, `get_desk`, `list_desk_priorities`, and `list_desk_services_catalogs`.

**Appointment valorization resolution** (`create_appointment`): three additional name parameters resolve valorization IDs scoped to the ticket itself — no cross-organization ambiguity:
- `contract_name` → resolves to `contract_rider_id` (fuzzy match over `contract_riders[].contract.name` — returns the **rider ID**, not the contract ID)
- `loose_service_name` → resolves to `loose_service_id` (fuzzy match over available loose services)
- `shift_name` → resolves to `shift_id` (fuzzy match over available travel shifts for the ticket)

Resolution order is **contract → loose service → shift**: the contract is resolved first (when given by name or ID) so that its `contract.id` can filter shift candidates before the shift's own fuzzy match — this matters because the same client can have several contract renewals sharing the exact same shift name (e.g. three "Joinville" shifts under three different contracts), and only the contract filter tells them apart. When `attendance_kind=2` and the contract is known, only shifts of that same contract (or contract-less/generic ones) are considered; when `attendance_kind=1`, only contract-less shifts are considered — mirroring what the API itself accepts.

All three apply a stricter-than-generic-fuzzy resolution: a candidate only counts with a match score of 70+ (the term must match the start of a word in the name — a loose substring in the middle no longer resolves, closing a silent-wrong-match risk). 0 qualifying candidates → error (with up to 5 close-but-below-threshold suggestions, if any) and a pointer to the corresponding `get_ticket_*` tool; 1 candidate → resolved; 2+ candidates → resolved **only** when exactly one is an exact name match (score 100), otherwise a disambiguation list with IDs (shift ambiguity lines include the shift's scope and contract; contract ambiguity lines include the contract ID and its validity period). When both the ID field and the name field are given, the ID takes precedence and no extra API call is made.

The confirmation of a successfully created appointment (`201`) echoes back whichever contract/loose service/shift were actually used — the resolved name and ID when resolved by name, just the ID when passed directly by ID, and nothing when that field wasn't used. No extra API call is made for this — it only reflects what was already resolved in-memory before the write.

**Services catalog resolution** (all `*_services_catalog*` and `*_services_catalog_item*` tools): two additional name parameters resolve catalog and area IDs server-side using the API's built-in `ilike` filter (no client-side fuzzy):
- `services_catalog_name` → resolves to `services_catalog_id` via `GET /services-catalogs?name={value}&limit=50`
- `area_name` → resolves to `services_catalogs_area_id` via `GET /services-catalogs/{catalog_id}/areas?name={value}&limit=50`

Both apply the same 0/1/N behavior: 0 matches → error; 1 match → resolved; N matches → disambiguation list with IDs. When both the ID field and the name field are given, the ID takes precedence.

---

### get_billings_history
Returns the organization's billing history. Filters are all optional: billing period (`billing_start_date` + `billing_end_date`, mandatory in pair), due date period (`due_start_date` + `due_end_date`, mandatory in pair), client by ID (`client_id`) or name with fuzzy resolver (`client_name`), NFe number (`nfe_number`), ticket number (`ticket_number`), and billing status (`type`). The response has 7 fields per billing (ID, Client, Billing date, Due date, NFe, Status and Value) — a markdown table in `rich`, `|`-delimited rows (with a header line) in `compact` — plus the sum of `real_value` on the page EXCLUDING reversals (`reversal: true`), with a note of what was excluded. The endpoint returns only the record count (`X-Total-Items`), never a value total for the filter. If the page exceeds ~40k characters it is cut at a row boundary with the exact `offset`/`limit` to continue, and the page sum then covers only the rows shown (so continuation sums add up without double counting).

**Permissions:** Requires "Faturar serviços avulsos e contratos" + Tickets license. Returns **403** for users without billing permission.

**Parameters:**

| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| `billing_start_date` | string | no (pair) | — | Start of billing period (`YYYY-MM-DD`). Must be used with `billing_end_date`. |
| `billing_end_date` | string | no (pair) | — | End of billing period (`YYYY-MM-DD`). Must be used with `billing_start_date`. |
| `due_start_date` | string | no (pair) | — | Start of due date period (`YYYY-MM-DD`). Must be used with `due_end_date`. |
| `due_end_date` | string | no (pair) | — | End of due date period (`YYYY-MM-DD`). Must be used with `due_start_date`. |
| `client_id` | number | no | — | Filter by client ID. Takes precedence over `client_name`. |
| `client_name` | string | no | — | Client name (partial or exact) for fuzzy auto-resolution. Ignored when `client_id` is provided. |
| `nfe_number` | number | no | — | Filter by NFe number. |
| `ticket_number` | number | no | — | Filter by ticket number associated with the billing. |
| `type` | string | no | — | Billing status: `billed`, `reversed`, or `paid`. Omit for all statuses. |
| `offset` | number | no | 1 | Page number. |
| `limit` | number | no | 20 | Results per page (max 200). |

**Returns:** Markdown table with 7 columns — ID, Cliente, Data faturamento, Vencimento, NFe, Situação, Valor — plus a page sum row (`Soma desta página (sem estornos)`) and pagination footer. Situação is derived: `reversal=true` → Estornado; `paid=true` → Pago; both false → Faturado. Monetary values formatted as `R$ X.XXX,XX`.

> **Page sum semantics:** `Soma desta página (sem estornos)` adds up `real_value` for the rows on the current page **excluding reversed billings** (`reversal=true`). A reversal is an in-place `UPDATE` on the billing record — the API returns its `real_value` as a **positive** number and there is no offsetting entry, so summing it would inflate the total and subtracting it would double-count (the reversed work can later be re-billed under a new `billing_id`). The product convention (internal report and native screen) is to **filter, not subtract**. When the page contains reversals, a note lists how many were excluded and their summed value. Note: `type: "paid"` does **not** exclude reversals (it filters by financial-integration status only) — use `type: "billed"` for a set with no reversals. The endpoint returns no monetary total for the filter, only the record count via the `X-Total-Items` header.

**Example:**
```json
{
  "billing_start_date": "2024-10-01",
  "billing_end_date": "2024-10-31",
  "type": "billed"
}
```

**Example response:**
```
**Faturamentos (1)**

| ID | Cliente | Data faturamento | Vencimento | NFe | Situação | Valor |
|---|---|---|---|---|---|---|
| 2 | Zemlak-Cremin | 2024-10-10 | 2024-10-15 | 4310034 | Faturado | R$ 755,90 |

**Soma desta página (sem estornos):** R$ 755,90
```

## Services Catalogs Tools

Manage the three-level catalog hierarchy: **catalog → area → item**. Catalogs and areas are containers; items are the SLA-bearing leaves that can be selected when creating tickets. All write operations require the **`service_catalogs_manage`** role ("Gerenciar catálogos de serviço").

> **No `get_*` shortcut exists.** The API does not expose `GET /services-catalogs/{id}`, `GET /services-catalogs-areas/{id}`, or `GET /services-catalogs-areas/{id}/items/{id}`. To retrieve details of a specific catalog, area, or item, use the corresponding `list_*` tool with the `name` filter.

> **Smart name resolution:** `services_catalog_name` and `area_name` are resolved server-side (API `ilike` filter, no fuzzy fallback). See [Smart Name Resolution](#smart-name-resolution).

### list_services_catalogs
List all service catalogs in the organization (org-wide configuration view). Use the `name` filter to locate a specific catalog.

**Different from** `list_desk_services_catalogs` (catalogs linked to one desk) and `search_catalog_item` (items selectable in tickets of a desk).

**Parameters:**

| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| `name` | string | no | — | Filter by name (partial, case/accent-insensitive, server-side) |
| `offset` | number | no | 1 | Page number |
| `limit` | number | no | 20 | Results per page (max 200) |

**Returns:** List of catalogs with `id` and `name`. Header `X-Total-Items` for total count.

**Example:**
```json
{ "name": "Infra" }
```

### create_services_catalog
Create a new service catalog. The name must be unique across the organization (uniqueness validated only on create).

**Permissions:** Requires `service_catalogs_manage`.

**Parameters:**

| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| `name` | string | yes | — | Catalog name (unique org-wide) |

**Returns:** Created catalog with `id` and `name`.

**Example:**
```json
{ "name": "Infraestrutura" }
```

### update_services_catalog
Update the name of an existing service catalog.

**Permissions:** Requires `service_catalogs_manage`. Note: uniqueness is validated only on create — the update accepts duplicate names without error (API v2 behavior).

**Parameters:**

| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| `id` | number | yes | — | Catalog ID |
| `name` | string | yes | — | New catalog name |

**Example:**
```json
{ "id": 5, "name": "Infraestrutura TI" }
```

### delete_services_catalog
Remove (soft delete) a service catalog and **all** its areas and items in cascade.

**Permissions:** Requires `service_catalogs_manage`.

**Warning:** The deletion cascade silently deactivates all areas, items, and recurring activities that reference those items — including items currently in use by tickets. The pre-flight count is **informational only, not a gate**: it runs before the DELETE (afterwards the records are already inactive and no longer countable) and its result is only appended to the success message. There is no `confirm` / `dry_run` parameter — the DELETE always proceeds, even if the pre-flight fails.

**Parameters:**

| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| `id` | number | yes | — | Catalog ID to delete |

**Example:**
```json
{ "id": 5 }
```

### list_services_catalog_areas
List active areas belonging to a service catalog. Use `services_catalog_id` (direct) or `services_catalog_name` (auto-resolved). Use the `name` filter to locate a specific area.

**Parameters:**

| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| `services_catalog_id` | number | one of | — | Catalog ID (takes precedence over `services_catalog_name`) |
| `services_catalog_name` | string | one of | — | Catalog name for auto-resolution |
| `name` | string | no | — | Filter areas by name (partial, server-side) |
| `offset` | number | no | 1 | Page number |
| `limit` | number | no | 20 | Results per page (max 200) |

**Returns:** List of areas with `id`, `name`, and parent catalog name.

**Example:**
```json
{ "services_catalog_name": "Infraestrutura", "name": "Redes" }
```

### create_services_catalog_area
Create a new area inside a service catalog.

**Permissions:** Requires `service_catalogs_manage`.

**Parameters:**

| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| `services_catalog_id` | number | one of | — | Catalog ID (takes precedence over `services_catalog_name`) |
| `services_catalog_name` | string | one of | — | Catalog name for auto-resolution |
| `name` | string | yes | — | Area name (unique per catalog) |

**Example:**
```json
{ "services_catalog_name": "Infraestrutura", "name": "Servidores" }
```

### update_services_catalog_area
Update the name of an area inside a service catalog.

**Permissions:** Requires `service_catalogs_manage`.

**Parameters:**

| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| `services_catalog_id` | number | one of | — | Catalog ID (takes precedence over `services_catalog_name`) |
| `services_catalog_name` | string | one of | — | Catalog name for auto-resolution |
| `id` | number | yes | — | Area ID |
| `name` | string | yes | — | New area name |

**Example:**
```json
{ "services_catalog_id": 1, "id": 10, "name": "Servidores Linux" }
```

### delete_services_catalog_area
Remove (soft delete) an area and **all** its items in cascade.

**Permissions:** Requires `service_catalogs_manage`.

**Warning:** All items in the area are deactivated. The pre-flight count is **informational only, not a gate** — it runs before the DELETE just to report the number of affected items; there is no confirmation step and the DELETE always proceeds.

**Parameters:**

| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| `services_catalog_id` | number | one of | — | Catalog ID (takes precedence over `services_catalog_name`) |
| `services_catalog_name` | string | one of | — | Catalog name for auto-resolution |
| `id` | number | yes | — | Area ID to delete |

**Example:**
```json
{ "services_catalog_id": 1, "id": 10 }
```

### list_services_catalog_items
List active items in a service catalog area. Use `services_catalogs_area_id` (direct) or the combination `area_name` + `services_catalog_id`/`services_catalog_name`.

**Different from** `search_catalog_item` which is desk-scoped (items selectable in tickets of a specific desk).

**Parameters:**

| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| `services_catalogs_area_id` | number | one of | — | Area ID (takes precedence over `area_name`) |
| `area_name` | string | one of | — | Area name for auto-resolution (requires catalog id or name) |
| `services_catalog_id` | number | — | — | Catalog ID (used in area resolution; takes precedence over `services_catalog_name`) |
| `services_catalog_name` | string | — | — | Catalog name for auto-resolution |
| `name` | string | no | — | Filter items by name (partial, server-side) |
| `offset` | number | no | 1 | Page number |
| `limit` | number | no | 20 | Results per page (max 200) |

**Returns:** List of items with `id`, `name`, parent area/catalog names, `start_time` (SLA atendimento), `end_time` (SLA solução).

**Example:**
```json
{ "area_name": "Redes", "services_catalog_name": "Infraestrutura" }
```

### create_services_catalog_item
Create a new item in a service catalog area.

**Permissions:** Requires `service_catalogs_manage`.

**SLA fields:** `start_time` = attendance deadline (SLA de atendimento); `end_time` = solution deadline (SLA de solução). Format `"HH:MM"` with hours 0–999 (e.g. `"120:30"` = 120 h 30 min). `end_time` must be >= `start_time`. Both fields are required by the API (the Swagger declares `required: []` but the model enforces them).

**Parameters:**

| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| `services_catalogs_area_id` | number | one of | — | Area ID (takes precedence over `area_name`) |
| `area_name` | string | one of | — | Area name for auto-resolution |
| `services_catalog_id` | number | — | — | Catalog ID (used in area resolution) |
| `services_catalog_name` | string | — | — | Catalog name for auto-resolution |
| `name` | string | yes | — | Item name |
| `start_time` | string | yes | — | SLA de atendimento (`HH:MM`, hours 0-999) |
| `end_time` | string | yes | — | SLA de solução (`HH:MM`, hours 0-999, >= `start_time`) |

**Example:**
```json
{
  "services_catalogs_area_id": 10,
  "name": "Troca de Switch",
  "start_time": "08:00",
  "end_time": "24:00"
}
```

### update_services_catalog_item
Update an item in a service catalog area (partial update — only provided fields are sent).

**Permissions:** Requires `service_catalogs_manage`.

At least one of `name`, `start_time`, or `end_time` must be provided. SLA format and `end_time >= start_time` constraint apply when both time fields are supplied.

**Parameters:**

| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| `services_catalogs_area_id` | number | one of | — | Area ID (takes precedence over `area_name`) |
| `area_name` | string | one of | — | Area name for auto-resolution |
| `services_catalog_id` | number | — | — | Catalog ID (used in area resolution) |
| `services_catalog_name` | string | — | — | Catalog name for auto-resolution |
| `id` | number | yes | — | Item ID |
| `name` | string | no | — | New item name |
| `start_time` | string | no | — | New SLA de atendimento (`HH:MM`) |
| `end_time` | string | no | — | New SLA de solução (`HH:MM`, >= `start_time`) |

**Example:**
```json
{ "services_catalogs_area_id": 10, "id": 50, "end_time": "48:00" }
```

### delete_services_catalog_item
Remove (soft delete) a service catalog item. No cascade — items are leaf nodes.

**Permissions:** Requires `service_catalogs_manage`.

**Warning:** An item in use by tickets is silently deactivated (no blocking, no confirmation prompt). There is no pre-flight count for items.

**Parameters:**

| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| `services_catalogs_area_id` | number | one of | — | Area ID (takes precedence over `area_name`) |
| `area_name` | string | one of | — | Area name for auto-resolution |
| `services_catalog_id` | number | — | — | Catalog ID (used in area resolution) |
| `services_catalog_name` | string | — | — | Catalog name for auto-resolution |
| `id` | number | yes | — | Item ID to delete |

**Example:**
```json
{ "services_catalogs_area_id": 10, "id": 50 }
```

## Organization Groups Tools

Read-only tools for inspecting the organization's structure: attendant groups (technical groups — SLA expedients, linked desks/clients/users) and permission groups (role groups — which permissions they grant, their members, and which attendant groups they're linked to). Use `list_technical_groups`/`list_role_groups` to discover IDs — the API does not support filtering these endpoints by name — then drill down with the `get_*`/`list_*_*` tools below. The `technical_group_id` discovered here also feeds `technical_group_id`/`technical_group_name` in `create_user`/`update_user` and `technical_group_ids` in `create_knowledge`/`update_knowledge`.

### list_technical_groups
List the organization's attendant groups (technical groups): ID and name. The API does **not** accept a name/search filter on this endpoint — only pagination (`offset`/`limit`); to find a specific group, paginate (up to `limit: 200`) and look for the name in the response. Requires the "Gerenciar mesas de serviços" permission.

**Parameters:**
- `offset` (number, optional): Page number (default: 1)
- `limit` (number, optional): Results per page (default: 20, max: 200)

**Returns:** Markdown list with `id` and `name` per group.

**Example:**
```json
{ "limit": 50 }
```

### get_technical_group
Get the detail of an attendant group by ID: name, description, whether it has an attendance compromise (`has_compromise`), periodic appointment-hours email setting, and expedients (start/end time, weekdays, holiday flag). Pass `show_departments: true` to also include the linked departments (id, name) — off by default. `technical_group_id` must come from a `list_technical_groups` row (the API does not accept name search on either endpoint).

**Parameters:**
- `technical_group_id` (number, required): Attendant group ID (from `list_technical_groups`)
- `show_departments` (boolean, optional): Include linked departments. Default: `false`

**Returns:** Markdown with description (truncated at 800 chars), compromise flag, periodic-hours setting, expedients list and, when requested, the departments list.

**Example:**
```json
{ "technical_group_id": 219, "show_departments": true }
```

### list_technical_group_desks
List the desks (service queues) linked to an attendant group. Requires the "Gerenciar mesas de serviços" permission.

**Parameters:**
- `technical_group_id` (number, required): Attendant group ID (from `list_technical_groups`)
- `offset` / `limit` (number, optional): Pagination (default 20, max 200)

**Returns:** Markdown list with `id`, internal name, display name, active status and appointment type per desk.

**Example:**
```json
{ "technical_group_id": 10 }
```

### list_technical_group_clients
List the clients linked to an attendant group. Requires the "Gerenciar mesas de serviços" permission.

**Parameters:**
- `technical_group_id` (number, required): Attendant group ID (from `list_technical_groups`)
- `offset` / `limit` (number, optional): Pagination (default 20, max 200)

**Returns:** Markdown list with `id`, name, corporate name (`social`), CNPJ (`social_revenue`) and active status per client.

**Example:**
```json
{ "technical_group_id": 10 }
```

### list_technical_group_users
List the users/attendants that belong to an attendant group. This endpoint was already used internally by `search_user`'s non-admin fallback, but had no dedicated tool until now. Requires the "Gerenciar mesas de serviços" permission.

**Parameters:**
- `technical_group_id` (number, required): Attendant group ID (from `list_technical_groups`)
- `offset` / `limit` (number, optional): Pagination (default 20, max 200)

**Returns:** Markdown list with name, e-mail, type (Cliente/Atendente/Administrador), active status, last login (`Nunca` when `null`) and 2FA status per user.

**Example:**
```json
{ "technical_group_id": 10 }
```

### list_role_groups
List the organization's permission groups (role groups): ID, name and whether the group can be deleted (`deletable` — default organization groups, such as "Administrador" and "Clientes", cannot). The API does **not** accept a name/search filter — only pagination. Requires the "Gerenciar grupos de permissão" permission.

**Parameters:**
- `offset` / `limit` (number, optional): Pagination (default 20, max 200)

**Returns:** Markdown list with `id`, name and whether the group is deletable.

**Example:**
```json
{ "limit": 50 }
```

### get_role_group
Get the detail of a permission group by ID: name, description, whether it's deletable, and the `roles[]` it grants (id, name, description of each permission). `role_group_id` must come from a `list_role_groups` row.

**Parameters:**
- `role_group_id` (number, required): Permission group ID (from `list_role_groups`)

**Returns:** Markdown with description, deletable flag and the list of granted permissions (description truncated at 200 chars; omitted entirely in compact verbosity).

**Example:**
```json
{ "role_group_id": 116 }
```

### list_role_group_users
List the users that belong to a permission group. Optional `email` filter (up to 255 chars). **Note:** the Swagger incorrectly documents the item's `id` as "ID do grupo de permissões" — it is actually the **user**'s ID, as the endpoint's own example shows. Requires the "Gerenciar grupos de permissão" permission.

**Parameters:**
- `role_group_id` (number, required): Permission group ID (from `list_role_groups`)
- `email` (string, optional, max 255 chars): Filter users by e-mail
- `offset` / `limit` (number, optional): Pagination (default 20, max 200)

**Returns:** Markdown list with name, e-mail, type (Cliente/Atendente/Administrador), active status, last login, 2FA status and the user's attendant group (`technical_group_id`).

**Example:**
```json
{ "role_group_id": 116, "email": "ana@example.com" }
```

### list_role_group_technical_groups
List the attendant groups (technical groups) linked to a permission group.

**Parameters:**
- `role_group_id` (number, required): Permission group ID (from `list_role_groups`)
- `offset` / `limit` (number, optional): Pagination (default 20, max 200)

**Returns:** Markdown list with `id` and `name` per attendant group.

**Example:**
```json
{ "role_group_id": 116 }
```

## API Endpoints Used

The MCP server integrates with the following Tiflux API v2 endpoints:

- `GET /tickets/{id}` - Retrieve ticket details
- `POST /tickets` - Create new tickets (supports multipart with file attachments via `files_base64`; `requestor_id` body field links existing requestor)
- `PUT /tickets/{id}` - Update existing tickets (supports `requestor_id` to change the ticket's requestor; `followers` replaces the full list)
- `PUT /tickets/{id}/entities` - Update ticket custom fields
- `PUT /tickets/{ticket_number}/cancel` - Cancel specific ticket
- `PUT /tickets/{ticket_number}/close` - Close specific ticket
- `PUT /tickets/{ticket_number}/reopen` - Reopen closed or canceled ticket (supports optional `disapproval_reason`)
- `POST /tickets/{ticket_number}/answers` - Create ticket answer (client communication)
- `GET /tickets/{ticket_number}/answers` - List ticket answers (client communications), paginated
- `GET /tickets/{ticket_number}/answers/{id}` - Get specific ticket answer with attached files
- `DELETE /tickets/{ticket_number}/answers/{id}` - Remove a ticket answer (`delete_ticket_answer`)
- `POST /tickets/{ticket_number}/client-answers` - Create an answer on behalf of the client (`create_client_answer`). Multipart: `name`, `author_name`, `files[]` (up to 10, 25MB each). Administrators only.
- `DELETE /ticket_answers/{ticket_answer_id}/files/{id}` - Remove a file from a ticket answer (`delete_ticket_answer_file`)
- `GET /tickets/{ticket_number}/histories` - Get ticket event history (timeline) with optional filters
- `GET /tickets` - List tickets with filters (supports `requestor_ids`, `requestor_email`, `services_catalogs_item_ids`, `priority_ids` query params; response includes `services_catalog` and `priority` per ticket). Also supports `group_by` (values: day/week/month/desk) for aggregated counts — returns `{ group_by, date_type, total, buckets: [{period, count}] }` instead of a ticket list. **Note:** `group_by` and the aggregated response shape are not documented in the public Swagger as of 2026-06-30; the feature was added in api_rails ticket #96694 and is live in production. Used internally by `get_tickets_comparison` (2 calls per invocation) and `list_tickets` (with `group_by` param). A documentation request has been registered with the API team.
- `GET /clients` - Search/list clients (`search_client`, `list_clients`, and `client_name` auto-resolve in `list_tickets` and `create_ticket`)
- `GET /clients/{id}` - Get client details (`get_client`)
- `POST /clients` - Create a new client (`create_client`)
- `PUT /clients/{id}` - Update client fields (`update_client`)
- `PUT /clients/{id}/entities` - Update client custom fields (`update_client_entities`)
- `GET /clients/{id}/desks` - List desks associated with a client (`get_client_desks`)
- `GET /clients/{id}/technical-groups` - List technical groups associated with a client (`get_client_technical_groups`)
- `POST /clients/{id}/users` - Create a portal user for a client (`create_client_user`)
- `POST /clients/{id}/email_tickets_permissions` - Add authorized email/domain for a client (`add_client_email_permission`)
- `GET /clients/{client_id}/addresses` - List addresses of a client (`list_client_addresses`)
- `POST /clients/{client_id}/addresses` - Create an address for a client (`create_client_address`)
- `GET /clients/{client_id}/addresses/{id}` - Get a specific address of a client (`get_client_address`)
- `PUT /clients/{client_id}/addresses/{id}` - Update an address of a client (`update_client_address`)
- `DELETE /clients/{client_id}/addresses/{id}` - Remove an address from a client (`delete_client_address`)
- `GET /clients/{client_id}/contacts` - List contacts of a client (`list_client_contacts`)
- `POST /clients/{client_id}/contacts` - Create a contact for a client (`create_client_contact`)
- `GET /clients/{client_id}/contacts/{id}` - Get a specific contact of a client (`get_client_contact`)
- `PUT /clients/{client_id}/contacts/{id}` - Update a contact of a client (`update_client_contact`)
- `DELETE /clients/{client_id}/contacts/{id}` - Remove a contact from a client (`delete_client_contact`)
- `GET /requestors` - Search requestors with server-side filtering (`search_requestor`, and `requestor_name`/`requestor_email` auto-resolve in `create_ticket` and `update_ticket`). Used as fallback when the client-scoped route returns 403 or when no `client_id` is available.
- `GET /clients/{client_id}/requestors` - Client-scoped requestor listing/search. Powers `list_requestors`. **Primary route** for `requestor_name`/`requestor_email` auto-resolve in `create_ticket` and `update_ticket` when `client_id` is known (scoped-first since v2.37.0, eliminates ticket #98515 class of bug). Falls back to global `GET /requestors` on 403.
- `GET /clients/{client_id}/requestors/{id}` - Get a single requestor of a client (`get_requestor`; `include_entity_fields` for custom fields).
- `POST /clients/{client_id}/requestors` - Create a requestor in a client (`create_requestor`).
- `PUT /clients/{client_id}/requestors/{id}` - Update a requestor (`update_requestor`, partial).
- `PUT /clients/{client_id}/requestors/{id}/entities` - Update a requestor's custom fields (`update_requestor_entities`).
- `DELETE /clients/{client_id}/requestors/{id}` - Delete a requestor of a client (`delete_requestor`). Irreversible. Requires "Gerenciar clientes e grupos de recursos".
- `POST /users` - Create a new user/agent (`create_user`). Admin-only — returns 403 for non-admin keys.
- `GET /users/{id}` - Get user details (`get_user`). Admin-only.
- `PUT /users/{id}` - Update user fields (`update_user`, partial). Admin-only.
- `PUT /users/profile` - Update the authenticated user's own profile: `name`, `extension` (integer), `telephone`, `country_code` (`update_my_profile`). The API also accepts `email`, but the MCP blocks e-mail changes.
- `GET /users` - Search users (used by `search_user`, `responsible_name` auto-resolve, and as level 3 of the `search_requestor` fallback chain — the matched user's email becomes `requestor_email`). Returns 403 for non-admin users — handled automatically by the fallback below.
- `GET /users/me` - Current authenticated user (used as the final level of the `search_requestor` chain — suggests opening the ticket as yourself via `requestor_email`).
- `GET /technical-users` - Search technical attendants with server-side filtering by name, email, desk_id, client_id (`search_technical_user`). **Does not require user management permission** — works for admin and non-admin. **Primary path** for `responsible_name` auto-resolve in `create_ticket`, `update_ticket`, `list_tickets`. Note: absent from the public swagger.json as of 2026-06-18 but live in production.
- `GET /technical-groups` - List attendant groups: `id`, `name` (`list_technical_groups`; also used by `search_user` non-admin fallback and as fallback for `responsible_name` resolution when `/technical-users` returns 404/403). No name/search filter — pagination only. Header `X-Total-Items`. Requires "Gerenciar mesas de serviços" permission.
- `GET /technical-groups/{id}` - Get the detail of an attendant group: description, compromise flag, periodic-hours setting, expedients and, with `show_departments=true`, linked departments (`get_technical_group`). No formal schema in the Swagger (only `example`). Requires "Gerenciar mesas de serviços" permission.
- `GET /technical-groups/{id}/desks` - List desks linked to an attendant group (`list_technical_group_desks`). Header `X-Total-Items`. Requires "Gerenciar mesas de serviços" permission.
- `GET /technical-groups/{id}/clients` - List clients linked to an attendant group (`list_technical_group_clients`). Header `X-Total-Items`. Requires "Gerenciar mesas de serviços" permission.
- `GET /technical-groups/{id}/users` - List users in an attendant group (`list_technical_group_users`; also the non-admin fallback for `search_user` — deduplicated, fuzzy-matched). Header `X-Total-Items`. Requires "Gerenciar mesas de serviços" permission.
- `GET /role-groups` - List the organization's permission groups: `id`, `name`, `deletable` (`list_role_groups`). No name/search filter — pagination only. Header `X-Total-Items`. Requires "Gerenciar grupos de permissão" permission.
- `GET /role-groups/{id}` - Get the detail of a permission group, including the `roles[]` it grants (`get_role_group`). Requires "Gerenciar grupos de permissão" permission.
- `GET /role-groups/{id}/users` - List users of a permission group, with optional `email` filter (`list_role_group_users`). **Note:** the Swagger's item schema mislabels `id` as the permission group's ID — it's actually the user's ID. Header `X-Total-Items`. Requires "Gerenciar grupos de permissão" permission.
- `GET /role-groups/{id}/technical-groups` - List attendant groups linked to a permission group (`list_role_group_technical_groups`). Header `X-Total-Items`. Requires "Gerenciar grupos de permissão" permission.
- `GET /departments` - List organization departments with optional name search (`list_departments`). Admin: all active; non-admin: only linked to attendant group
- `GET /desks` - Search/list desks (used by Smart Name Resolution and `list_desks`)
- `GET /desks/{id}` - Get full desk configuration (`get_desk`)
- `POST /desks` - Create a service desk (`create_desk`). Required: `name`, `display_name`, `description`. 201 has the same shape as `GET /desks/{id}`. Requires "Gerenciar mesas de serviços" + Tickets License. No `DELETE /desks` exists in API v2.
- `GET /desks/{id}/priorities` - Get desk priorities (`list_desk_priorities`, `update_ticket` priority_name resolution, `list_tickets` priority_name resolution)
- `GET /desks/{id}/services-catalogs` - Get desk service catalogs (`list_desk_services_catalogs`)
- `GET /desks/{id}/stages` - Get desk stages
- `GET /desks/{id}/services-catalogs-items` - Get service catalog items (supports `?name` for server-side search by catalog/area/item name; used by `search_catalog_item` and `list_tickets` `catalog_query` resolver)
- `POST /tickets/{ticket_number}/internal_communications` - Create internal communication
- `GET /tickets/{ticket_number}/internal_communications` - List internal communications
- `GET /tickets/{ticket_number}/internal_communications/{id}` - Get specific internal communication
- `PUT /tickets/{ticket_number}/internal_communications/{id}` - Update internal communication text (`update_internal_communication`)
- `DELETE /tickets/{ticket_number}/internal_communications/{id}` - Remove an internal communication (`delete_internal_communication`)
- `GET /tickets/{ticket_number}/files` - Get ticket attached files
- `POST /tickets/{ticket_number}/files` - Upload files to an existing ticket (`upload_ticket_files`)
- `DELETE /tickets/{ticket_number}/files/{id}` - Remove a file attached to a ticket (`delete_ticket_file`)
- `GET /tickets/{ticket_number}/stages-slas` - Get ticket stages history with SLA outcomes
- `GET /tickets/{ticket_number}/service-types` - List service types available for valorization of a ticket appointment (contract riders and loose services). Used by `get_ticket_service_types` and by `create_appointment` for `loose_service_name`/`contract_name` resolution.
- `GET /tickets/{ticket_number}/shifts` - List displacements available for valorization of a ticket appointment (travel/visit costs, filterable by contract_id). Used by `get_ticket_shifts` and by `create_appointment` for `shift_name` resolution.
- `GET /tickets/{ticket_number}/checklists` - List checklists (forms) of a ticket with all fields and fill state (`get_ticket_checklists`; paginated via `offset`/`limit`; header `X-Total-Items` for total count)
- `PUT /tickets/{ticket_number}/checklists/{id}/items/{index}` - Fill or clear a single checklist field (`update_ticket_checklist_item`; payload: `{ value }` for text/textarea/value/radio or `{ options: [{id, checked}] }` for checkbox; `{ value: null }` clears any field)
- `POST /tickets/{ticket_number}/appointments` - Create a ticket appointment. Supports 9 valorization fields: `attendance` (1/2/3), `attendance_kind` (1/2), `contract_rider_id`, `loose_service_id`, `shift_id`, `shift_owner_ticket_number`, `guarantee`, `value`, `external_user_name`. Plus 3 name-resolution params: `shift_name`, `loose_service_name`, `contract_name`.
- `GET /appointments` - List global appointments across all tickets with server-side filters (user_ids, desk_ids, client_ids, contract_ids, start_date, end_date, include_valorization); returns X-Total-Items header. Response includes `external_user_name`, `valorization.shift_owner_ticket`, and `contract` at the item top level (view :global). Used by `list_appointments_global` and `list_appointments_report`.
- `GET /tickets/{ticket_number}/appointments` - List ticket appointments with filters; returns X-Total-Items header. Response includes `external_user_name` and `valorization.shift_owner_ticket`. Used by `list_appointments`.
- `GET /tickets/{ticket_number}/pre-appointments` - List open (in-progress) time records for a ticket; returns X-Total-Items header. Requires "Criar e editar apontamentos" permission and Tickets license. Used by `list_pre_appointments`.
- `GET /chats/{id}` - Retrieve chat details. `origin` is an object `{ integration_id, _type, fields }`; displayed as `_type`. `assumed_at` synthesized for legacy chats (may differ from listing)
- `GET /chats/inbox` - List inbox chats. `origin` object → `_type`; `assumed_at` always null (unassumed chats)
- `GET /chats/mine` - List chats assigned to the authenticated user. `origin` object → `_type`; `assumed_at` raw column
- `GET /chats/in_attendance` - List chats currently in attendance. `origin` object → `_type`; `assumed_at` raw column
- `GET /chats/archived` - List archived (finished or canceled) chats. `origin` object → `_type`; `assumed_at` raw column
- `GET /chats/{id}/messages` - List messages of a chat in chronological order (`list_chat_messages`)
- `PUT /chats/{id}` - Update a chat (transfer attendant/department, link ticket)
- `POST /chats/send_message` - Send a WhatsApp message (free text or HSM template), creating the chat
- `PUT /chats/{id}/archive` - Finish (archive) a chat
- `GET /entities` - List custom field groups (`list_entities`)
- `GET /entities/{entity_id}/fields` - List custom subfields of an entity (`list_entity_fields`)
- `GET /entity_fields/{entity_field_id}/options` - List options of a single_select/checkbox field (`list_entity_field_options`)
- `POST /entities` - Create a custom field group, optionally linked to a services catalog/area/item, desks or an equipment type (`create_entity`). Requires `manage_entities` role; catalog/ticket/equipment `applied_in` also requires a Tickets license
- `PUT /entities/{id}` - Partially update a custom field group: rename, change description/desks/equipment type/menu_item, activate/inactivate (`update_entity`). `applied_in` and the catalog link are immutable (400 if sent)
- `POST /entities/{entity_id}/fields` - Create a subfield (entity_field); accepts inline `options[]` for single_select/checkbox (`create_entity_field`). Omitting `options` on those types auto-creates a "Padrão" placeholder option
- `PUT /entities/{entity_id}/fields/{id}` - Partially update a subfield: rename, change `required` (`update_entity_field`). `field_type` is immutable (400 if sent)
- `POST /entity_fields/{entity_field_id}/options` - Add an option to a single_select/checkbox subfield (`create_entity_field_option`)
- `PUT /entity_fields/{entity_field_id}/options/{id}` - Rename an option of a single_select/checkbox subfield (`update_entity_field_option`). **Note:** there is no DELETE for entity, entity_field or entity_field_option in the API v2 at any level — corrections go through these `update_*` tools or the portal; the reversible way to "undo" a group is `update_entity { active: false }`
- `GET /knowledges` - List knowledge base articles with optional search/folder filter (`list_knowledges`). Without "Gerenciar base de conhecimento" permission: public + attendant group only; with permission: all
- `GET /knowledges/{id}` - Fetch full detail of a knowledge article by ID (`get_knowledge`). Returns `description` converted from HTML to Markdown
- `POST /knowledges` - Create a new knowledge base article (`create_knowledge`). Accepts Markdown in `description` (converted to HTML before sending). Requires "Gerenciar conhecimento" permission
- `PUT /knowledges/{id}` - Partially update an existing knowledge base article (`update_knowledge`). Only fields provided are sent. Sending `description` creates a new version. Requires "Gerenciar conhecimento" permission. **Note:** was absent from the public Swagger snapshot as of 2026-09-01 — the contract (verb, field names, partial-update and validation semantics) was confirmed empirically against the live API on 2026-09-01. The 2026-09-23 snapshot documents the endpoint, confirming the 8 accepted fields match `update_knowledge`'s contract exactly
- `DELETE /knowledges/{id}` - Archive (soft-delete) a knowledge base article (`delete_knowledge`). Sets `archived: true` (content and versions kept) and unlinks from all folders. Irreversible via API (restorable in the TiFlux app). Requires "Gerenciar conhecimento" permission
- `GET /knowledge-folders/{id}` - Fetch full detail of a knowledge folder by ID (`get_knowledge_folder`). Returns title, description, icon, tags, total article count, and list of visible articles
- `PUT /knowledge-folders/{id}` - Partially update an existing knowledge folder: title, description, icon, tags (`update_knowledge_folder`). Only fields provided are sent. `icon: null` explicitly clears the folder's icon (distinct from omitting the field, which preserves it). Does not alter the folder's articles. Requires "Gerenciar conhecimento" permission
- `GET /knowledge-folders` - List knowledge base folders (`list_knowledge_folders`)
- `POST /knowledge-folders` - Create a new knowledge base folder (`create_knowledge_folder`). Required: `title`. Optional: `description`, `icon`, `tags`. The new folder always starts empty (`qty_knowledges: 0`). Requires "Gerenciar conhecimento" permission
- `GET /contracts` - List the organization's contracts (`list_contracts`), read-only. Returns 14 fields per contract plus `kind` (`"contract"` | `"contract_group"`); secondary fields (IDs, `rider_value`/`rider_tax`, durations) exposed via `include_details: true`. Header `X-Total-Items` for total count. Monetary fields require "Visualizar valores dos tickets" permission (otherwise `"--"`). Inactive records never appear, regardless of `status`.
- `GET /contracts/{id}` - Get the detail of an individual (non-Shared) contract, including its last rider (`get_contract`). A contract that is a member of a contract group returns `404` here (member contracts are queried through `get_contract_group` instead). Monetary fields of the rider require "Visualizar valores dos tickets" permission (otherwise `"--"`). Requires "Visualizar contratos" permission + Tickets License.
- `GET /contract-groups/{id}` - Get the detail of a contract group (modality Shared), including its member contracts (`get_contract_group`). Status (Ativo/Inativo/Expirado/Cancelado) is derived from the expiration date and the record. Monetary values are masked (`"--"`) without the "Visualizar valores dos tickets" permission, same as `list_contracts`/`get_contract`. Requires "Visualizar contratos" permission + Tickets License.
- `GET /contract-types` - List the contract types registered in the organization: ID, name, modality (`list_contract_types`). No search parameter, only pagination. Header `X-Total-Items` for total count. The IDs returned are the ones accepted by the `contract_type_ids` filter of `GET /contracts`. Requires "Visualizar contratos" permission.
- `GET /contracts/{id}/usage` - Consumption and balance per billing cycle of an individual contract (`get_contract_usage`, new 2026-10-06). Up to 3 cycles, most recent first, up to an optional `base_date`. No group-level equivalent in the API v2. Requires "Visualizar contratos" (atendente) or "Visualizar contrato" (usuário-cliente) permission + Tickets License.
- `GET /reports/feedbacks/chats` - Chats satisfaction/feedback report (`get_chats_feedback_report`). Returns `summary` (rating_average, chats_evaluated, chats_finished, clients_evaluated, answers_percentage); optional `chats_list` with `chats_list=true`. Requires administrator/reports permission (403 for non-admin).
- `GET /reports/feedbacks/tickets` - Tickets satisfaction/feedback report (`get_tickets_feedback_report`). Same structure as chats; list items use `tickets_list=true`, `rating` (integer), `revised_in_time` (timestamp), `comments` (plural, may be `""`), `desk_id`/`desk_name`. Requires administrator/reports permission (403 for non-admin).
- `GET /reports/billings/history` - Billing history report (`get_billings_history`). Returns paginated array of billing records with `billing_id`, `billing_date`, `client_id`, `client_name`, `due_date`, `nfe_number`, `paid`, `real_value`, `reversal`. Filters: `billing_start_date`/`billing_end_date` (pair), `due_start_date`/`due_end_date` (pair), `client_id`, `nfe_number`, `ticket_number`, `_type` (billed|reversed|paid). Header `X-Total-Items` for total count. Requires "Faturar serviços avulsos e contratos" permission + Tickets license (403 code `40301` without permission, `40304` without license).
- `GET /equipments` - List equipment/resources with optional filters (`list_equipments`). Supports `client_id`, `include_manufacturer`, `include_system`, `include_technical_info` (hardware inventory: processor, memory, disks, antivirus, windows_update, etc. — summarized to one line per resource) flags, pagination. Requires "Visualizar recursos" + Tickets License.
- `GET /equipments/{id}` - Get full details of a single equipment/resource (`get_equipment`). Returns hardware inventory, OS, manufacturer, network, custom fields (optional). Requires "Visualizar recursos" + Tickets License.
- `POST /equipments` - Create a new equipment/resource (`create_equipment`). Required: `name`, `client_id`, `equipment_type_id`. Optional: `equipment_group_id` (auto-assigned if omitted), `acquisition_date`, `warranty_date`.
- `PUT /equipments/{id}` - Update an existing equipment/resource (`update_equipment`). Partial update — only provided fields are sent.
- `GET /equipments/{id}/softwares` - List installed software on a resource (`list_equipment_softwares`). No pagination params — endpoint returns all at once.
- `GET /equipment-groups` - List equipment groups with optional `client_id` filter (`list_equipment_groups`). Paginated with `X-Total-Items`.
- `GET /equipment-types` - List equipment types with optional `name` filter (partial, case-insensitive) (`list_equipment_types`). Paginated with `X-Total-Items`.
- `GET /pre-tickets` - List pre-tickets (service requests in pre-triage) with optional filters (`list_pre_tickets`). Supports `archived`, `client_id`, `created_after`, `created_before`, `include_description`, pagination. Header `X-Total-Items` for total count. Requires Tickets license + "Gerenciar pré-tickets" permission.
- `POST /pre-tickets` - Create a new pre-ticket (`create_pre_ticket`). `multipart/form-data`. Required: `title`, `description`, `requestor_name`, `requestor_email`, `requestor_telephone`. Optional: `requestor_ramal`, `requestor_country`, `client_id`, `files[]` (max 10, 25MB each).
- `GET /templates/gupshup` - List HSM templates from the Gupshup integration (`list_gupshup_templates`). Filters: `integration_id`, `offset`, `limit`. Header `X-Total-Items` for total count. Requires "Gerenciar Modelos" permission.
- `GET /templates/whatsapp_cloud` - List templates from the WhatsApp Cloud (Meta) integration (`list_whatsapp_cloud_templates`). Filters: `integration_id`, `status` (enum: APPROVED/MISSING_VARS/REJECTED/PENDING), `offset`, `limit`. Header `X-Total-Items` for total count. Requires "Gerenciar Modelos" permission.
- `GET /services-catalogs` - List org-wide service catalogs (`list_services_catalogs`). Filter by `name` (ilike). Header `X-Total-Items`. Requires `service_catalogs_manage` role for write operations (read is unrestricted).
- `POST /services-catalogs` - Create a service catalog (`create_services_catalog`). Body `{ services_catalog: { name } }`. Name must be unique. Requires `service_catalogs_manage`.
- `PUT /services-catalogs/{id}` - Update a service catalog name (`update_services_catalog`). Body `{ services_catalog: { name } }`. Requires `service_catalogs_manage`.
- `DELETE /services-catalogs/{id}` - Soft-delete a service catalog and cascade-deactivate its areas and items (`delete_services_catalog`). Returns 204. Requires `service_catalogs_manage`.
- `GET /services-catalogs/{id}/areas` - List areas of a catalog (`list_services_catalog_areas`). Filter by `name`. Header `X-Total-Items`.
- `POST /services-catalogs/{id}/areas` - Create an area in a catalog (`create_services_catalog_area`). Body `{ area: { name } }`. Requires `service_catalogs_manage`.
- `PUT /services-catalogs/{catalog_id}/areas/{id}` - Update an area name (`update_services_catalog_area`). Body `{ area: { name } }`. Requires `service_catalogs_manage`.
- `DELETE /services-catalogs/{catalog_id}/areas/{id}` - Soft-delete an area and cascade-deactivate its items (`delete_services_catalog_area`). Returns 204. Requires `service_catalogs_manage`.
- `GET /services-catalogs-areas/{id}/items` - List items of an area (`list_services_catalog_items`). Filter by `name`. Header `X-Total-Items`.
- `POST /services-catalogs-areas/{id}/items` - Create an item in an area (`create_services_catalog_item`). Body `{ item: { name, start_time, end_time } }`. `start_time`/`end_time` in `HH:MM` format (hours 0-999). Requires `service_catalogs_manage`.
- `PUT /services-catalogs-areas/{area_id}/items/{id}` - Partial update of an item (`update_services_catalog_item`). Body `{ item: { name?, start_time?, end_time? } }`. Requires `service_catalogs_manage`.
- `DELETE /services-catalogs-areas/{area_id}/items/{id}` - Soft-delete an item (`delete_services_catalog_item`). Returns 204. Requires `service_catalogs_manage`.

## Avançado: execução local (SDK via npx)

> [!NOTE]
> Recomendamos o **servidor hospedado** (`https://mcp.tiflux.com` — veja [Como conectar](#como-conectar)): sempre atualizado, sem instalação e com mais controle e segurança. A execução local existe para cenários específicos (desenvolvimento, restrições de rede, ambientes offline).

Requisitos: Node.js >= 18.

Execute diretamente via `npx`:

```bash
npx @tiflux/mcp@latest
```

Ou instale globalmente:

```bash
npm install -g @tiflux/mcp
```

Configuração no cliente MCP (stdio):

```json
{
  "tiflux": {
    "command": "npx",
    "args": ["@tiflux/mcp@latest"]
  }
}
```

A chave de API é lida da variável de ambiente `TIFLUX_API_KEY`. Crie um arquivo `.env` (ou exporte as variáveis) com suas credenciais:

```bash
# Tiflux API Configuration
TIFLUX_API_KEY=your_api_key_here

# Default values for ticket creation
TIFLUX_DEFAULT_CLIENT_ID=1
TIFLUX_DEFAULT_DESK_ID=1
TIFLUX_DEFAULT_PRIORITY_ID=1
TIFLUX_DEFAULT_CATALOG_ITEM_ID=1
```

## Licença

MIT

## Suporte

Para suporte, entre em contato com o time Tiflux ou abra uma issue no [repositório público](https://github.com/tiflux/tiflux-mcp/issues).

TDQS

A3.5/5.0

Scored across 31 tools

Disambiguation4/5

Most tools have distinct purposes, with clear prefixes and context. Some potential confusion exists between list_tickets and get_ticket, and between get_chat and list_*_chats, but the descriptions are detailed enough to differentiate.

Naming Consistency4/5

Predominantly follows verb_noun pattern (e.g., create_ticket, get_ticket, list_tickets). Minor deviations include 'search_catalog_item' instead of 'find_catalog_item' and 'get_ticket_stages_slas' with an irregular plural, but overall consistent.

Tool Count3/5

31 tools is on the higher end but justifiable for a comprehensive ticketing and chat system. Some tools are very specific (e.g., list_entity_field_options), which might add unnecessary complexity for basic use cases.

Completeness4/5

Covers most lifecycle operations for tickets and chats, including search and customization. Missing delete_ticket and direct client/user management tools, but the core workflows are well-supported.

Maintenance

ActivityActive
ResponsivenessNo issues