Skip to main content
Imagens não são enviadas inline na API. Você manda URLs e a Voop baixa de forma assíncrona. Isso permite cargas de 100k+ produtos sem timeout, sem upload manual, e com dedup global.

Lifecycle de uma URL

                 ┌────────────────────────────────────────────────┐
 cliente:        │ POST /items/upsert                              │
 envia URLs   ──▶│ media: [{ url: "https://...", role: "primary" }]│
                 └────────────────────────────────────────────────┘


                 ┌────────────────────────────────────────────────┐
                 │ CatalogMediaSource: status=pending             │
                 │ (dedup por URL normalizada)                     │
                 └────────────────────────────────────────────────┘

                                      ▼ (BullMQ job)
                 ┌────────────────────────────────────────────────┐
                 │ catalogMediaIngestWorker                       │
                 │  1. HEAD com If-None-Match (ETag dedup)        │
                 │  2. Per-domain semaphore (10 simultâneos)      │
                 │  3. GET streaming → R2 (sem buffer em RAM)     │
                 │  4. SHA-256 hash em paralelo                   │
                 └────────────────────────────────────────────────┘


                 ┌────────────────────────────────────────────────┐
                 │ MediaAsset criado em R2 (com replicação GCS)   │
                 │ ProductMedia rows materializados (item ↔ asset) │
                 └────────────────────────────────────────────────┘


                 ┌────────────────────────────────────────────────┐
                 │ Webhook: media.ingested                        │
                 └────────────────────────────────────────────────┘

Dedup duplo

1. Por URL normalizada

A mesma URL usada em N produtos = 1 download, 1 asset, N referências. Antes de hashear, a URL é normalizada:
  • Host em lowercase
  • Fragment removido (#section)
  • Query params ordenados alfabeticamente
  • Tracking params removidos (utm_*, v, t, ref, fbclid, gclid)
Resultado: https://CDN.X.com/img.jpg?utm=x&keep=y e https://cdn.x.com/img.jpg?keep=y são tratadas como a mesma fonte.

2. Por ETag (HTTP)

Antes de re-baixar, a Voop faz HEAD com If-None-Match:
  • 304 Not Modified → status skipped_unchanged, zero custo
  • 200 OK → re-baixa apenas se conteúdo mudou

Validação automática

Antes do download, a Voop verifica:
CheckLimiteStatus quando falha
Content-Length50 MBskipped_oversized
Content-Typeimage/* (não SVG)skipped_invalid_type
SVG é bloqueado por risco de XSS quando renderizado em navegadores.

Per-domain rate limit

Para não sobrecarregar o servidor de origem do cliente, a Voop limita a 10 downloads simultâneos por domínio. Subdomínios são pools separados: cdn.example.com e assets.example.com têm 10 slots cada.
Se seu CDN tem rate limit próprio (ex: Cloudflare Free com 100 req/s), considere hospedar imagens em um subdomínio dedicado para que outros clientes da Voop não compitam pelo mesmo pool com você.

Status de uma source

pending             — registrada, aguardando worker
fetching            — em download
stored              — sucesso, asset criado
failed              — erro (4xx, 5xx, timeout)
skipped_unchanged   — ETag bate, sem mudança
skipped_oversized   — > 50 MB
skipped_invalid_type — tipo não suportado
Você pode consultar o status de cada item em GET /items/{externalSystem}/{externalId} — o array media retorna o estado atual.

Webhooks de mídia

media.ingested            — sucesso após download
media.ingestion_failed    — falha após attempts esgotados
Use esses webhooks para reagir a falhas no seu pipeline (ex: notificar o time de catálogo que uma imagem está quebrada).

Re-tentativa manual

Se uma source ficou failed por motivo transitório (CDN do cliente caiu por 1h), o worker tenta automaticamente até 5 vezes com backoff exponencial (30s, 60s, 2min, 4min, 8min). Após esgotado, você pode disparar re-ingest fazendo um upsert do mesmo item com a mesma URL — o backend re-enfileira automaticamente se o status for não-terminal.