Devoluções

Tratamento de devoluções (RMA) encaminhadas ao seller pela plataforma — aprovação, rejeição e geração de coleta reversa — e o acompanhamento do reembolso ao cliente.

Pré-requisitos: leia primeiro Pedidos: visão geral. A devolução é um recurso vinculado a um pedido; ela só existe depois que o cliente solicita devolução de um pedido entregue.

Reembolso é processado pela plataforma, não pelo seller. Não há endpoint de estorno nesta API — o seller acompanha o reembolso pelo status da devolução (refunded). 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 — "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/>plataforma decide} N -->|criar solicitação de estorno| O[refunded<br/>financeiro processando] O -->|estorno concluído| P[closed<br/>resolution=refunded] N -->|resolvido fora da plataforma| Q[closed<br/>resolution=resolved_externally]

Status possíveis (OrderReturnStatusEnum):

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:00Z",
        "sla_exceeded": false,
        "created_at": "2026-04-26T10:15:00Z"
      }
    ]
  }
}

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: motivo informado pelo cliente (reason), 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 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": 7, "uid": "01HM0Z6E...", "name": "Correios PAC",   "estimated_freight": 18.90 },
      { "id": 9, "uid": "01HM0X1A...", "name": "Loggi Reverso",  "estimated_freight": 22.50 }
    ]
  }
}

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).

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:00Z",
  "pickup_window_to":   "2026-04-28T18:00:00Z"
}
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,
      "uid": "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 a plataforma assume a decisão do desfecho (estorno ou encerramento 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), 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.

A decisão de estorno (após received) é exclusiva da plataforma e nunca aparece aqui — acompanhe pelos campos status e resolution.


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 }

"Vou recusar, cliente está fora do prazo legal de 7 dias"

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 a plataforma decide o desfecho. Sem essa confirmação, a devolução em coleta manual não avança.

"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

Você não vai encontrar um endpoint de estorno nesta API — e isso é de propósito. O reembolso mexe com dinheiro do cliente, conciliação de pagamento e regras de repasse, então a plataforma centraliza esse passo para garantir que o estorno só aconteça quando o produto realmente voltou e o caso fechou. Do seu lado, em vez de "emitir" um reembolso, você acompanha o desfecho pelos campos status e resolution da devolução.

Pela ótica do seller, o ciclo final é:

StatusO que significa para o reembolso
receivedProduto chegou de volta à loja. A plataforma decide o desfecho.
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ê não inicia nem cancela um reembolso pela API. Casos excepcionais (divergência de valor, acordo direto com o cliente) são tratados pela operação/suporte da plataforma — 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 sucessivas são possíveis. Encerrada uma devolução, o cliente pode abrir outra para o restante dos itens — cada uma com seu próprio desfecho. 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).