# API de logs

Mande logs por HTTP para o FiveDash com o mesmo formato do webhook do Discord: endpoint, autenticação, corpo, limites, respostas, erros e exemplos.

Source: https://fivedash.fivex.gg/docs/pt/developers/logs-api

Cada log do FiveDash tem um link próprio que funciona como um webhook do Discord. Qualquer script, bot ou sistema que já manda para `discord.com/api/webhooks/...` passa a mandar para o FiveDash trocando só o link. Esta página é a referência completa desse endpoint, e mostra também o jeito mais rápido de mandar logs de dentro da cidade: o export do recurso `fivedash`.

## Antes de começar

* **Plano:** o módulo **Logs** precisa estar no plano da organização.
* **Um log criado:** em **Logs → Novo log**. Veja [Criar logs](https://fivedash.fivex.gg/docs/pt/logs/create-logs).
* **Nível de acesso:** o link aparece só para quem pode configurar o log: nível **Configurar** em **Todos os logs** ("Criar logs, configurar todos e ver os links"), ou quem criou o log pela área **Criar logs**. Para os outros, o painel mostra "Quem configura este log vê o link do webhook."
* **Qual usar:**

| Jeito                         | Quando usar                                                                                                                                           |
| ----------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| Export `exports.fivedash:log` | Scripts da cidade, no lado do servidor. Mais rápido, já lê o passaporte, o nome e os identificadores do jogador, e não expõe nenhum link. Recomendado |
| Link (webhook)                | Scripts prontos que já mandam para um webhook do Discord, bots e sistemas fora da cidade                                                              |

## Pegar o link do log

### Abra o log

No módulo **Logs**, clique no log na barra lateral.

### Abra Como mandar

Clique em **Como mandar**. Se o log está no modo export, clique em **Usar webhook**.

### Copie o link

Copie o link e cole no script onde estava o link do Discord. Não compartilhe: com o link, qualquer um manda logs para esse log.

O link tem esta forma:

```http
https://<endereço-dos-logs>/api/logs/<channelId>/<token>
```

Use sempre o link exatamente como o painel mostra. O endereço dos logs pode ser diferente do endereço do painel. Links antigos, no endereço do painel, continuam funcionando.

> O token no fim do link é a senha do log. Não publique o link no GitHub, em prints ou em chats. Se ele vazar, abra o log, clique em **Editar log**, depois em **Gerar novo link**, e troque nos scripts: o link atual para de funcionar na hora.

## Endpoint

| Método | URL                             | O que faz                                            |
| ------ | ------------------------------- | ---------------------------------------------------- |
| `POST` | `/api/logs/<channelId>/<token>` | Grava um ou mais logs                                |
| `GET`  | `/api/logs/<channelId>/<token>` | Confere o link (responde como um webhook do Discord) |

**Autenticação:** o próprio token no caminho. Não precisa de cabeçalho `Authorization`.

### Parâmetros de URL

| Parâmetro   | Onde    | O que é                                                                                                                      |
| ----------- | ------- | ---------------------------------------------------------------------------------------------------------------------------- |
| `channelId` | caminho | O ID do log (um UUID)                                                                                                        |
| `token`     | caminho | O segredo do log                                                                                                             |
| `wait`      | query   | `?wait=true` faz a resposta trazer a mensagem gravada (status `200`), como no Discord. Sem ele, a resposta é `204` sem corpo |

### Cabeçalhos

| Cabeçalho             | Obrigatório           | O que faz                                                                                                                                              |
| --------------------- | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `Content-Type`        | Sim                   | `application/json`, `multipart/form-data` ou `application/x-www-form-urlencoded`                                                                       |
| `Idempotency-Key`     | Não                   | Um ID seu para a mensagem (1 a 256 caracteres, sem caracteres de controle). Repetir o mesmo ID não grava o log duas vezes. Exige `X-FiveDash-Event-At` |
| `X-FiveDash-Event-At` | Com `Idempotency-Key` | Quando aconteceu, em milissegundos Unix (ex.: `1762711200000`). Precisa estar entre 7 dias atrás e 5 minutos à frente                                  |

Sem esses dois cabeçalhos, cada `POST` grava logs novos, com a hora de chegada.

## Corpo

### JSON

O mesmo corpo de um webhook do Discord:

```json
{
  "username": "meu-script",
  "content": "Texto solto, opcional",
  "embeds": [
    {
      "title": "Compra na loja",
      "description": "João comprou 3 águas",
      "color": 3066993,
      "fields": [
        { "name": "Item", "value": "água", "inline": true },
        { "name": "Qtd", "value": "3", "inline": true }
      ],
      "author": { "name": "Loja 24/7" },
      "footer": { "text": "Cidade Exemplo" },
      "image": { "url": "https://cdn.fivex.sh/cidade-exemplo/prints/print-0001.webp" },
      "timestamp": "2026-11-09T18:00:00.000Z"
    }
  ]
}
```

| Campo                    | Tipo   | O que vira no FiveDash                                                    | Limite                               |
| ------------------------ | ------ | ------------------------------------------------------------------------- | ------------------------------------ |
| `content`                | string | Texto do log. Sem `title`, a primeira linha vira o título                 | 2.000 caracteres                     |
| `username`               | string | O nome de quem mandou. Aparece como o **Script** do log e serve de filtro | 80 caracteres                        |
| `embeds`                 | array  | Cada embed vira um log separado                                           | 10 embeds                            |
| `embeds[].title`         | string | Título do log                                                             | 256                                  |
| `embeds[].description`   | string | Texto do log                                                              | 4.096                                |
| `embeds[].url`           | string | Link do título (`http`/`https`)                                           |                                      |
| `embeds[].color`         | number | Cor da faixa                                                              |                                      |
| `embeds[].fields`        | array  | Campos `{ "name", "value", "inline" }`                                    | 25 campos; `name` 256, `value` 1.024 |
| `embeds[].author.name`   | string | Autor                                                                     | 256                                  |
| `embeds[].footer.text`   | string | Rodapé                                                                    | 2.048                                |
| `embeds[].image.url`     | string | Imagem grande (`http`/`https`)                                            |                                      |
| `embeds[].thumbnail.url` | string | Miniatura (`http`/`https`)                                                |                                      |
| `embeds[].timestamp`     | string | Data do embed (ISO 8601)                                                  | 64                                   |

Outros campos do Discord (`avatar_url`, `tts`, `components`, `allowed_mentions`…) são aceitos e ignorados. Textos maiores que o limite são cortados.

O FiveDash também lê o texto para a busca e os filtros, sem você mudar nada no script:

| No texto                                                                                                                                | Vira                   |
| --------------------------------------------------------------------------------------------------------------------------------------- | ---------------------- |
| Linhas como `**Item:** água`, `Item: água`, `Item = água` ou `[Item]: água`, e os `fields`                                              | Pares de campo e valor |
| `Passaporte: 123`, `passport 123`, `user_id: 123`, `citizenid: 123`, ou um campo como **ID**, **Vítima** ou **Assassino** com um número | ID de jogador          |
| `<@123456789012345678>` ou `discord:123456789012345678`                                                                                 | Conta do Discord       |
| `license:…`, `license2:…`, `steam:…`, `fivem:…`, `xbl:…`, `live:…`                                                                      | Identificadores        |
| `vector3(1.0, 2.0, 3.0)`, `x: 1.0, y: 2.0, z: 3.0` ou `1.0, 2.0, 3.0`                                                                   | Coordenadas            |

Isso vale até 10 jogadores, 10 Discords, 10 identificadores e 10 coordenadas por log.

### Multipart e formulário

Para scripts que mandam como o Discord aceita em `multipart/form-data` ou `application/x-www-form-urlencoded`:

| Campo do formulário            | O que é                                                              |
| ------------------------------ | -------------------------------------------------------------------- |
| `payload_json`                 | O mesmo JSON acima, como texto                                       |
| `content`                      | Usado se `payload_json` não tiver `content`                          |
| `username`                     | Usado se `payload_json` não tiver `username`                         |
| Arquivos (`files[0]`, `file`…) | Aceitos, mas **não são guardados**: o log registra só quantos vieram |

Uma mensagem só com arquivo (sem texto e sem embed) também vira um log.

### Limites

| Limite                        | Valor                    |
| ----------------------------- | ------------------------ |
| Corpo JSON                    | 256 KB                   |
| Corpo multipart ou formulário | 4 MB                     |
| Embeds por mensagem           | 10 (cada um vira um log) |
| Campos por embed              | 25                       |

## Respostas

| Status | Quando                     | Corpo                                           |
| ------ | -------------------------- | ----------------------------------------------- |
| `204`  | Gravado (sem `?wait=true`) | vazio                                           |
| `200`  | Gravado, com `?wait=true`  | A mensagem, no formato do Discord (veja abaixo) |
| `200`  | `GET` no link              | Os dados do webhook                             |

Resposta com `?wait=true`:

```json
{
  "id": "0193a5f0-0000-7000-8000-000000000000",
  "type": 0,
  "channel_id": "<channelId>",
  "webhook_id": "<channelId>",
  "author": { "id": "<channelId>", "username": "meu-script", "bot": true, "discriminator": "0000", "avatar": null },
  "attachments": [],
  "mentions": [],
  "mention_roles": [],
  "pinned": false,
  "mention_everyone": false,
  "tts": false,
  "timestamp": "2026-11-09T18:00:00.123Z",
  "edited_timestamp": null,
  "flags": 0,
  "components": [],
  "content": "Texto solto, opcional",
  "embeds": [{ "title": "Compra na loja" }]
}
```

Resposta do `GET`:

```json
{ "type": 1, "id": "<channelId>", "name": "Loja", "avatar": null, "channel_id": "<channelId>", "guild_id": null, "application_id": null, "token": "<token>" }
```

## Erros

Os erros seguem os códigos do Discord, para scripts que já tratam esses casos.

| Status | Corpo                                                                              | Quando                                                                                                                                         |
| ------ | ---------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
| `400`  | `{"code":50109,"message":"The request body contains invalid JSON."}`               | JSON inválido ou formulário malformado                                                                                                         |
| `400`  | `{"code":50006,"message":"Cannot send an empty message"}`                          | Sem `content`, sem embed com conteúdo e sem arquivo                                                                                            |
| `400`  | `{"code":50035,"message":"…"}`                                                     | Conteúdo inválido, `Idempotency-Key` inválida, ou `X-FiveDash-Event-At` faltando ou fora do prazo ("data do evento inválida ou fora do prazo") |
| `401`  | `{"code":50027,"message":"Invalid Webhook Token"}`                                 | O token não confere (por exemplo, depois de **Gerar novo link**)                                                                               |
| `404`  | `{"code":10015,"message":"Unknown Webhook"}`                                       | O log não existe ou foi excluído                                                                                                               |
| `409`  | `{"code":50035,"message":"destino do evento removido ou inválido"}`                | O log não está mais ativo                                                                                                                      |
| `413`  | `{"code":40005,"message":"Request entity too large"}`                              | Corpo acima do limite                                                                                                                          |
| `429`  | `{"error":"too_many","message":"limite de eventos ou bytes por segundo atingido"}` | Rápido demais para o servidor. Respeite o `Retry-After` (1 s)                                                                                  |
| `503`  | `{"error":"unavailable","message":"serviço temporariamente indisponível"}`         | Serviço ocupado ou fora. Tente de novo depois do `Retry-After`                                                                                 |

> Para tentar de novo sem duplicar, mande `Idempotency-Key` e `X-FiveDash-Event-At` com os mesmos valores em cada tentativa.

## Exemplos

### cURL

```bash
curl -X POST "https://<endereço-dos-logs>/api/logs/<channelId>/<token>" \
  -H "Content-Type: application/json" \
  -d '{
    "username": "meu-script",
    "embeds": [{
      "title": "Compra na loja",
      "fields": [{ "name": "Item", "value": "água" }, { "name": "Qtd", "value": "3" }]
    }]
  }'
```

Com confirmação e sem duplicar:

```bash
curl -X POST "https://<endereço-dos-logs>/api/logs/<channelId>/<token>?wait=true" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: compra-48213" \
  -H "X-FiveDash-Event-At: 1762711200000" \
  -d '{ "content": "Pedido 48213 entregue" }'
```

Multipart, como o Discord:

```bash
curl -X POST "https://<endereço-dos-logs>/api/logs/<channelId>/<token>" \
  -F 'payload_json={"username":"meu-script","content":"Print anexado"}' \
  -F "files[0]=@print.png"
```

### Lua (PerformHttpRequest)

No lado do servidor de um script que hoje manda para o Discord, troque só o link:

```lua
local LOG_URL = "https://<endereço-dos-logs>/api/logs/<channelId>/<token>"

local function sendLog(title, fields)
    local embed = { title = title, color = 3066993, fields = {} }
    for _, field in ipairs(fields) do
        embed.fields[#embed.fields + 1] = { name = field[1], value = tostring(field[2]), inline = true }
    end
    PerformHttpRequest(LOG_URL, function(status)
        if status ~= 204 then print(("[logs] FiveDash respondeu %s"):format(status)) end
    end, "POST", json.encode({ username = GetCurrentResourceName(), embeds = { embed } }), {
        ["Content-Type"] = "application/json",
    })
end

sendLog("Compra na loja", { { "Item", "água" }, { "Qtd", 3 } })
```

> Nunca coloque o link em scripts do cliente (`client_scripts`): eles vão para o PC dos jogadores, e qualquer um poderia mandar logs falsos. Mande do servidor.

### JavaScript (Node, bot ou servidor)

```ts
const LOG_URL = process.env.FIVEDASH_LOG_URL!; // o link do log, fora do código

await fetch(`${LOG_URL}?wait=true`, {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "Idempotency-Key": "ban-9921",
    "X-FiveDash-Event-At": String(Date.now()),
  },
  body: JSON.stringify({
    username: "bot-da-staff",
    embeds: [{ title: "Banimento", fields: [{ name: "Motivo", value: "RDM" }] }],
  }),
});
```

## Mandar logs de dentro da cidade (Lua)

Nos scripts da cidade, prefira o export do recurso `fivedash`. Ele não usa link, junta os logs em lotes, guarda na fila quando o painel está fora e preenche o passaporte, o nome e os identificadores do jogador sozinho:

```lua
exports.fivedash:log("mortes", {
    title = "Morte registrada",
    players = { { source = killer, label = "Assassino" }, { source = victim, label = "Vítima" } },
    fields = { { "Arma", weapon } },
})
```

A **Chave** (`mortes`) é a do log no painel. Todas as opções estão em [Exports, eventos e comandos](https://fivedash.fivex.gg/docs/pt/developers/exports-and-events).

Por baixo, o recurso usa dois endpoints próprios, autenticados pela chave do servidor (`Authorization: Bearer fdk_…`): `GET /api/fivem/logs/channels` (quais chaves existem) e `POST /api/fivem/logs` (lotes de até 500 logs, JSON com gzip opcional, até 2 MB). Eles são do recurso: não chame direto dos seus scripts.

## Perguntas frequentes

**Meu script manda para o Discord e para o FiveDash ao mesmo tempo?**
Não: você troca o link do Discord pelo do FiveDash. Se quiser os dois, o script precisa mandar duas vezes.

**O FiveDash guarda os arquivos anexados?**
Não. Ele aceita o envio (para não quebrar o script) e registra quantos arquivos vieram. Para guardar imagens, use a [CDN](https://fivedash.fivex.gg/docs/pt/developers/cdn-api) e mande o link em `image.url`.

**Recebi 401 depois de um tempo.**
Alguém clicou em **Gerar novo link**. Copie o link novo do painel e troque no script.

**Uma mensagem com 3 embeds vira quantos logs?**
Três: um por embed. O `content` vai junto do primeiro.

## Próximos passos

[Migrar dos webhooks](https://fivedash.fivex.gg/docs/pt/logs/migrate-from-webhooks)Troque os webhooks do Discord pelos links do FiveDash.
[Exports, eventos e comandos](https://fivedash.fivex.gg/docs/pt/developers/exports-and-events)O export de logs em detalhe.
[Criar logs](https://fivedash.fivex.gg/docs/pt/logs/create-logs)Crie um log e escolha como o script manda.
[Buscar e filtrar](https://fivedash.fivex.gg/docs/pt/logs/search-and-filter)Encontre qualquer log por jogador, item ou período.
