Galerias e arquivos
Galerias
Seção intitulada “Galerias”Listar galerias (assets:read)
Seção intitulada “Listar galerias (assets:read)”Parâmetros: project (filtra por projeto) e cursor (veja a receita de paginação, a receita “Listar com cursor” abaixo).
curl -sS "$NEBULAR_API/v1/galleries" \ -H "Authorization: Bearer $NEBULAR_TOKEN"{ "items": [ { "id": "gal_01J9ZK5Q", "key": "marcas-k7p2", "name": "Marcas", "bucket": {"id": "bkt_01J9ZG00", "label": "media", "kind": "shared", "host": "media.nebularcdn.com"}, "project": null, "assets_count": 12, "bytes": 4823910, "created_at": "2026-10-09T14:03:11Z" } ], "next_cursor": null, "has_more": false}key é a identificação pública e imutável da galeria (nomes podem mudar): é ela que vai nas rotas e nas URLs.
Criar galeria (galleries:manage)
Seção intitulada “Criar galeria (galleries:manage)”name (1 a 80 caracteres) é obrigatório; project_id (opcional) apenas organiza a galeria sob um projeto.
curl -sS -X POST "$NEBULAR_API/v1/galleries" \ -H "Authorization: Bearer $NEBULAR_TOKEN" \ -H "Content-Type: application/json" \ -d '{"name": "Marcas"}'{ "id": "gal_01J9ZK5Q", "key": "marcas-k7p2", "name": "Marcas", "bucket": {"id": "bkt_01J9ZG00", "label": "media", "kind": "shared", "host": "media.nebularcdn.com"}, "project": null, "assets_count": 0, "bytes": 0, "created_at": "2026-10-09T14:03:11Z"}Guarde o key retornado (nos exemplos: $GALLERY). Cada chamada cria uma galeria nova, então não repita sem necessidade.
Arquivos
Seção intitulada “Arquivos”Enviar ou versionar (assets:write)
Seção intitulada “Enviar ou versionar (assets:write)”Corpo multipart/form-data com file (obrigatório) e name (opcional; padrão: o nome do arquivo enviado
normalizado). Se o nome já existir, cria uma nova versão e o alias passa a apontar para ela. Enviar o mesmo
conteúdo (mesmo SHA-256) para o mesmo nome não cria versão nova.
Respostas: 201 (limpo e já publicado), 202 (pending: ainda não publicado, veja Uploads pending) ou um erro (Erros).
curl -sS -X POST "$NEBULAR_API/v1/galleries/$GALLERY/assets" \ -H "Authorization: Bearer $NEBULAR_TOKEN" \ -H "Idempotency-Key: logo-acme-2026-10-09" \ -F "file=@logo.png" \ -F "name=logos/acme.png"{ "id": "ast_01J9ZK8W", "name": "logos/acme.png", "gallery_key": "marcas-k7p2", "current": { "id": "ver_01J9ZK8X", "hash": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08", "bytes": 48213, "content_type": "image/png", "class": "image", "width": 1200, "height": 630, "variants": ["320", "640", "1280"], "scan": {"status": "clean", "reason": null, "engine": "clamav", "scanned_at": "2026-10-09T14:05:02Z"}, "url": "https://media.nebularcdn.com/marcas-k7p2/9f86d081884c/logos/acme.png", "uploaded_by": {"kind": "token", "name": "agente-deploy"}, "created_at": "2026-10-09T14:05:01Z" }, "alias_url": "https://media.nebularcdn.com/marcas-k7p2/logos/acme.png", "versions_count": 1, "created_at": "2026-10-09T14:05:01Z"}Use sempre a url/alias_url devolvidas pela API, sem montá-las à mão (o domínio de classes de download pode mudar).
url e alias_url são null enquanto o arquivo não estiver clean.
Idempotência (Idempotency-Key)
Seção intitulada “Idempotência (Idempotency-Key)”Envie Idempotency-Key (até 255 caracteres; um UUID ou algo derivado do conteúdo) em todo upload automatizado.
Mesma chave com o mesmo corpo devolve a mesma resposta, então repetir após timeout ou erro de rede é seguro e não cria
versões duplicadas. Mesma chave com corpo diferente devolve 409 idempotency_conflict: gere uma chave nova.
# repetir a mesma chave com o mesmo arquivo devolve a mesma respostacurl -sS -X POST "$NEBULAR_API/v1/galleries/$GALLERY/assets" \ -H "Authorization: Bearer $NEBULAR_TOKEN" \ -H "Idempotency-Key: logo-acme-2026-10-09" \ -F "file=@logo.png" \ -F "name=logos/acme.png"{"id": "ast_01J9ZK8W", "name": "logos/acme.png", "versions_count": 1, "alias_url": "https://media.nebularcdn.com/marcas-k7p2/logos/acme.png"}Mesma chave, arquivo diferente:
curl -sS -X POST "$NEBULAR_API/v1/galleries/$GALLERY/assets" \ -H "Authorization: Bearer $NEBULAR_TOKEN" \ -H "Idempotency-Key: logo-acme-2026-10-09" \ -F "file=@banner.png" \ -F "name=logos/acme.png"{"error": "idempotency_conflict", "message": "Esta Idempotency-Key já foi usada com outro conteúdo. Gere uma chave nova."}Listar arquivos (assets:read)
Seção intitulada “Listar arquivos (assets:read)”Parâmetros: prefix (só nomes que começam com ele) e cursor.
curl -sS -G "$NEBULAR_API/v1/galleries/$GALLERY/assets" \ --data-urlencode "prefix=logos/" \ -H "Authorization: Bearer $NEBULAR_TOKEN"{ "items": [ { "id": "ast_01J9ZK8W", "name": "logos/acme.png", "gallery_key": "marcas-k7p2", "current": { "id": "ver_01J9ZK8X", "hash": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08", "bytes": 48213, "content_type": "image/png", "class": "image", "width": 1200, "height": 630, "variants": ["320", "640", "1280"], "scan": {"status": "clean", "reason": null, "engine": "clamav", "scanned_at": "2026-10-09T14:05:02Z"}, "url": "https://media.nebularcdn.com/marcas-k7p2/9f86d081884c/logos/acme.png", "uploaded_by": {"kind": "token", "name": "agente-deploy"}, "created_at": "2026-10-09T14:05:01Z" }, "alias_url": "https://media.nebularcdn.com/marcas-k7p2/logos/acme.png", "versions_count": 1, "created_at": "2026-10-09T14:05:01Z" } ], "next_cursor": null, "has_more": false}Detalhe, versões e status de varredura (assets:read)
Seção intitulada “Detalhe, versões e status de varredura (assets:read)”O nome vai na rota; use %2F no lugar de cada / (a barra literal também é aceita).
curl -sS "$NEBULAR_API/v1/galleries/$GALLERY/assets/logos%2Facme.png" \ -H "Authorization: Bearer $NEBULAR_TOKEN"{ "asset": { "id": "ast_01J9ZK8W", "name": "logos/acme.png", "gallery_key": "marcas-k7p2", "current": { "id": "ver_01J9ZK8X", "hash": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08", "bytes": 48213, "content_type": "image/png", "class": "image", "width": 1200, "height": 630, "variants": ["320", "640", "1280"], "scan": {"status": "clean", "reason": null, "engine": "clamav", "scanned_at": "2026-10-09T14:05:02Z"}, "url": "https://media.nebularcdn.com/marcas-k7p2/9f86d081884c/logos/acme.png", "uploaded_by": {"kind": "token", "name": "agente-deploy"}, "created_at": "2026-10-09T14:05:01Z" }, "alias_url": "https://media.nebularcdn.com/marcas-k7p2/logos/acme.png", "versions_count": 1, "created_at": "2026-10-09T14:05:01Z" }, "versions": [ { "id": "ver_01J9ZK8X", "hash": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08", "bytes": 48213, "content_type": "image/png", "class": "image", "width": 1200, "height": 630, "variants": ["320", "640", "1280"], "scan": {"status": "clean", "reason": null, "engine": "clamav", "scanned_at": "2026-10-09T14:05:02Z"}, "url": "https://media.nebularcdn.com/marcas-k7p2/9f86d081884c/logos/acme.png", "uploaded_by": {"kind": "token", "name": "agente-deploy"}, "created_at": "2026-10-09T14:05:01Z" } ]}Excluir (assets:write)
Seção intitulada “Excluir (assets:write)”Exclui o arquivo e purga o cache da CDN (a URL passa a dar 404). O backup retém os bytes por 30 dias.
Resposta: 204 sem corpo.
curl -sS -X POST "$NEBULAR_API/v1/galleries/$GALLERY/assets" \ -H "Authorization: Bearer $NEBULAR_TOKEN" \ -F "file=@hero.png" -F "name=temp/hero.png" -o /dev/nullcurl -sS -X DELETE "$NEBULAR_API/v1/galleries/$GALLERY/assets/temp%2Fhero.png" \ -H "Authorization: Bearer $NEBULAR_TOKEN" -w 'HTTP %{http_code}\n'Depois disso o GET do mesmo nome devolve 404 not_found.
Uploads pending (202): como esperar
Seção intitulada “Uploads pending (202): como esperar”Se o scanner estiver ocupado ou fora do ar, o upload devolve 202 com scan.status = "pending", url e alias_url
null: o arquivo foi recebido mas não está publicado. Não reenvie. Consulte o detalhe do arquivo com
backoff (comece em 2 s, dobre a cada tentativa até 30 s) até scan.status deixar de ser pending:
clean (publicado, url/alias_url preenchidos) ou rejected (recusado; o campo scan.reason diz o motivo).
curl -sS -X POST "$NEBULAR_API/v1/galleries/$GALLERY/assets" \ -H "Authorization: Bearer $NEBULAR_TOKEN" \ -F "file=@slow.png" -F "name=site/lento.png"{ "id": "ast_01J9ZK8W", "name": "site/lento.png", "gallery_key": "marcas-k7p2", "current": { "id": "ver_01J9ZK8X", "hash": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08", "bytes": 48213, "content_type": "application/octet-stream", "class": "binary", "width": null, "height": null, "variants": [], "scan": {"status": "pending", "reason": null, "engine": null, "scanned_at": null}, "url": null, "uploaded_by": {"kind": "token", "name": "agente-deploy"}, "created_at": "2026-10-09T14:05:01Z" }, "alias_url": null, "versions_count": 1, "created_at": "2026-10-09T14:05:01Z"}Enquanto pending, content_type/class são provisórios (application/octet-stream/binary) e width/height são null;
valem os valores do GET depois que o status virar clean.
Polling com backoff (no máximo 8 tentativas):
delay=2for attempt in 1 2 3 4 5 6 7 8; do status=$(curl -sS "$NEBULAR_API/v1/galleries/$GALLERY/assets/site%2Flento.png" \ -H "Authorization: Bearer $NEBULAR_TOKEN" | jq -r '.asset.current.scan.status') echo "tentativa $attempt: $status" [ "$status" = "pending" ] || break sleep "$delay" delay=$(( delay * 2 > 30 ? 30 : delay * 2 ))done[ "$status" = "clean" ] # sai com erro se foi recusado ou se ainda está pendenteReceitas
Seção intitulada “Receitas”(a) Subir e usar no Next com next/image
Seção intitulada “(a) Subir e usar no Next com next/image”- Suba a imagem e guarde a
alias_url:
curl -sS -X POST "$NEBULAR_API/v1/galleries/$GALLERY/assets" \ -H "Authorization: Bearer $NEBULAR_TOKEN" \ -H "Idempotency-Key: hero-$(cksum < hero.png | tr ' ' '-')" \ -F "file=@hero.png" -F "name=site/hero.png" | jq -r '.alias_url'- Um loader de
next/imageque acrescenta?w=&q=(larguras donext/imagejá estão na lista permitida):
import type { ImageLoaderProps } from "next/image";
export default function nebularLoader({ src, width, quality }: ImageLoaderProps): string { const url = new URL(src); url.searchParams.set("w", String(width)); url.searchParams.set("q", String(quality ?? 75)); return url.toString();}- Libere o host e use o loader (o
srcé aalias_urlsem parâmetros):
import type { NextConfig } from "next";
const config: NextConfig = { images: { loader: "custom", loaderFile: "./lib/nebularLoader.ts", },};export default config;import Image from "next/image";
export function Hero() { return ( <Image src="https://media.nebularcdn.com/marcas-k7p2/site/hero.png" alt="Banner principal" width={1200} height={630} sizes="100vw" /> );}Como o alias sempre aponta para a versão atual, trocar a imagem (receita c) atualiza o site sem novo deploy.
(b) Subir em lote com laço shell (429, Retry-After e idempotência)
Seção intitulada “(b) Subir em lote com laço shell (429, Retry-After e idempotência)”A Idempotency-Key derivada do conteúdo torna o laço seguro de repetir inteiro: o que já foi enviado não gera versão nova.
Erros permanentes (4xx exceto 429) não são repetidos; 429 e 503 esperam Retry-After.
fail=0for f in imagens/*.png; do name="lote/$(basename "$f")" key="lote-$name-$(cksum < "$f" | tr ' ' '-')" for try in 1 2 3 4 5; do code=$(curl -sS -o resp.json -D hdr.txt -w '%{http_code}' \ -X POST "$NEBULAR_API/v1/galleries/$GALLERY/assets" \ -H "Authorization: Bearer $NEBULAR_TOKEN" \ -H "Idempotency-Key: $key" \ -F "file=@$f" -F "name=$name") case "$code" in 201|202) echo "$name -> $code"; break ;; 429|503) wait=$(awk -F': ' 'tolower($1)=="retry-after"{print $2+0}' hdr.txt) echo "$name -> $code, aguardando ${wait:-5}s (tentativa $try)" >&2 sleep "${wait:-5}" ;; *) echo "$name FALHOU ($code): $(cat resp.json)" >&2; fail=1; break ;; esac donedone[ "$fail" = 0 ]Arquivos que voltarem 202 ainda precisam do polling da Uploads pending antes de a URL existir.
(c) Trocar uma imagem mantendo a alias_url
Seção intitulada “(c) Trocar uma imagem mantendo a alias_url”Envie o novo arquivo com o mesmo name: nasce uma nova versão e o alias passa a servir a nova imagem; a url
imutável antiga continua válida (e imutável). Não precisa mudar nada em quem usa a alias_url.
alias_antes=$(curl -sS "$NEBULAR_API/v1/galleries/$GALLERY/assets/site%2Fhero.png" \ -H "Authorization: Bearer $NEBULAR_TOKEN" | jq -r '.asset.alias_url')
curl -sS -X POST "$NEBULAR_API/v1/galleries/$GALLERY/assets" \ -H "Authorization: Bearer $NEBULAR_TOKEN" \ -F "file=@logo-v2.png" -F "name=site/hero.png" > nova.json
jq -r '"versões: \(.versions_count)"' nova.jsontest "$(jq -r '.alias_url' nova.json)" = "$alias_antes" # a alias_url não mudoutest "$(jq -r '.versions_count' nova.json)" = "2"Se a alias_url ainda mostrar a imagem antiga por instantes, é o cache de 60 s do alias; a url imutável da nova
versão já serve o novo conteúdo.
(d) Listar com cursor (next_cursor / has_more)
Seção intitulada “(d) Listar com cursor (next_cursor / has_more)”Siga next_cursor enquanto has_more for true. O cursor é opaco: não monte nem altere.
cursor=""while :; do url="$NEBULAR_API/v1/galleries/$GALLERY/assets" [ -n "$cursor" ] && url="$url?cursor=$cursor" page=$(curl -sS "$url" -H "Authorization: Bearer $NEBULAR_TOKEN") echo "$page" | jq -r '.items[] | "\(.name)\t\(.alias_url)"' [ "$(echo "$page" | jq -r '.has_more')" = "true" ] || break cursor=$(echo "$page" | jq -r '.next_cursor')doneO mesmo vale para GET /v1/galleries.