# API REST

Uma API para cada recurso: acesso por sessão, token e público, leitura, escrita, exportação, importação e arquivos.

Todo recurso tem a mesma API REST. Defina um recurso e ele responde em `/api/v1/<resource>`: listar, ler, criar, alterar, excluir, exportar e importar, com as permissões de quem chama. Aprenda para um recurso e você sabe para todos. Os exemplos desta página usam `projects`.

| Requisição | Faz | Permissão |
|---|---|---|
| `GET /api/v1/projects` | Lista registros, com filtro, ordenação, busca e paginação | `view-projects` |
| `GET /api/v1/projects/<uuid>` | Lê um registro | `view-projects` |
| `POST /api/v1/projects` | Cria um registro | `create-projects` |
| `POST /api/v1/projects/<uuid>` | Cria um registro com um id escolhido por você | `create-projects` |
| `PUT` ou `PATCH /api/v1/projects/<uuid>` | Altera os campos que você envia | `edit-projects` |
| `PUT` ou `PATCH /api/v1/projects` | Altera vários registros numa requisição | `edit-projects` |
| `DELETE /api/v1/projects/<uuid>` | Exclui um registro | `delete-projects` |
| `DELETE /api/v1/projects` | Exclui todos os registros | `api_delete_resource_projects` |
| `GET /api/v1/projects/export` | Baixa todos os registros como JSON, CSV ou TSV | `export-projects` |
| `POST /api/v1/projects/import` | Cria e altera registros a partir de um arquivo | `import-projects` |

`manage-projects` inclui todas as operações, exceto excluir todos os registros de uma vez, que só a própria permissão permite.

## Quem está chamando

Há três formas de entrar, e as permissões são as mesmas em todas.

| Quem chama | Entra com | Uso típico |
|---|---|---|
| Suas próprias páginas e scripts | A sessão do usuário autenticado | Formulários, edição na página, uma aplicação numa rota |
| Outro sistema | Um token | Um app, um servidor, uma integração |
| Um visitante anônimo | Uma permissão explícita por operação | Um formulário de contato ou de cadastro |

### Das suas próprias páginas: `nb.api`

O código no próprio site não precisa de token. O navegador envia a sessão do usuário autenticado, e a API aplica as permissões dele, exatamente como no admin. A shell da página fornece um pequeno cliente, `nb.api`, e o endereço base do site em `nb.base_url`:

```js
const list = await nb.api.get(`${nb.base_url}/api/v1/projects?filter=published:1&limit=12`);

const created = await nb.api.post(`${nb.base_url}/api/v1/projects`, {
    title: 'Harbour Atlas',
    sector: 'public',
});

await nb.api.put(`${nb.base_url}/api/v1/projects/${uuid}`, { published: true });
await nb.api.delete(`${nb.base_url}/api/v1/projects/${uuid}`);
```

Cada chamada devolve a resposta JSON já interpretada, também em caso de erro, então verifique `success`:

```js
const answer = await nb.api.post(`${nb.base_url}/api/v1/leads`, form);
if (!answer.success) {
    show_error(answer.message);
}
```

Qualquer outro cliente funciona do mesmo jeito, desde que envie o cookie de sessão: `fetch(url, { credentials: 'same-origin' })` a partir de React, Vue ou JavaScript puro. Um visitante não autenticado tem as permissões de um visitante anônimo, que por padrão são nenhuma.

### De outro sistema: tokens

Quem chama de fora entra como usuário do Nimbly e recebe um token.

**1. Dê a quem chama um usuário próprio.** Crie uma função só com as permissões necessárias, por exemplo `view-projects` para um feed, e um usuário com essa função. Ligue **API access** no painel de ações do usuário no admin, ao lado do formulário de edição em `/nb-admin/users/<uuid>`; isso exige uma função que possa editar usuários. Ao desligar, o token do usuário para de funcionar na hora.

**2. Peça um token:**

```bash
curl -X POST https://example.com/api/v1/auth/token \
  -H "Content-Type: application/json" \
  -d '{"email": "feed@example.com", "password": "…"}'
```

```json
{
  "token": "anSojGGKOveDuIypVGbx…",
  "token_created": 1791940697,
  "token_expires": 1792075420,
  "code": 200,
  "success": true,
  "status": "ok"
}
```

**3. Envie-o em toda requisição:**

```bash
curl https://example.com/api/v1/projects \
  -H "Authorization: Bearer $TOKEN"
```

Um token vale por 10 minutos depois da última renovação. Renove-o antes disso com um `GET` no mesmo endereço:

```bash
curl https://example.com/api/v1/auth/token -H "Authorization: Bearer $TOKEN"
```

Uma senha errada responde `401 INVALID_CREDENTIALS`; um usuário sem acesso à API responde `403 ACCESS_DENIED`. Guarde as credenciais no cofre de segredos de quem chama, nunca num repositório.

### De visitantes anônimos: uma operação de cada vez

Um formulário público precisa criar registros sem que ninguém entre. Permita exatamente essa operação, e nada mais, com uma rota em `ext/uri/api/v1/<resource>/index.tpl`:

```html
[#api-allow post leads#]
```

Todas as outras operações no recurso continuam fechadas. Os formulários feitos com `[#build-form#]` acrescentam uma chave de formulário e um campo honeypot, que a API verifica. Veja [Formulários](/pt/docs/guides/forms/).

## Leitura

### Uma lista

```bash
GET /api/v1/projects?filter=published:1&sort=year|numeric|desc&limit=12&offset=0
```

```json
{
  "projects": {
    "4f1c9a2e7b": {"uuid": "4f1c9a2e7b", "title": "Harbour Atlas", "sector": "public", "year": 2026, "published": true, "_created": 1792074820, "_modified": 1792074830},
    "9d03b6e1c4": {"uuid": "9d03b6e1c4", "title": "Northlight Library", "sector": "culture", "year": 2026, "published": true, "_created": 1792074821, "_modified": 1792074821}
  },
  "count": 2,
  "total": 9,
  "code": 200,
  "success": true,
  "status": "ok"
}
```

Os registros são um objeto com o UUID como chave. `count` é o número devolvido; `total`, presente quando você filtra, ordena ou pagina, é o número antes do corte.

| Parâmetro | Exemplo | Faz |
|---|---|---|
| `filter` | `status:open`, `status:!draft`, `status:new\|\|todo`, `published:1,sector:public` | Mantém os registros correspondentes. `!` nega, `\|\|` é ou, uma vírgula é e |
| `sort` | `year\|numeric\|desc`, `title\|text\|asc`, `date\|desc,title\|asc` | Ordena por um ou mais campos |
| `limit`, `offset` | `limit=20&offset=40` | Uma página: a terceira página de 20 |
| `search` | `search=harbour` | Registros cujo título contém o texto; num recurso sem campo de título, qualquer campo |

Uma lista filtrada, ordenada ou paginada lê só os registros da página, também num recurso grande. Filtrar ou ordenar por um campo que a API nunca devolve é recusado com `400`.

### Um registro

```bash
GET /api/v1/projects/4f1c9a2e7b
```

A resposta tem o mesmo formato de uma lista, com um registro.

### Dados sem alteração

As leituras respondem com um cabeçalho `Last-Modified`. Devolva-o como `If-Modified-Since` e um registro ou uma lista sem alteração responde `304 Not Modified`, sem corpo.

## Escrita

Envie JSON com `Content-Type: application/json`. Os valores são validados em relação à definição do recurso, o texto formatado é limpo para o markup permitido, e cada alteração entra no histórico do registro com o usuário que a fez.

### Criar

```bash
POST /api/v1/projects
{"title": "Harbour Atlas", "sector": "public", "year": 2026}
```

```json
{
  "projects": {"1ztuvo56o5vze6t0": {"uuid": "1ztuvo56o5vze6t0", "title": "Harbour Atlas", "sector": "public", "year": 2026}},
  "count": 1,
  "message": "RESOURCE_CREATED",
  "code": 201,
  "success": true,
  "status": "ok"
}
```

O Nimbly gera o UUID. Para usar um identificador de outro sistema, coloque-o no endereço, `POST /api/v1/projects/erp-1042`, ou envie-o como `uuid` no corpo. Um identificador que já existe responde `409 RESOURCE_EXISTS`; um campo obrigatório ausente ou um valor inválido responde `422 INVALID_DATA`.

### Alterar

```bash
PUT /api/v1/projects/1ztuvo56o5vze6t0
{"published": true}
```

Só mudam os campos que você envia. `PATCH` faz exatamente o mesmo; use o que seu cliente espera. A resposta traz o registro inteiro como está agora, com `RESOURCE_UPDATED`. Um recurso com `"upsert": true` no `.meta` cria o registro quando ele ainda não existe.

Vários registros numa requisição, com o UUID como chave:

```bash
PUT /api/v1/projects
{
  "1ztuvo56o5vze6t0": {"published": true},
  "9d03b6e1c4": {"published": false}
}
```

### Excluir

```bash
DELETE /api/v1/projects/1ztuvo56o5vze6t0
```

responde `RESOURCE_DELETED`. O registro continua restaurável pelo histórico no admin por 90 dias. `DELETE /api/v1/projects` exclui todos os registros e precisa de uma permissão própria, `api_delete_resource_projects`, que nenhuma outra permissão inclui.

## Exportação e importação

### Exportar

```bash
GET /api/v1/projects/export?format=csv
```

Baixa todos os registros como um arquivo com o nome do recurso e a data. `format` é `json` (o padrão), `csv` ou `tsv`. A exportação JSON traz a definição do recurso como `_meta` ao lado dos registros, para que o recurso possa ser recriado em outro lugar. Os campos que a API nunca devolve ficam de fora.

### Importar

```bash
curl -X POST https://example.com/api/v1/projects/import \
  -H "Authorization: Bearer $TOKEN" \
  -F "file=@projects.csv"
```

```json
{"stats": {"imported": 2, "errors": 0, "created": 1, "updated": 1, "total": 2}, "code": 200, "success": true, "status": "ok"}
```

O arquivo é JSON (uma exportação, um objeto de registros ou uma lista), CSV ou TSV com uma linha de cabeçalho com os nomes dos campos. Uma linha altera um registro existente quando o `uuid` corresponde, ou então o `id`, o `name` ou o `title`; as outras linhas criam registros novos. Uma exportação JSON importada num site sem o recurso cria o recurso a partir do `_meta`.

### Importar um documento

`POST /api/v1/projects/import-document` com um arquivo `.docx` propõe valores de campo para um novo registro, lidos do documento por um modelo de IA. É o que a ação **Import document** do admin usa. Ela precisa de `create-projects` e de uma chave da OpenAI no `.env`, e não cria nada sozinha.

## Arquivos

Os envios vão para a biblioteca de mídia por `.files`:

```bash
curl -X POST https://example.com/api/v1/.files \
  -H "Authorization: Bearer $TOKEN" \
  -F "file=@cover.jpg"
```

```json
{
  "files": {"uuid": "415efabde63801837fd88c62078f5db0", "name": "cover.jpg", "type": "image/jpeg", "size": 248113, "width": 2400, "height": 1600, "orientation": "landscape"},
  "message": "RESOURCE_CREATED",
  "code": 201
}
```

O UUID é o checksum do arquivo, então enviar o mesmo arquivo duas vezes dá o mesmo UUID. Guarde-o num campo `image`, `gallery` ou `file` como qualquer valor.

| Requisição | Faz |
|---|---|
| `GET /api/v1/.files` | Lista todos os arquivos com seus metadados |
| `GET /api/v1/.files/<uuid>` | Lê os metadados de um arquivo |
| `PUT /api/v1/.files/<uuid>` | Altera metadados como `title` |
| `DELETE /api/v1/.files/<uuid>` | Exclui o arquivo, seus metadados e suas cópias redimensionadas |
| `GET /img/<uuid>/<size>` | A imagem, redimensionada: `800w`, `400h`, `400x300c` (recorte), `400x300f` (ajuste), em WebP |
| `GET /download/<uuid>` | O arquivo como foi enviado, com nome e tipo |

As permissões de arquivos são definidas na linha Files da tela de funções. Um arquivo maior que o limite de envio do servidor responde `413 UPLOAD_TOO_LARGE`.

## Respostas e erros

Toda resposta é JSON, com `code`, `success` e `status` ao lado dos dados:

```json
{
  "message": "ACCESS_DENIED",
  "needs": "api_get_projects,api_(any)_projects,api_get_(any),api_(any),view-projects,manage-projects",
  "code": 403,
  "success": false,
  "status": "error"
}
```

| Código | Mensagem | Significa |
|---|---|---|
| 200 | `RESOURCE_UPDATED`, `RESOURCE_DELETED` | Feito |
| 201 | `RESOURCE_CREATED` | Criado |
| 304 | | Sem alteração desde `If-Modified-Since` |
| 400 | `INVALID_DATA` | A requisição está malformada, ou filtra por um campo oculto |
| 401 | `INVALID_CREDENTIALS` | E-mail ou senha errados ao pedir um token |
| 403 | `ACCESS_DENIED` | Não permitido; `needs` lista as permissões que permitiriam |
| 404 | `RESOURCE_NOT_FOUND` | Recurso ou registro inexistente |
| 405 | `METHOD_NOT_ALLOWED` | O recurso não suporta este método |
| 409 | `RESOURCE_EXISTS` | Já existe um registro com este identificador |
| 413 | `UPLOAD_TOO_LARGE` | O arquivo passa do limite de envio |
| 422 | `INVALID_DATA` | Um valor não passa na validação do recurso |
| 500 | `RESOURCE_CREATE_FAILED`, `RESOURCE_UPDATE_FAILED` | O servidor não conseguiu gravar |

Verifique `success` e `code`, e não uma mensagem específica.

## O que a API nunca devolve

Os campos listados em `hidden` no `.meta`, e os campos com criptografia de mão única, como senhas, são guardados e usados, mas nunca aparecem numa lista, num registro, numa resposta de escrita ou numa exportação. Nos usuários, a senha, o salt, o token da API e os tokens de redefinição ficam sempre ocultos.

## Endpoints próprios

Quando uma integração precisa de uma resposta que a API de recursos não dá, escreva uma rota própria. `api_access('<permission>')` aceita os três tipos de quem chama, então um endpoint atende tanto suas páginas quanto outros sistemas. Veja [Integrações](/pt/docs/guides/integrations/) e [O que uma rota devolve](/pt/docs/guides/responses/).

## OpenAPI

Todo site Nimbly descreve a própria API no padrão OpenAPI 3, em `/api/v1/openapi.json`. O documento lista os recursos do site com seus campos, tipos, campos obrigatórios e opções de seleção, e as operações disponíveis. Ele segue as permissões de quem chama, exatamente como a API: um token ou sessão vê só os recursos e operações que pode usar, os campos que a API nunca devolve ficam de fora, e uma requisição anônima é recusada.

```bash
curl https://example.com/api/v1/openapi.json -H "Authorization: Bearer $TOKEN" > openapi.json
```

Carregue-o no Postman, no Insomnia ou no Swagger UI, ou gere com ele um cliente tipado com qualquer gerador OpenAPI. Dê a uma integração um usuário próprio, e o documento mostra exatamente o que essa integração pode fazer.

Uma [descrição genérica](/reference/api/openapi.json) das operações que todo recurso compartilha, com exportação, importação e arquivos, está disponível aqui.

## Endpoints internos

Alguns endereços em `/api/v1/` servem ao admin e às ferramentas de edição: `.resources`, `.pages`, `.files-usage`, `.files-unused`, `.system-messages`, `session`, `openai/complete`, `openai/translate`, `git-status`, `git-pull`, `empty-resource`, `agent-chat` e `agent-remote`. Eles verificam permissões como qualquer outro endpoint, mas seu formato pode mudar junto com o admin; construa sobre a API de recursos.
