Referência
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:
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:
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:
curl -X POST https://example.com/api/v1/auth/token \
-H "Content-Type: application/json" \
-d '{"email": "feed@example.com", "password": "…"}'
{
"token": "anSojGGKOveDuIypVGbx…",
"token_created": 1791940697,
"token_expires": 1792075420,
"code": 200,
"success": true,
"status": "ok"
}
3. Envie-o em toda requisição:
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:
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:
[#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.
Leitura
Uma lista
GET /api/v1/projects?filter=published:1&sort=year|numeric|desc&limit=12&offset=0
{
"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
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
POST /api/v1/projects
{"title": "Harbour Atlas", "sector": "public", "year": 2026}
{
"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
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:
PUT /api/v1/projects
{
"1ztuvo56o5vze6t0": {"published": true},
"9d03b6e1c4": {"published": false}
}
Excluir
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
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
curl -X POST https://example.com/api/v1/projects/import \
-H "Authorization: Bearer $TOKEN" \
-F "file=@projects.csv"
{"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:
curl -X POST https://example.com/api/v1/.files \
-H "Authorization: Bearer $TOKEN" \
-F "file=@cover.jpg"
{
"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:
{
"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 e O que uma rota devolve.
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.
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 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.