Pular para o conteúdo

Galerias e arquivos

Parâmetros: project (filtra por projeto) e cursor (veja a receita de paginação, a receita “Listar com cursor” abaixo).

Janela do terminal
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.

name (1 a 80 caracteres) é obrigatório; project_id (opcional) apenas organiza a galeria sob um projeto.

Janela do terminal
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.

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).

Janela do terminal
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.

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.

Janela do terminal
# repetir a mesma chave com o mesmo arquivo devolve a mesma resposta
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", "versions_count": 1, "alias_url": "https://media.nebularcdn.com/marcas-k7p2/logos/acme.png"}

Mesma chave, arquivo diferente:

Janela do terminal
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."}

Parâmetros: prefix (só nomes que começam com ele) e cursor.

Janela do terminal
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).

Janela do terminal
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"
}
]
}

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.

Janela do terminal
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/null
curl -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.

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).

Janela do terminal
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):

Janela do terminal
delay=2
for 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á pendente
  1. Suba a imagem e guarde a alias_url:
Janela do terminal
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'
  1. Um loader de next/image que acrescenta ?w=&q= (larguras do next/image já estão na lista permitida):
lib/nebularLoader.ts
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();
}
  1. Libere o host e use o loader (o src é a alias_url sem parâmetros):
next.config.ts
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.

Janela do terminal
fail=0
for 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
done
done
[ "$fail" = 0 ]

Arquivos que voltarem 202 ainda precisam do polling da Uploads pending antes de a URL existir.

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.

Janela do terminal
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.json
test "$(jq -r '.alias_url' nova.json)" = "$alias_antes" # a alias_url não mudou
test "$(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.

Siga next_cursor enquanto has_more for true. O cursor é opaco: não monte nem altere.

Janela do terminal
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')
done

O mesmo vale para GET /v1/galleries.