Autenticação e visão geral (referência)
API pública da Nebular para agents e automações: gerencie projetos, serviços, deploys, variáveis, domínios, volumes, bancos e templates e as galerias de arquivos servidas pela CDN (*.nebularcdn.com). Todas as rotas ficam em /v1, usam apenas Authorization: Bearer e respondem JSON (application/json; charset=utf-8, sempre Cache-Control: no-store). Esta especificação é servida em GET /v1/openapi.json (pública) e é validada contra o código a cada mudança.
Como obter um token
Seção intitulada “Como obter um token”No painel: Time → Configurações → Tokens de API. A criação exige papel admin do time e confirmação recente de identidade (step-up). O token tem o formato nbk_<…>, é exibido uma única vez (guardamos apenas o hash SHA-256), vale 90 dias por padrão (máximo 1 ano) e pode ser revogado a qualquer momento. Ele pertence ao time, nunca a uma pessoa. Pode ser restrito a algumas galerias (assets:*) e/ou a alguns projetos (project_ids).
O token vai somente no cabeçalho Authorization: Bearer nbk_…. Nunca o envie em query string. Cookies são ignorados nestas rotas (não há CSRF por construção) e as rotas de sessão do painel não aceitam Bearer. Cada operação indica os escopos exigidos na extensão x-required-scopes (todos são necessários) e, quando um escopo opcional muda a resposta, em x-optional-scopes.
Escopos
Seção intitulada “Escopos”| Escopo | Permite |
|---|---|
assets:read |
listar galerias e arquivos, consultar detalhes e status de varredura |
assets:write |
enviar, versionar e excluir arquivos |
galleries:manage |
criar galerias |
projects:read |
ler projetos, serviços, deployments, domínios, volumes, bancos e backups |
projects:write |
criar, editar e apagar projetos e serviços |
deploy |
disparar, cancelar e reverter deploys (serviço e grupo), enviar código-fonte |
env:read |
ler variáveis (valores sensíveis vêm null) |
env:write |
criar, alterar e apagar variáveis |
env:reveal |
ler valores sensíveis, resolved=true e credenciais de banco. Exige env:read e que o token tenha sido criado com confirmação de identidade (step-up) |
domains:write |
anexar e remover domínios |
volumes:write |
anexar, aumentar e apagar o volume |
databases:write |
criar, alterar, apagar, rotacionar, backup e restore de bancos |
logs:read |
ler log de build e de execução |
templates:deploy |
listar e instanciar templates |
Token sem o escopo ⇒ 403 forbidden. Projeto de outro time, ou fora dos project_ids do token, é indistinguível de um projeto inexistente (404 not_found). GET /v1/me e GET /v1/limits aceitam qualquer token válido.
Projetos e serviços
Seção intitulada “Projetos e serviços”{p} nas rotas aceita o id (26 caracteres) ou o slug do projeto; {s} aceita o id ou o nome do serviço. Todo projeto nasce com um serviço web padrão. Variáveis podem referenciar outros serviços com ${{servico.VAR}} (ex.: ${{db.MYSQL_HOST}}); essas referências definem a ordem do deploy de grupo e são validadas no disparo.
Deploys são assíncronos
Seção intitulada “Deploys são assíncronos”POST …/deployments, POST …/deploy, rollback, backup e restore respondem 202 com um recurso em andamento. Acompanhe por polling com backoff (comece em 2 s e dobre até 15 s; não há webhooks neste ciclo):
- deployment:
queued → building → deploying → ready, oufailed,blocked,canceled,superseded; emfailed,errortrazcode,messageedetails; - deploy de grupo:
running → ready | failed | canceled, com o estado de cada serviço emitems[].status; - job de banco:
queued → running → done | failed; - domínio:
pending_dns → verifying → active | error.
Idempotência
Seção intitulada “Idempotência”Todo POST que cria ou dispara aceita Idempotency-Key (8 a 128 caracteres [A-Za-z0-9._:-]). Mesma chave + mesmo pedido em 24 h devolve a mesma resposta (com Idempotent-Replay: true); mesma chave com pedido diferente ⇒ 422 idempotency_conflict; chave malformada ⇒ 400. Só respostas 2xx são gravadas, então repetir depois de uma falha executa de verdade. O upload de arquivos de galeria tem regra própria (409 idempotency_conflict, mais abaixo).
Paginação
Seção intitulada “Paginação”Listas devolvem {"items": […], "next_cursor": "…"|null, "has_more": bool}. Passe next_cursor em ?cursor= para a página seguinte e use ?limit= de 1 a 200 (padrão 50). O cursor é opaco; cursor malformado ⇒ 400 invalid_request. Deployments, backups e a lista de projetos vêm do mais novo para o mais antigo; as demais do mais antigo para o mais novo (variáveis por nome).
Segredos
Seção intitulada “Segredos”Variáveis marcadas sensitive nunca voltam em claro por padrão (value: null); a resposta de PUT nunca ecoa um valor sensível; logs saem com segredos mascarados. Credenciais de banco só saem em POST …/credentials (nunca GET), com env:reveal, e a leitura é auditada.
Limites
Seção intitulada “Limites”- Plano do time: serviços, memória, volumes, bancos e deploys por hora (
GET /v1/limits). Ao atingir ⇒422 quota_exceeded. - Corpo JSON até 16 KiB (variáveis até 96 KiB por variável e 1 MiB no lote; serviços e templates até 64 KiB); código-fonte (multipart) até 512 MB.
- Galerias: 25 MB por arquivo (imagens também limitadas a 40 megapixels); cota por time no bucket compartilhado de 5 GB e 10 000 arquivos. Nomes de arquivo: até 4 segmentos separados por
/(3 barras), cada um em[a-z0-9._-], até 200 caracteres, sem.., sem segmento iniciando com.e com o primeiro segmento diferente de 12 caracteres hexadecimais minúsculos; únicos dentro da galeria. Nome inválido ⇒400 invalid_request. - Rate-limit por token e por IP: ao receber
429, espereRetry-Aftersegundos.
Classes de arquivo e como cada uma é servida
Seção intitulada “Classes de arquivo e como cada uma é servida”O tipo é detectado pelo conteúdo (magic bytes), nunca pela extensão ou pelo Content-Type enviado. Extensão divergente do conteúdo em executável/documento é recusada (415 extension_mismatch).
| Classe | Exemplos | Servido como |
|---|---|---|
image |
png, jpeg, webp, gif, avif | inline, transformável (?w=, ?q=) |
svg |
svg | inline só se passar na varredura de SVG, com CSP restritiva |
font |
woff2, woff, ttf, otf | inline, CORS * |
media |
mp4, webm, mp3, ogg | inline, com Accept-Ranges |
document |
pdf, txt, csv, json | Content-Disposition: attachment; texto sempre text/plain |
markup |
html, htm, xhtml, xml | nunca renderiza: attachment + text/plain + CSP sandbox |
archive |
zip, tar.gz, 7z | attachment; arquivo cifrado ou impossível de varrer é recusado |
binary |
exe, msi, dmg, apk, deb, appimage, desconhecido | attachment, application/octet-stream |
Varredura (nada é publicado antes de clean)
Seção intitulada “Varredura (nada é publicado antes de clean)”Todo upload entra em quarentena e passa por antivírus, regras YARA, reputação por hash (só o hash sai da nossa infra), varredura específica de SVG e decodificação segura de imagens. Detectado ⇒ 422 e a amostra não é guardada. Scanner indisponível ⇒ o arquivo fica pending e não é publicado (resposta 202; consulte o GET do arquivo até scan.status sair de pending). Todo dia os arquivos publicados são reescaneados com as assinaturas novas: uma detecção move o arquivo para quarantined (deixa de ser servido, responde 451, o cache é purgado e o time é avisado).
Todo erro tem o formato {"error": "<code>", "message": "…", "details"?: {…}}; message é legível (pt-BR) e error é estável para tratamento programático (enumerado em Error.error). Algumas respostas trazem details (ex.: references em unresolved_reference/reference_cycle: apenas nomes, nunca valores). Os 5xx e 429 trazem Retry-After quando repetir faz sentido. Nos corpos de serviço, variável e deploy, um campo desconhecido (ex.: memory_mb solto no topo; o certo é {"resources": {"memory_mb": 1024}}) é recusado com 400 invalid_request, e a mensagem nomeia o campo, em vez de ser ignorado em silêncio.
| Status | error |
|---|---|
| 400 | invalid_request, bad_json, bad_body |
| 401 | unauthorized (genérico de propósito: token ausente, inválido, expirado ou revogado) |
| 403 | forbidden |
| 404 | not_found |
| 409 | name_taken, slug_taken, project_quota_exceeded, project_not_empty, deploy_in_progress, nothing_to_rollback, not_cancelable, sensitive_lowered, duplicate_variable, domain_taken, volume_exists, volume_in_use, volume_attached, backup_not_ready, gallery_not_empty, idempotency_conflict (upload de galeria) |
| 413 | too_large, payload_too_large |
| 415 | unsupported_media_type, unsupported_type, extension_mismatch, class_not_allowed |
| 422 | quota_exceeded, idempotency_conflict, unresolved_reference, reference_cycle, invalid_reference, reference_too_deep, value_too_large, malware_detected, svg_unsafe, archive_encrypted, archive_bomb, archive_unsupported |
| 429 | rate_limited |
| 500 | internal |
| 503 | unavailable, authz_unavailable, database_unavailable |
Espelho do painel
Seção intitulada “Espelho do painel”O painel usa as mesmas operações em /api/teams/{tid}/… (sessão + CSRF, com confirmação de identidade em ações destrutivas). Essas rotas não fazem parte desta API pública e não aceitam Bearer.
Servidores
Seção intitulada “Servidores”https://api.nebularws.com— Produção
Esquemas de segurança
Seção intitulada “Esquemas de segurança”bearer: http (bearer) Token de API de time (nbk_…) criado em Time → Configurações → Tokens de API. Somente no cabeçalhoAuthorization: Bearer; nunca em query string. Cookies são ignorados nestas rotas.