Skip to main content
Duas coisas podem ser enviadas: o avatar de um usuário e o logo de uma organização. As duas funcionam igual, e nenhuma manda bytes pela API.
Quando o servidor não tem storage configurado, as duas rotas respondem 501 uploads_unavailable e GET /v1/session informa uploads_available: false — leia essa flag antes de mostrar um botão de upload, em vez de esbarrar no 501 no meio do fluxo.

O fluxo

1

Peça uma URL assinada

2

Faça o PUT direto no bucket

Envie exatamente os headers que voltaram — eles fazem parte da assinatura.
3

Salve a URL pública

Salvar é uma chamada separada de propósito. O upload pode falhar no meio, e um perfil apontando para um objeto escrito pela metade seria pior que um ainda apontando para a imagem antiga.

O logo da organização

Idêntico, com duas diferenças: exige organization:update, e a URL é salva com PATCH /v1/organization.

O que é permitido

Uma allowlist, não uma blocklist — deliberadamente. Esses arquivos são servidos de volta a partir de um host público, e text/html entre eles seria um vetor de XSS armazenado naquele host. O tipo e o tamanho fazem parte da assinatura, então é o bucket que os impõe. A verificação no lado da API existe para te dar um erro legível, não para ser a imposição.

Substituindo uma imagem

Cada upload ganha uma chave de objeto aleatória, então substituir uma imagem nunca sobrescreve o objeto antigo. Uma URL em cache num CDN ou num e-mail não pode começar a servir a foto de outra pessoa. O objeto antigo fica para trás. Limpar é um job de manutenção, não parte da requisição.

Fazendo isso do navegador

Os três passos funcionam igual a partir do código no cliente. A URL pré-assinada é de vida curta e delimitada a um objeto, um content type e um tamanho, então entregá-la ao navegador não dá nada reutilizável.
Um token de sessão no código do navegador é tão seguro quanto o lugar onde você o guarda. Se o seu app tem servidor, o formato que vale copiar é assinar a primeira e a terceira chamadas lá e deixar o navegador fazer só o PUT — o envio para o bucket é o único dos três que precisa sair do dispositivo que tem o arquivo.