Mídia
A Mídia é o utilitário de upload compartilhado da API, para imagens e vídeos. Em vez de cada recurso ter seu próprio upload, você sobe o arquivo uma vez aqui, recebe um identificador e depois vincula esse identificador ao recurso final.
Hoje quem consome a Mídia é:
- Produtos: a galeria do produto (Produtos › Galeria) e o vídeo do SKU.
- Anúncios: as imagens da publicação (Anúncios) e o vídeo do anúncio (Anúncios › Imagens).
Imagem e vídeo têm fluxos diferentes: a imagem sobe numa única chamada
multipart/form-data; o vídeo sobe direto no storage por URL pré-assinada, em três chamadas. Comece pelas imagens abaixo e veja Vídeos para o segundo fluxo.
Como funciona
O upload é síncrono e em uma única chamada (multipart/form-data): você manda o arquivo e, na mesma resposta, recebe a imagem já processada. O servidor converte tudo para WebP, gera automaticamente as variações de tamanho (thumbnail, sm, md, lg, xl) e devolve as URLs públicas de cada uma. Você não precisa redimensionar nada do seu lado.
O identificador da imagem é o campo id (um ULID). É esse valor que você usa como image_id ao vincular a um produto ou anúncio, e como {file_hash} nas rotas de detalhe e exclusão.
Ciclo de vida
A imagem nasce solta (in_use: false, sem reference_id). Ela só passa a contar como "em uso" quando você a vincula a um recurso. Por exemplo, ao adicioná-la à galeria com POST /api/products/{id}/gallery passando o id como image_id.
- Solta: imagens sem vínculo são removidas por uma rotina de limpeza da plataforma (janela de ~72h). Suba a imagem só quando for usá-la em seguida.
- Em uso: uma imagem
in_use: truefica protegida: qualquer tentativa de excluir ou alterar retorna 409. É uma trava de segurança: evita que você derrube sem querer uma foto que está aparecendo num produto ou anúncio publicado. Quando quiser mesmo remover, tire o vínculo no recurso primeiro (ex.: remova da galeria do produto) e aí a exclusão libera.
Subir uma imagem
Envie como multipart/form-data no campo image:
| Regra | Valor |
|---|---|
| Campo | image (arquivo) |
| Formatos aceitos | image/jpeg, image/png, image/webp |
| Tamanho máximo | 10 MB |
curl -X POST https://api.unisupri.com/api/media/upload \
-H "Authorization: Bearer sk_live_a1b2c3d4e5f6..." \
-F "image=@/caminho/para/foto.jpg"Resposta 201. O arquivo já vem convertido para WebP, com uma URL por tamanho:
{
"success": true,
"data": {
"id": "01K8PBIMG00001VWXYZ12345678",
"original_source": "https://cdn.unisupri.com/products/2026/05/28/original_01K8PBIMG00001VWXYZ12345678.webp",
"urls": [
{ "name": "thumbnail", "size": "135x135", "url": "https://cdn.unisupri.com/.../thumbnail_01K8PBIMG....webp" },
{ "name": "sm", "size": "200x200", "url": "https://cdn.unisupri.com/.../sm_01K8PBIMG....webp" },
{ "name": "md", "size": "400x400", "url": "https://cdn.unisupri.com/.../md_01K8PBIMG....webp" },
{ "name": "lg", "size": "800x800", "url": "https://cdn.unisupri.com/.../lg_01K8PBIMG....webp" },
{ "name": "xl", "size": "1600x1600", "url": "https://cdn.unisupri.com/.../xl_01K8PBIMG....webp" }
],
"in_use": false,
"reference_id": null,
"reference_model": null,
"created_at": "2026-05-28T10:00:00-03:00",
"updated_at": "2026-05-28T10:00:00-03:00"
}
}Listar imagens
Lista paginada das imagens da sua loja. Filtros:
| Parâmetro | Para quê |
|---|---|
filter[reference_id] | Imagens vinculadas a um recurso específico (ID do produto/anúncio). |
filter[reference_model] | Tipo do recurso vinculado. Os que interessam à integração: product (produto), publication (anúncio) e store (loja). |
filter[in_use] | true (já vinculadas) ou false (soltas). |
sort | created_at ou updated_at (prefixe com - para descendente). |
per_page | Itens por página (1 a 100, default 15). |
Reaproveitar fotos de SKUs
Duas rotas atalham o caso "já tenho as fotos nesse SKU, quero as mesmas em outro lugar", sem baixar e reenviar arquivo:
O GET consulta, para uma lista de SKUs da sua loja, as imagens já na galeria de cada um. SKU que não é seu volta com status: "sku_not_found" e lista vazia. O POST cria cópias dessas imagens (novas imagens, novos ids) para você vincular onde quiser. Campos e limites nos schemas da referência.
Detalhes de uma imagem
Retorna os mesmos dados do upload para a imagem cujo id você informar. 404 se ela não existir ou não pertencer à sua loja.
Excluir uma imagem
Remove o registro e os arquivos no storage. Retorna 204. Se a imagem estiver em uso por um produto ou anúncio (in_use: true), retorna 409: desvincule antes.
Vídeos
Um vídeo por produto (SKU) e um por anúncio. O identificador é o video_id (também um ULID), e o ciclo de vida é o mesmo das imagens: nasce solto, vira in_use quando você vincula, e vídeo solto é limpo pela rotina da plataforma.
A diferença está no upload. O arquivo de vídeo é grande demais para passar pela API, então ele vai direto para o storage por uma URL pré-assinada, o mesmo mecanismo dos anexos do Chat.
Limites
Não há conversão do nosso lado: o arquivo é aceito ou recusado como veio. Converta antes de enviar.
| Regra | Valor |
|---|---|
| Formato | video/mp4 |
| Codec de vídeo | H.264 |
| Codec de áudio | AAC, MP3 ou sem áudio |
| Tamanho máximo | 100 MB |
| Duração máxima | 90 segundos |
| Validade da URL de upload | 15 minutos |
O fluxo em três chamadas
1. Reserve o slot. Informe nome, tipo e tamanho do arquivo:
curl -X POST https://api.unisupri.com/api/media/videos/intent \
-H "Authorization: Bearer sk_live_a1b2c3d4e5f6..." \
-H "Content-Type: application/json" \
-d '{ "file_name": "demo.mp4", "mime_type": "video/mp4", "size_bytes": 8342100 }'Resposta 201:
{
"success": true,
"data": {
"video_id": "01K8PBVID00001VWXYZ12345678",
"upload_url": "https://cdn.unisupri.com/cdn-content/2026/08/17/video_01K8PBVID00001VWXYZ12345678.mp4?X-Amz-Signature=...",
"method": "PUT",
"expires_at": "2026-08-17T10:15:00-03:00"
}
}2. Suba o arquivo.PUT no upload_url, com o binário no corpo, sem o header Authorization (a assinatura já está na URL):
curl -X PUT "https://cdn.unisupri.com/cdn-content/..." \
-H "Content-Type: video/mp4" \
--upload-file /caminho/para/demo.mp43. Confirme. Só então a plataforma conta o vídeo como enviado:
curl -X POST https://api.unisupri.com/api/media/videos/01K8PBVID00001VWXYZ12345678/confirm \
-H "Authorization: Bearer sk_live_a1b2c3d4e5f6..."O confirm relê tamanho e tipo direto do storage: o que você declarou no passo 1 não é palavra final. Arquivo que não chegou ao storage volta 422; arquivo que estourou os 100 MB volta 422 e é descartado do storage. Passando, o vídeo entra na fila de processamento. A chamada é idempotente: repetir o confirm não reprocessa nada.
Estados do vídeo
O processamento é assíncrono: o confirm devolve o vídeo em uploaded/processing, e é o status que diz quando ele pode ser usado.
status | O que significa |
|---|---|
reserved | Slot criado no intent, arquivo ainda não enviado. |
uploaded | Arquivo no storage, na fila de processamento. |
processing | Em análise (codec, duração, extração da capa). |
ready | Pronto para vincular. Só neste estado o vídeo pode ir para um produto ou anúncio. |
rejected | Recusado por regra de conteúdo (codec, áudio ou duração). O motivo vem em rejection_reason. |
failed | Erro no processamento. Suba de novo. |
Consulte com GET/api/media/videos/{id} até chegar a ready (alguns segundos, na prática). 404 se o vídeo não for da sua loja.
{
"success": true,
"data": {
"id": "01K8PBVID00001VWXYZ12345678",
"url": "https://cdn.unisupri.com/cdn-content/2026/08/17/video_01K8PBVID00001VWXYZ12345678.mp4",
"duration_seconds": 32.5,
"width": 1080,
"height": 1920,
"poster": {
"id": "01K8PBIMG00009VWXYZ12345678",
"original_source": "https://cdn.unisupri.com/.../original_01K8PBIMG....webp",
"resources": [
{ "name": "sm", "size": "200x200", "url": "https://cdn.unisupri.com/.../sm_01K8PBIMG....webp" },
{ "name": "lg", "size": "800x800", "url": "https://cdn.unisupri.com/.../lg_01K8PBIMG....webp" }
]
},
"status": "ready",
"rejection_reason": null,
"size_bytes": 8342100
}
}O poster é a capa: um frame extraído do vídeo e publicado como imagem WebP nos tamanhos de sempre. Use no atributo poster do seu player. Ele é best-effort: se a extração falhar o vídeo continua ready, só que com poster: null. Trate esse caso no front.
status, rejection_reason e size_bytes só aparecem para você, o dono. Nas respostas públicas (vitrine, página do produto) o vídeo vem sem esses campos, e vídeo que não está ready simplesmente não aparece.
Vincular a um produto ou anúncio
PUT/api/products/{id}/gallery/video
Com o video_id em ready, mande { "video_id": "01K8PB..." } para vincular ao SKU, e { "video_id": null } para desvincular. Para anúncios, o campo video_id vai no corpo do create/update: veja Anúncios › Imagens.
| Erro | Quando |
|---|---|
| 404 | O video_id não existe ou não é da sua loja. (No create/update de anúncio, id inexistente volta 422 pela validação do payload; id de outra loja volta 404.) |
| 422 | O vídeo ainda não está ready (ou foi rejected/failed). |
| 409 | O vídeo já está vinculado a outro produto ou anúncio. |
Um vídeo pertence a um único recurso. Para usar o mesmo conteúdo no produto e no anúncio, veja a clonagem abaixo.
Reaproveitar o vídeo de um SKU
POST/api/media/videos/clone-from-sku
Mesma ideia do clone de fotos, na versão de vídeo: informe { "sku": "..." } e a plataforma copia o vídeo daquele SKU num registro novo (arquivo e capa próprios), pronto para vincular a um anúncio. Sem download nem reenvio.
Sempre 200, com um status:
status | O que aconteceu |
|---|---|
cloned | Cópia criada. O vídeo vem em data.video. |
no_video | O SKU não tem vídeo, ou o vídeo ainda não está ready. |
sku_not_found | O SKU não existe ou não é da sua loja. |
failed | A cópia no storage falhou. Tente de novo. |
O GET/api/media/skus-gallery também devolve o campo video de cada SKU: dá para descobrir num só request o que existe antes de clonar.
Excluir um vídeo
Remove o registro, o arquivo e a capa. Retorna 204. Vídeo em uso por um produto ou anúncio retorna 409: desvincule antes (video_id: null no recurso).
Retenção
- Um intent que nunca recebeu upload (
reserved) é descartado depois de ~1 hora. Reserve o slot na hora de subir o arquivo, não antes. - Um vídeo solto (nunca vinculado) é removido pela limpeza diária, na mesma janela das imagens (~72h).

