Autenticação
A API do marketplace UniSupri usa autenticação bearer token em todos os endpoints de seller. O token é obtido a partir de uma credencial (username + senha) criada no Portal do Vendedor. Leia Primeiros Passos se ainda não criou a sua.
Modelo
Dois conceitos distintos:
| Conceito | Onde mora | Quem usa | Quando renova |
|---|---|---|---|
Credencial (username + senha) | Portal do Vendedor | Operador humano | Manualmente, quando suspeita de vazamento ou rotação programada |
Token de acesso (sk_live_*) | Memória do ERP | Sistema externo | Automaticamente via /api/integration/auth, antes de expirar |
A credencial fica parada no seu cofre de senhas e não vai em chamadas de API. O token é o que circula em cada requisição.
Permissões da credencial
Ao criar a credencial (no Portal do Vendedor, em Configurações → API & Integrações → Nova credencial), além do nome você escolhe quais acessos ela concede. O token herda exatamente essas permissões: ele só consegue chamar os endpoints cobertos pelos acessos marcados. Conceda o mínimo necessário para a integração.
| Acesso | O que libera | Escopos |
|---|---|---|
| Produtos | Catálogo, preços, estoque e locais de estoque | store_products_read, store_products_write, store_stock_locations_read, store_stock_locations_write |
| Anúncios | Publicação e gestão de anúncios | store_item_read, store_item_create, store_item_update |
| Pedidos | Leitura, avanço de status e geração de envio | store_orders_read, store_orders_manage, store_orders_create_shipment |
| Pedidos de sublojas | Estende os acessos de Pedidos às sublojas faturadas pela matriz — ver Matriz e sublojas | store_orders_substores |
| Fulfillment | Remessas e RMA (devoluções) | store_fulfillment_read, store_fulfillment_create_shipment, store_fulfillment_manage_rma |
| Atendimento | Conversas com o comprador | store_conversations_read, store_conversations_manage |
| Perguntas | Q&A dos anúncios | store_questions_read, store_questions_answer |
| Ocorrências | Ocorrências/disputas | store_occurrences_read, store_occurrences_manage |
| Clientes | Dados de clientes | store_customers_read, store_customers_manage |
| Financeiro | Leitura de relatórios financeiros | store_finance_read |
| Reputação | Avaliações da loja | store_rating_read |
Uma chamada a um endpoint fora das permissões da credencial retorna 403 Forbidden: marque o acesso correspondente no portal e gere uma nova credencial (ou ajuste a existente).
Vários acessos são exclusivos do Portal e não podem ser atribuídos a uma credencial de API — entre eles gestão de webhooks, gestão de tokens de API, integrações ERP, logística e transportadoras, força de vendas, dashboard e configurações da loja. Se você tentar criar uma credencial com um desses escopos, a API recusa com
422. No caso dos webhooks, isso continua valendo para a gestão pelo Portal — mas a credencial pode configurar o próprio webhook na chamada de autenticação, sem escopo nenhum: ver Configurar o webhook no login.
Obter um token
Endpoint público, não exige Authorization. A própria credencial (no body) é a forma de autenticação.
Request
{
"username": "loja-xpto@12345678",
"password": "Xk9p2Lm7nQ4rT8vW3yZ1"
}Response 200
{
"success": true,
"data": {
"token": "sk_live_a1b2c3d4e5f6...",
"expires_at": "2026-06-19T12:34:56-03:00",
"credential": {
"name": "ERP Bling",
"username": "loja-xpto@12345678",
"abilities": ["*"]
},
"store": {
"store_id": "01KYN1VYYBFRDF14ZDXVWFB2MY",
"store_name": "Loja XPTO",
"store_code": "LOJA-XPTO",
"store_status": "active",
"document": "12345678000190",
"company_name": "XPTO Comércio Ltda"
}
}
}Identificar a conta do token
A própria emissão já diz de quem é o token — útil pra quem integra várias lojas e precisa casar o token com o cadastro do lado dele, sem uma segunda chamada.
| Campo | O que é |
|---|---|
store.store_id | Identificador público da loja (ULID). É este id que aparece nos demais recursos da API — use ele como chave do seu lado. |
store.store_name | Nome da loja, como cadastrado no portal. |
store.store_code | Código interno da loja, quando definido. |
store.store_status | Situação da loja em minúsculas (ex.: active). Loja fora de active autentica, mas pode ter operações bloqueadas. |
store.document | CNPJ da empresa dona da loja, só dígitos. |
store.company_name | Razão social / nome da empresa. |
credential.name | Nome que o vendedor deu à credencial no portal (ex.: "ERP Bling") — identifica qual integração é, quando a loja tem várias. |
credential.username | O mesmo username usado na chamada, ecoado por conveniência. |
credential.abilities | Escopos da credencial. ["*"] = acesso total. |
Os campos token e expires_at continuam onde sempre estiveram; os blocos credential e store são adição, não substituição.
Se você precisa desses dados fora do momento da emissão (ex.: validar um token já em cache), use o GET /api/stores/auth/session, que devolve o cadastro completo da loja.
Rotação
Cada chamada bem-sucedida ao /api/integration/auth:
- Emite um token novo.
- Substitui o token anterior. O antigo deixa de funcionar imediatamente.
- Repopula
expires_atparaagora + 30 dias.
Implicação prática: só existe 1 token ativo por credencial. Se você roda dois sistemas que precisam de tokens independentes, crie duas credenciais separadas no portal.
Quando renovar
Renove antes de expirar. Recomendamos disparar uma nova chamada quando faltarem 24-48h pra expiração. Você não vai encontrar um endpoint de refresh-token separado: a renovação é a própria chamada ao /api/integration/auth (a mesma que você já usa pra obter o token). Um endpoint só, que serve tanto pra primeira emissão quanto pra renovar. Se o token expirar e você fizer uma chamada com ele, a API responde 401 e o ERP deve renovar e tentar de novo.
Configurar o webhook no login
Cadastrar webhook pelo Portal é trabalho técnico que cai no colo da pessoa errada: a URL é sua, os eventos são seus, o secret vai pro seu cofre. Por isso a autenticação aceita um bloco webhook opcional — o vendedor entrega só username e password, e sua integração se configura sozinha.
Request
{
"username": "loja-xpto@12345678",
"password": "Xk9p2Lm7nQ4rT8vW3yZ1",
"webhook": {
"url": "https://erp.exemplo.com/unisupri/webhook",
"events": ["*"],
"active": true,
"headers": { "X-Erp-Key": "..." }
}
}| Campo | Regra |
|---|---|
webhook | Opcional. Ausente (ou null) não cria, não altera e não apaga nada. |
webhook.url | Obrigatória quando o bloco existe. Precisa ser HTTPS, no máximo 2048 caracteres. |
webhook.events | Opcional. Ausente ou ["*"] assina tudo que a credencial pode. Aceita evento individual e nome de grupo. |
webhook.active | Opcional, true por padrão. falsepausa as entregas sem apagar a configuração. |
webhook.headers | Opcional. Cabeçalhos extras enviados em toda entrega (ex.: um Authorization do seu endpoint). |
Response 200
{
"success": true,
"data": {
"token": "sk_live_a1b2c3d4e5f6...",
"expires_at": "2026-09-05T12:34:56-03:00",
"credential": { "...": "..." },
"store": { "...": "..." },
"webhook": {
"id": "9f1c8a2e-4b7d-4c31-9f0a-2e6d8b1c5a44",
"url": "https://erp.exemplo.com/unisupri/webhook",
"events": ["order.created", "order.paid"],
"active": true,
"secret": "whsec_AT1JfFiUI1xfZZb40rXw1K08XaWnn0lwArgIbHQN",
"status": "created"
}
}
}| Campo | O que é |
|---|---|
id | Identificador do webhook. É o mesmo valor que chega em webhook_id no envelope de cada entrega. |
events | A lista resolvida: "*" e grupos já expandidos, e já filtrados pelas permissões da credencial. É exatamente o que você vai receber. |
secret | O signing secret, usado pra validar a assinatura. Vem em toda resposta que traga o bloco, não só na criação. |
status | created, updated ou unchanged — o que a chamada fez. |
Exemplo completo
Cenário real: um ERP que só se importa com pedidos e logística, cujo endpoint de recepção fica atrás de um gateway que exige um cabeçalho próprio, e que quer receber tudo num caminho versionado.
curl -X POST https://api.unisupri.com/api/integration/auth \
-H "Content-Type: application/json" \
-d '{
"username": "loja-xpto@12345678",
"password": "Xk9p2Lm7nQ4rT8vW3yZ1",
"webhook": {
"url": "https://erp.exemplo.com/v2/webhooks/unisupri",
"events": ["orders", "logistics", "occurrence.created"],
"active": true,
"headers": {
"X-Erp-Tenant": "loja-xpto",
"Authorization": "Bearer o-token-do-SEU-gateway"
}
}
}'Três coisas acontecendo aí:
eventsmistura grupo e evento avulso.orderselogisticsexpandem para os 11 eventos dessas áreas;occurrence.createdentra sozinho, sem trazeroccurrence.resolvedjunto.headersviaja em toda entrega. São os cabeçalhos que a UniSupri vai mandar para você — servem pra atravessar o seu gateway, seu WAF ou seu roteador de multi-tenant. Se você repetir um nome que a plataforma já usa (Content-Type,User-Agent), o seu valor substitui o padrão. OX-Webhook-Signaturecontinua sendo calculado normalmente.active: trueé o default, mas mandar explícito deixa o bloco autocontido — o mesmo JSON serve pra reativar um webhook que você pausou antes.
Resposta:
{
"success": true,
"data": {
"token": "sk_live_a1b2c3d4e5f6...",
"expires_at": "2026-09-05T12:34:56-03:00",
"credential": {
"name": "ERP Bling",
"username": "loja-xpto@12345678",
"abilities": ["store_orders_read", "store_fulfillment_read", "store_occurrences_read"]
},
"store": { "store_id": "01KYN1VYYBFRDF14ZDXVWFB2MY", "store_name": "Loja XPTO" },
"webhook": {
"id": "9f1c8a2e-4b7d-4c31-9f0a-2e6d8b1c5a44",
"url": "https://erp.exemplo.com/v2/webhooks/unisupri",
"events": [
"occurrence.created",
"order.cancelled",
"order.created",
"order.invoiced",
"order.paid",
"order.refunded",
"order.status_changed",
"shipment.cancelled",
"shipment.created",
"shipment.dispatched",
"shipment.out_for_delivery",
"shipment.status_changed"
],
"active": true,
"secret": "whsec_AT1JfFiUI1xfZZb40rXw1K08XaWnn0lwArgIbHQN",
"status": "created"
}
}
}Repare que events volta expandido e ordenado, com os 12 eventos resolvidos. É essa lista que vale — não a que você mandou.
Os
headersnão voltam na resposta. Eles ficam guardados na configuração e são aplicados nas entregas; se precisar trocar um, mande o objetoheadersinteiro de novo (ele é substituído, não mesclado). Mandar o blocowebhooksem a chaveheaderslimpa os cabeçalhos customizados.
Manutenção pelo mesmo endpoint
Tudo o que você precisa fazer depois passa pelo mesmo bloco:
| O que você quer | O que mandar | status de volta |
|---|---|---|
| Confirmar que está tudo certo | o mesmo bloco de sempre | unchanged |
| Trocar de endpoint | o bloco com a url nova | updated |
| Assinar mais um evento | o bloco com a lista nova, completa | updated |
| Pausar durante uma manutenção | "active": false | updated |
| Voltar do pause | "active": true | updated |
| Trocar o cabeçalho do gateway | o objeto headers inteiro | updated |
Em nenhum desses casos o id ou o secret mudam. Você não precisa (nem deve) apagar e recriar pra alterar qualquer campo.
Um webhook por credencial
A configuração é idempotente pela credencial, não pela URL. Mandar o mesmo bloco em todo login é o uso recomendado: a primeira chamada cria (created), as seguintes não fazem nada (unchanged).
Trocar a URL atualiza a configuração existente: mesmo id, mesmo secret, histórico de entregas preservado. Você não precisa (nem consegue) acumular webhooks por esse caminho — não existe o risco de, a cada deploy, nascer um webhook novo apontando pro endpoint antigo.
Para pausar, mande "active": false. Para apagar de vez, use o Portal ou revogue a credencial.
O webhook criado por aqui aparece no Portal do Vendedor marcado como gerido pela integração. O vendedor consegue ver, testar e consultar o histórico, mas não editar nem apagar — quem manda na configuração é o seu sistema. Se a loja precisar cortar as entregas na marra, o caminho é revogar a credencial.
Eventos aceitos
Você pode listar eventos individuais, nomes de grupo, ou "*" pra assinar tudo.
| Grupo | Eventos |
|---|---|
orders | order.created, order.paid, order.status_changed, order.cancelled, order.invoiced, order.refunded |
logistics | shipment.created, shipment.dispatched, shipment.out_for_delivery, shipment.status_changed, shipment.cancelled |
occurrences | occurrence.created, occurrence.resolved |
conversations | conversation.opened, conversation.message, conversation.closed |
stock | product.stock_updated, stock.depleted, stock.restored, product.price_updated |
catalog | publication.approved, publication.rejected |
{"events": ["orders", "logistics"]} é equivalente a listar os 11 eventos desses dois grupos.
O que cada evento entrega está no catálogo de payloads.
Os eventos seguem as permissões da credencial
"*" não significa "tudo que existe", e sim "tudo que esta credencial já poderia ler pela API". Uma credencial que só tem acesso a Produtos recebe eventos de estoque, mas não de pedidos — do contrário o webhook seria uma porta lateral para dados que a permissão nega.
| Grupo de eventos | Exige o acesso |
|---|---|
orders | Pedidos |
logistics | Pedidos ou Fulfillment |
occurrences | Ocorrências |
conversations | Atendimento |
stock | Produtos |
catalog | Anúncios |
Pedir explicitamente um evento fora das suas permissões devolve 422 dizendo qual evento e qual acesso falta. Se é pra receber, marque o acesso correspondente na credencial (Portal → Configurações → API & Integrações).
Quando a configuração é recusada
Bloco webhook inválido devolve 422 e não emite token. A resposta traz, em meta, o catálogo que aquela credencial aceita:
{
"success": false,
"code": 422,
"message_code": "VALIDATION_ERROR",
"errors": {
"webhook.events": ["Evento desconhecido: 'order.shipped'."]
},
"meta": {
"accepted_events": ["order.created", "order.paid"],
"accepted_groups": ["orders", "logistics", "occurrences", "conversations", "stock", "catalog"]
}
}422 aqui é erro de configuração, não falha temporária — não repita a chamada. Corrija o bloco usando o
meta.accepted_eventsda resposta. Enquanto o bloco estiver inválido, o login não emite token: se sua integração está no ar, remova o blocowebhookda chamada pra voltar a autenticar enquanto investiga.
Testar a assinatura
POST/api/integration/webhook/test
Autenticado pelo seu token. Enfileira uma entrega real do evento webhook.test no webhook que você configurou, com assinatura e tudo — serve pra validar a recepção ponta a ponta sem esperar um pedido de verdade acontecer.
curl -X POST https://api.unisupri.com/api/integration/webhook/test \
-H "Authorization: Bearer sk_live_a1b2c3d4e5f6..."{ "success": true, "data": { "queued": true, "event": "webhook.test", "webhook_id": "9f1c8a2e-..." } }A resposta confirma o enfileiramento, não a entrega: o resultado aparece no histórico do Portal em alguns instantes. Não use essa rota pra "acordar" a integração — ela não substitui o /auth nem renova nada.
| Status | Quando |
|---|---|
| 404 | Você ainda não configurou webhook pelo /auth. |
| 422 | O webhook está pausado (active: false). |
| 429 | Mais de 5 chamadas por minuto. |
Usar o token
Em qualquer endpoint do seller, envie o token no cabeçalho Authorization:
curl https://api.unisupri.com/api/products \
-H "Authorization: Bearer sk_live_a1b2c3d4e5f6..."A API valida, nesta ordem:
- Existência: o token corresponde a uma credencial cadastrada.
- Ativação: a credencial está ativa (não foi desativada nem revogada).
- Validade:
expires_atainda não passou.
Falha em qualquer uma → resposta 401 Unauthorized.
Depois disso vem a checagem de escopo: se o token existe mas não tem a permissão exigida pela rota, a resposta é 403, com os escopos que faltam na lista errors. Um token com abilities: ["*"] passa em qualquer rota.
Uma exceção:store_orders_substores (alcance às sublojas) não é coberto pelo wildcard — ele precisa estar declarado explicitamente na credencial. Isso é deliberado: nenhuma integração existente passa a receber pedidos de outra loja sem alguém marcar o acesso. Ver Matriz e sublojas.
Confirmar a autenticação
Use para validar que o token está autenticando a loja correta. A resposta traz os dados da loja (id, nome, código, status, tipo, limites e features), a company associada (CNPJ, razão social, regime tributário) e auth_type: "integration_token".
Limites de taxa
O /api/integration/auth é protegido contra brute-force:
- 5 tentativas por minuto por par
(IP, username). - Estourou o limite: 429 Too Many Requests, com
Retry-Afterem segundos.
O contador é por par IP+username; tentativas legítimas de outras lojas/IPs não são afetadas.
Este limite é só da emissão de token. O limite das demais rotas de integração está em Limites de uso.
Erros possíveis
| Status | Quando acontece | O que fazer |
|---|---|---|
401 no /integration/auth | Username inexistente, senha errada ou credencial inativa | Verifique username/senha. A mensagem é genérica de propósito e não revela qual dos três casos é. Se persistir, confirme no portal que a credencial está ativa. |
| 401 em endpoints autenticados | Token ausente, malformado, revogado ou expirado | Renove o token via /integration/auth. Se o erro persistir após renovar, a credencial pode ter sido revogada; verifique no portal. |
422 no /integration/auth | username ou password ausente / vazio | Garanta que os dois campos vão no body como strings não-vazias. |
422 no /integration/auth com bloco webhook | URL não-HTTPS, evento inexistente ou evento fora das permissões da credencial | Erro de configuração: não repita a chamada. Corrija o bloco usando meta.accepted_events da resposta. Sem bloco webhook, o login volta a funcionar normalmente. |
429 no /integration/auth | Mais de 5 tentativas/minuto pra mesma credencial+IP | Aguarde o Retry-After segundos antes de tentar novamente. Cache o token e não chame o auth em loop. |
Trocar a senha
Quando trocar:
- Você suspeita que a senha vazou.
- Rotação preventiva periódica (recomendado a cada 90 dias).
- Trocou de mãos a operação do ERP.
Como trocar:
- Portal do Vendedor → Configurações → API & Integrações.
- Localize a credencial na lista.
- Clique no ícone de chave (
🔑 Trocar senha). - Escolha Gerar aleatória (recomendado, 20 caracteres) ou Definir manualmente (entre 12 e 64 caracteres).
- Copie a nova senha. Ela aparece uma única vez.
Importante: trocar a senha não invalida o token corrente. O ERP que está usando o token atual continua funcionando até a próxima expiração ou até você revogar a credencial inteira. Se você precisa invalidar imediatamente (suspeita de comprometimento), revogue a credencial (lixeira) e crie uma nova.
Revogar uma credencial
Portal do Vendedor → Configurações → API & Integrações → ícone de lixeira na linha da credencial.
Revogar corta o acesso na hora, dos dois lados:
- Username e senha deixam de autenticar no
/integration/auth. - O token corrente para de funcionar imediatamente, mesmo dentro do prazo de 30 dias: a validação parte do registro da credencial, que deixou de existir. A partir daí, toda chamada com esse token responde 401.
O mesmo vale para uma credencial marcada como inativa: o token dela é recusado na próxima chamada.
É esse o caminho para cortar acesso de imediato em caso de vazamento. Trocar a senha não serve para isso — veja a seção anterior.
Boas práticas
- Cache o token. Não chame
/integration/authantes de cada requisição; emita um token e use-o pelos 30 dias. - Renove proativamente. Agende um job que renove o token quando faltarem ~24h.
- Trate 401 como sinal de renovação. Se uma chamada autenticada retornar 401, renove o token e tente uma vez. Se ainda falhar, alerte um humano.
- Uma credencial por sistema. Não compartilhe credenciais entre ERPs diferentes; a rotação substitui o token, e isso quebra o sistema que ficou pra trás.
- Não logue a senha nem o token em arquivos de log persistentes. Use mascaramento (
sk_live_...XXXX). - Cofre de senhas. Guarde
username+passwordem um gerenciador de segredos (1Password, Vault, AWS Secrets Manager, etc.), nunca em código-fonte commitado. - Mande o bloco
webhookem todo login. É idempotente: cria na primeira vez, e depois só confirma (unchanged). Isso mantém a configuração convergindo sozinha, inclusive se alguém mexer nela. - Logue o
statusdo webhook.createdem regime normal significa que a configuração sumiu e foi recriada — vale investigar.

