Devoluções

Tratamento de devoluções (RMA) encaminhadas ao seller pela plataforma: aprovação, rejeição, geração de coleta reversa e decisão do desfecho (solicitar estorno ou encerrar sem estorno).

Você decide o desfecho; o financeiro da plataforma executa o estorno. Depois que o produto volta (received), você solicita o estorno (/request-refund) ou encerra o caso sem estorno (/close-without-refund). O pagamento do estorno em si é processado pela plataforma. Veja Reembolsos ao final.


Como devoluções chegam ao seller

O fluxo de devolução começa do lado do cliente, não do seller. A plataforma faz uma triagem inicial: algumas devoluções são resolvidas automaticamente (ex.: pedidos com seguro, casos óbvios de extravio). Outras são encaminhadas ao seller com status forwarded_to_seller (o "encaminhar" transfere a tomada de decisão, não a visibilidade): a API lista todas as devoluções da loja desde a abertura, para a operação acompanhar mesmo quando a decisão ainda é da plataforma.

flowchart TD A[Cliente solicita devolução] --> B{Triagem da<br/>plataforma} B -->|resolvido automaticamente| Z[closed] B -->|encaminhado| C[forwarded_to_seller] C --> D{Decisão do<br/>seller} D -->|approve| E[approved] D -->|reject| F[rejected] E --> G{Coleta?} G -->|method=carrier| H[POST /reverse/generate<br/>+ carrier_id] H --> I[label_generated<br/>+ shipment reverso criado] G -->|method=manual| J[POST /reverse/generate<br/>method=manual] J --> K[label_generated<br/>seller organiza coleta] I --> L[return_in_progress<br/>coleta em trânsito] K --> L L -->|POST /mark-received| M[received<br/>produto voltou à loja] K -->|POST /mark-received| M M --> N{Desfecho<br/>seller decide} N -->|POST /request-refund| O[refunded<br/>financeiro processando] O -->|estorno concluído| P[closed<br/>resolution=refunded] N -->|POST /close-without-refund| Q[closed<br/>resolution=resolved_externally]

Status possíveis:

StatusSignifica
pendingSolicitação recém-criada pelo cliente, ainda em triagem.
forwarded_to_sellerA plataforma encaminhou; sua loja precisa aprovar ou rejeitar.
approvedSeller aprovou. A coleta reversa pode ser gerada.
rejectedSeller recusou. Cliente vê o motivo.
cancelledCliente desistiu antes de qualquer decisão.
label_generatedColeta reversa gerada (em qualquer modo); aguardando coleta/etiqueta. Daqui você já pode confirmar o recebimento quando o produto chegar.
return_in_progressColeta em andamento (em qualquer modo).
receivedProduto chegou de volta à loja (confirmado via /mark-received). É o ponto de decisão do desfecho.
refundedSolicitação de estorno criada; o financeiro da plataforma está processando o reembolso.
closedCaso encerrado. O campo resolution diz como: refunded (estorno concluído) ou resolved_externally (resolvido fora da plataforma, sem estorno).

O desfecho (quem decide, quanto é estornado em devoluções parciais, e o caso "resolvido por fora") tem guia próprio: Devoluções: Resolução.


1. Listagem

GET/api/v1/sellers/orders/returns

Sem filtros, devolve todas as devoluções da loja, inclusive as que ainda estão em triagem na plataforma (pending) e as já encerradas. Use os filtros para recortar o que interessa:

Para…Use
Fila aguardando sua decisão?status=forwarded_to_seller
Todas as devoluções de um pedido?order_id=01JX8K3M9PEDIDO000000000AB
Apenas as recusadas?status=rejected
Buscar por número de pedido?q=ORD-0001
Janela de datas?date_from=2026-04-01&date_to=2026-04-30

Resposta paginada (?per_page=15 é o default):

{
  "success": true,
  "message_code": "SUCCESS",
  "data": {
    "meta": {
      "pagination": {
        "page": 1,
        "per_page": 15,
        "last_page": 1,
        "has_prev_page": false,
        "has_next_page": false,
        "records": { "from": 1, "to": 1, "records": 1 }
      }
    },
    "data": [
      {
        "id": "01JX8K3M9DEVOLUCAO00000000",
        "order_id": "01JX8K3M9PEDIDO000000000AB",
        "order_number": "ORD-000123",
        "status": "forwarded_to_seller",
        "notes": "Produto com defeito",
        "seller_response_deadline_at": "2026-04-28T10:15:00-03:00",
        "sla_exceeded": false,
        "created_at": "2026-04-26T10:15:00-03:00"
      }
    ]
  }
}

seller_response_deadline_at e sla_exceeded indicam o SLA configurado na plataforma (returns.seller_response_sla_hours, default 48h). Use para destacar visualmente devoluções com prazo estourado.


2. Detalhe da devolução

GET/api/v1/sellers/orders/returns/{id}

Devolve o registro completo: observação do cliente (notes), itens devolvidos (items[]), endereço de coleta (pickup_address), janela e contato (preenchidos quando vai haver coleta), histórico de timestamps (approved_at, rejected_at, cancelled_at, etc.).

O motivo vem em dupla: return_reason_key (chave para lógica) e return_reason_label (rótulo humano; é o que você exibe na tela). A lista de motivos é configurável pela operação da plataforma, então não fixe um enum próprio no seu sistema: trate return_reason_key como um identificador que pode ganhar novos valores. Cada item de items[] vem enriquecido com os dados do item do pedido correspondente: title, sku, variation, unit_price, thumbnail_url e o próprio reason_label, prontos para renderizar sem consulta extra ao pedido.

{
  "data": {
    "id": "01JX8K3M9DEVOLUCAO00000000",
    "return_reason_key": "product_defect",
    "return_reason_label": "Defeito de fabricação",
    "items": [
      {
        "order_item_id": "01JX8K3M9ITEM0000000000001",
        "quantity": 2,
        "reason_key": "product_defect",
        "reason_label": "Defeito de fabricação",
        "title": "Furadeira de Impacto 650W",
        "sku": "FUR-650-IMP",
        "variation": "220V",
        "unit_price": 189.90,
        "thumbnail_url": "https://cdn.../sm/furadeira.webp"
      }
    ]
  }
}

É o que a tela "Detalhe da devolução" consome para mostrar todas as informações antes do seller decidir.


3. Decidir: aprovar ou rejeitar

Aprovar

POST/api/v1/sellers/orders/returns/{id}/approve

{
  "seller_notes": "Defeito confirmado nas fotos. Reembolso autorizado."
}

seller_notes é opcional (máx 1000 caracteres) e fica só no histórico interno; não é exibido ao cliente. Use para registrar a justificativa interna ("conferi com o setor X", "concordo com o atendimento que abriu o ticket").

Depois de aprovar, a próxima ação é gerar a coleta reversa (passo 4 abaixo).

Rejeitar

POST/api/v1/sellers/orders/returns/{id}/reject

{
  "reason": "Produto fora do prazo de devolução (entregue há mais de 60 dias)."
}

reason é obrigatório (máx 1000 caracteres) e é exibido ao cliente como justificativa. Escreva pensando que o cliente vai ler: explicação clara, sem jargão interno.


4. Coleta reversa

Aprovada a devolução, falta trazer o produto de volta. Dois modos:

  • carrier: usa um transportador parceiro da plataforma. Você escolhe da lista de elegíveis, a plataforma cria o shipment reverso (com tracking_code etc.) e o cliente recebe a etiqueta.
  • manual: você organiza a coleta por fora (motoboy próprio, cliente leva na loja, retirada combinada). Nenhum shipment é criado; só o estado da devolução avança e suas anotações ficam no histórico.

4a. Listar carriers elegíveis (modo carrier)

GET/api/v1/sellers/orders/returns/{id}/reverse/eligible-carriers

Lista os transportadores que cobrem o CEP do cliente e o frete estimado de cada um. Use para montar a UI de escolha do transportador.

{
  "data": {
    "has_coverage": true,
    "zip_code": "01310-100",
    "carriers": [
      { "id": "01HM0Z6E...", "name": "Correios PAC",  "estimated_freight": 18.90, "has_drivers": true },
      { "id": "01HM0X1A...", "name": "Loggi Reverso", "estimated_freight": 22.50, "has_drivers": false }
    ]
  }
}

has_coverage=false significa que nenhum carrier parceiro atende o endereço do cliente. Nesse caso, o modo carrier não está disponível; só resta manual (ou negociar suporte da plataforma).

Prefira carrier com has_drivers: true. A coleta só chega a alguém se o transportador escolhido tiver motorista vinculado. Escolher um sem motorista não dá erro: a coleta é criada, a devolução avança para label_generated e o pacote simplesmente nunca é buscado. Se o único carrier com cobertura vier has_drivers: false, o caminho seguro é a coleta manual.

4b. Gerar coleta

POST/api/v1/sellers/orders/returns/{id}/reverse/generate

Modo carrier:

{
  "method": "carrier",
  "carrier_id": 7,
  "freight_cost": 18.90,
  "pickup_contact_phone": "+5511999999999",
  "pickup_window_from": "2026-04-28T09:00:00-03:00",
  "pickup_window_to":   "2026-04-28T18:00:00-03:00"
}
CampoQuando preencher
methodObrigatório.carrier ou manual.
carrier_idObrigatório se method=carrier. Use o id de eligible-carriers.
freight_costValor que vai ser cobrado/reservado para o frete. Default 0. Normalmente o estimated_freight retornado por eligible-carriers.
notesObservações livres (máx 1000 caracteres). Útil em manual.
pickup_window_from / pickup_window_toJanela acordada com o cliente. Opcionais, mas se enviar ambos, o "to" precisa ser ≥ "from".
pickup_contact_phoneTelefone no endereço de coleta. Opcional (máx 32 caracteres).

Resposta em modo carrier:

{
  "data": {
    "order_return": { "id": "01JX8K3M9DEVOLUCAO00000000", "status": "label_generated" },
    "shipment": {
      "id": 9921,
      "id": "01HM1Z3...",
      "tracking_code": null,
      "status": "pending"
    }
  }
}

Modo manual:

{
  "method": "manual",
  "notes": "Cliente vai trazer pessoalmente sexta-feira à tarde."
}

Em manual não há shipment; a resposta traz só o order_return atualizado.


5. Confirmar recebimento

POST/api/v1/sellers/orders/returns/{id}/mark-received

O destino da coleta reversa é o endereço da loja (quem recebe o pacote fisicamente é você). Quando o produto chegar, confirme o recebimento: a devolução vira received e passa a ser seu o ponto de decisão do desfecho (solicitar estorno ou encerrar sem estorno).

Disponível a partir de label_generated ou return_in_progress, sem corpo na requisição. Na coleta manual não existe rastreio nenhum, então esta confirmação é a única forma de a devolução avançar: sem ela o caso fica parado e o cliente sem resposta.

POST /api/v1/sellers/orders/returns/01JX8K3M9DEVOLUCAO00000000/mark-received

6. Ações possíveis (workflow dirigido pela API)

GET/api/v1/sellers/orders/returns/{id}/possible-actions

Em vez de replicar a máquina de estados no seu sistema, pergunte à API o que pode ser feito agora. A resposta lista as ações válidas para o status atual (approve/reject em forwarded_to_seller, generate_reverse_label em approved, mark_received em label_generated/return_in_progress, request_refund/close_without_refund em received), cada uma com endpoint, method e os campos esperados em requires_input. is_terminal=true indica que a devolução encerrou e não há mais nada a fazer.

Em received, as ações de desfecho aparecem aqui: request_refund (cria a solicitação de estorno) e close_without_refund (encerra resolvida por fora, com notes obrigatório). A execução do estorno segue no financeiro da plataforma; acompanhe pelos campos status, resolution e pelo bloco refund.


Cenários comuns

"Recebi devolução por defeito de fabricação, vou aprovar e gerar coleta"

POST /api/v1/sellers/orders/returns/01JX8K3M9DEVOLUCAO00000000/approve
{ "seller_notes": "Defeito visível nas fotos. Reembolso autorizado." }
GET /api/v1/sellers/orders/returns/01JX8K3M9DEVOLUCAO00000000/reverse/eligible-carriers

Escolha um carrier baseado em estimated_freight e prazo, depois:

POST /api/v1/sellers/orders/returns/01JX8K3M9DEVOLUCAO00000000/reverse/generate
{ "method": "carrier", "carrier_id": 7, "freight_cost": 18.90 }
POST /api/v1/sellers/orders/returns/01JX8K3M9DEVOLUCAO00000000/reject
{
  "reason": "O prazo legal de 7 dias para devolução sem motivo (Art. 49 CDC) já expirou. Recebimento confirmado em 12/03; solicitação aberta em 28/03."
}

"Cliente mora numa cidade que nenhum carrier atende"

GET /reverse/eligible-carriers retorna has_coverage=false. Resolva manualmente:

POST /api/v1/sellers/orders/returns/01JX8K3M9DEVOLUCAO00000000/reverse/generate
{
  "method": "manual",
  "notes": "Sem cobertura de carrier. Combinado com cliente: motoboy contratado pela loja vai retirar dia 29/04."
}

"A coleta era manual e o produto chegou na loja"

POST /api/v1/sellers/orders/returns/01JX8K3M9DEVOLUCAO00000000/mark-received

A devolução vira received e passa a ser sua a decisão do desfecho. Sem essa confirmação, a devolução em coleta manual não avança.

"O produto voltou e conferi o defeito. Vou autorizar o estorno"

POST /api/v1/sellers/orders/returns/01JX8K3M9DEVOLUCAO00000000/request-refund

Sem corpo. A devolução vira refunded, a solicitação de estorno (valor dos itens devolvidos) vai para o financeiro processar, e o caso fecha em closed quando o estorno concluir. Acompanhe pelo bloco refund do detalhe.

"Reenviei outro produto ao cliente. Não quero estornar"

POST /api/v1/sellers/orders/returns/01JX8K3M9DEVOLUCAO00000000/close-without-refund
{ "notes": "Reenviei um produto novo via transportadora própria. Acordo com o cliente em 28/04." }

Encerra sem movimentar pagamento: closed com resolution=resolved_externally e a justificativa em resolution_notes.

"Cliente acha que vai dar tudo certo mas quero ver o histórico"

GET /api/v1/sellers/orders/returns?order_id=01JX8K3M9PEDIDO000000000AB

Devolve todas as devoluções do pedido 01JX8K3M9PEDIDO000000000AB, em qualquer status.

"Quero ver minhas devoluções recusadas no último mês"

GET /api/v1/sellers/orders/returns?status=rejected&date_from=2026-04-01&date_to=2026-04-30

Reembolsos

A partir de received, você decide o desfecho: POST /request-refund cria a solicitação de estorno, ou POST /close-without-refund encerra sem estorno (resolvido por fora). Quem executa o estorno (o pagamento em si, com conciliação de pagamento e regras de repasse) é o financeiro da plataforma: você toma a decisão, a plataforma processa. Isso mantém o dinheiro do cliente sob um único passo controlado, que só liquida depois que o produto voltou. Acompanhe pelos campos status, resolution e pelo bloco refund da devolução.

Pela ótica do seller, o ciclo final é:

StatusO que significa para o reembolso
receivedProduto chegou de volta à loja. É onde você decide o desfecho (/request-refund ou /close-without-refund).
refundedSolicitação de estorno criada. O financeiro da plataforma está processando. O valor corresponde aos itens devolvidos (devolução parcial estorna só a parte devolvida).
closed + resolution=refundedEstorno concluído. O valor (e eventuais descontos de frete reverso) entra no seu extrato/repasse.
closed + resolution=resolved_externallyEncerrada sem estorno: o caso foi resolvido fora da plataforma (ex.: você reenviou o produto direto ao cliente). A justificativa fica em resolution_notes.

Pontos a ter em mente:

  • Você inicia o estorno, mas não o cancela pela API. A solicitação parte de você (/request-refund); depois de criada, cancelar ou ajustar o valor é com a operação/suporte da plataforma. Casos excepcionais (divergência de valor) também são tratados pela operação e refletem na devolução via resolution.
  • O frete reverso pode impactar o repasse. O freight_cost informado na geração da coleta é o valor que entra no acerto financeiro daquela devolução.
  • Aprovar a devolução não é o mesmo que reembolsar. Aprovar (/approve) autoriza a volta do produto; o desfecho financeiro só é decidido no fim do trilho, após o recebimento.
  • Devoluções parciais podem coexistir. O cliente pode abrir outra devolução para o restante dos itens mesmo com uma em andamento; o limite é o saldo devolvível de cada item, que já desconta as devoluções ativas. Detalhes e exemplos em Devoluções: Resolução.

Cuidados

reject.reason é visto pelo cliente. Escreva como se fosse uma resposta no SAC: profissional, clara, sem jargão interno. Para anotações internas use approve.seller_notes (que não é visto pelo cliente).

Coleta reversa só depois de approved. Tentar gerar coleta antes de aprovar retorna 422 com A devolução precisa estar aprovada para gerar a coleta reversa.. O fluxo é: forwarded_to_sellerapprove (vira approved) → reverse/generate.

freight_cost é o valor real (não estimativa). Use o estimated_freight como base, mas se você negociou um valor diferente com o carrier, mande o valor negociado. Esse é o número que entra no extrato/repasse.

pickup_address é montado a partir do snapshot do pedido. Você não precisa enviar; ele vem do endereço que estava no pedido original. Se for diferente (cliente mudou), edite no shipment via operação depois de gerar.

Aprovar é um caminho só de ida. Não há "des-aprovar". A razão é que, assim que você aprova, o cliente já é avisado e a coleta reversa passa a poder ser gerada; voltar atrás depois disso confundiria o cliente e a logística. Por isso, uma vez approved, a devolução segue o trilho da coleta reversa. Se aprovou por engano, não é algo que você reverte pela API. É caso de operação (contate o suporte).

Datas inválidas em date_from/date_to são ignoradas silenciosamente. Não há erro; o filtro simplesmente não é aplicado. Se a busca não está filtrando, cheque o formato (ISO 8601).