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.

Ver esta página em Markdown