Disputas e MED (contestação Pix)
Um pagamento Pix pode ser contestado depois de pago. No Pix, o canal para isso é o MED (Mecanismo Especial de Devolução) do BACEN: o pagador pede ao banco dele a devolução de uma transação que alega ter sido fraude, golpe ou erro. Quando um MED é aberto contra uma cobrança sua, a AvioraPay abre um caso MED para ele, retém o valor da sua conta enquanto o caso corre e te avisa — por webhook, por e-mail e no painel — para que você não descubra o prejuízo só no fim do mês.
Isto é diferente de um estorno (refund), que é uma devolução que você decide
fazer. O MED é iniciado pelo pagador, do lado do banco dele.
Tudo nesta página está disponível pela API (/v1/med/cases), pelos SDKs oficiais (recurso
medCases) e pelo painel Disputas e casos MED do seu dashboard. Os três leem e alteram
o mesmo caso.
#O ciclo de vida, de relance
cobrança paid
│
MED aberto pelo banco do pagador
│
▼
med_case "open" ── valor retido da sua conta,
│ cobrança vira "disputed", estorno bloqueado
┌───────────┴──────────────┐
▼ ▼
você aceita você contesta (+ evidências)
│ │
▼ ▼
"accepted" "appealed"
(perda assumida) │
┌───────────┴───────────┐
▼ ▼
"rejected" "closed"
(MED mantido, (a seu favor, ou
perda assumida) encerrado sem devolução)Um caso também pode ir direto de open para closed — por exemplo quando o pagador
desiste da reclamação ou a decisão sai a seu favor antes de você responder.
#O que acontece quando um MED abre
- Um caso MED é criado (
med_case, idmed_…) ligado à cobrança contestada (charge_id,ch_…). Ele começa emopen. - A AvioraPay retém o valor da sua conta. O valor contestado é debitado do seu saldo operacional (pode ficar negativo) e fica retido enquanto o caso corre. Se o caso fechar a seu favor, ele volta; se você perder, ele já estava reservado.
- A cobrança vira
disputed. O status público da cobrança (vocabulário de status) passa depaidparadisputed. Uma cobrançadisputednão pode ser estornada — o estorno duplicaria a devolução (o seu estorno + o MED executado pelo banco). Se o caso fechar a seu favor, a cobrança volta parapaid. - Você é avisado, por todos os canais abaixo.
#Como você é avisado
| Canal | O que chega |
|---|---|
Webhook med_case.created | O objeto med_case completo quando o caso abre, com response_deadline quando o prazo é conhecido. |
Webhook med_case.updated | O objeto med_case completo a cada mudança de status (appealed, accepted, rejected, closed) e quando a plataforma te pede evidências (evidence_requested_at). |
Webhook med.resolved | O destino final do dinheiro: refunded_to_merchant, closed_without_refund ou lost. |
| E-mails "MED aberto" e "MED resolvido" para o contato da sua conta. | |
| Painel | Disputas e casos MED, com o valor, o motivo alegado e as ações abaixo. |
| API | GET /v1/med/cases e GET /v1/med/cases/{id} — sempre atualizados. |
Corpos e assinaturas dos três eventos estão em Webhooks.
Nenhum webhook
charge.*sai por causa de um MED. A cobrança foi paga; MED não é falha de cobrança, nem estorno —charge.failedecharge.refundednão são enviados quando um MED abre, é ganho ou é perdido. Assinemed_case.created,med_case.updatedemed.resolved. Relendo a cobrança (GET /v1/charges/{id}) você vêstatus: "disputed"enquanto o caso está aberto.
Webhook é sinal; a API é a fonte da verdade. Os avisos de desfecho (
med.resolved, emed_case.updatedparaaccepted/rejected/closed) vêm ligados por padrão, mas a plataforma pode desligá-los, e um evento de caso MED cuja publicação falha não é reenviado. Reconcilie comGET /v1/med/cases(por exemplo uma vez por dia, filtrando porcreated_after) e considerestatus+money_outcomeda API como o estado final.
#Estados do caso
status | Significado | Efeito no dinheiro |
|---|---|---|
open | MED recém-aberto. Aguardando você aceitar ou contestar. | Valor retido. |
appealed | Você contestou (enviou sua defesa); aguardando o desfecho. | Valor segue retido até o desfecho. |
accepted | Você aceitou a perda sem contestar. | Valor segue debitado (perda assumida). |
rejected | A contestação não prosperou — o MED foi mantido. | Valor segue debitado (perda definitiva). |
closed | O caso foi encerrado. Leia money_outcome para saber para onde foi o dinheiro. | Devolvido a você, ou não — veja abaixo. |
money_outcome é null enquanto o caso está open ou appealed e, depois, diz o destino
final do dinheiro:
money_outcome | Significado |
|---|---|
refunded_to_merchant | O valor retido voltou para o seu saldo (decisão a seu favor, ou reclamação retirada). |
closed_without_refund | O caso foi encerrado, mas o valor não voltou para o seu saldo. |
lost | O caso terminou em perda (accepted ou rejected); o valor segue debitado. |
closedsozinho não quer dizer que você ganhou. Leia sempremoney_outcome.
Alguns desfechos são aplicados automaticamente quando o banco do pagador ou o BACEN decide o caso; outros passam antes por uma revisão da equipe de operações da AvioraPay, e o caso mantém o status atual até essa revisão terminar.
#Prazos
response_deadline (ISO 8601, UTC) é o prazo para responder ao caso. Ele vem preenchido
quando o prazo foi informado à AvioraPay, e null quando não. Vai no med_case.created e em
toda leitura. Planeje contestar (com suas evidências) bem antes dele — a decisão não
espera por você.
#Sua defesa: contestar, aceitar e evidências
Enquanto o caso está open, você escolhe um de dois caminhos:
- Contestar (
POST /v1/med/cases/{id}/appeal) — você contesta o MED com uma justificativa escrita (reason, de 5 a 2.000 caracteres). O caso vai paraappealed. - Aceitar (
POST /v1/med/cases/{id}/accept) — você desiste de contestar. O caso vai paraacceptede o valor segue debitado.
Evidências (nota fiscal, comprovante de entrega, conversas com o comprador — qualquer coisa que mostre que a cobrança foi legítima) são anexadas pelo upload em três passos abaixo. Você pode anexar antes ou depois de contestar; anexe antes do prazo.
Importante — o que a defesa é, e o que não é. Sua contestação e suas evidências ficam registradas na plataforma AvioraPay, onde a equipe de operações as usa para avaliar e conduzir o caso. Elas não são encaminhadas automaticamente ao provedor de pagamento, ao banco do pagador nem ao BACEN: hoje nenhum provedor oferece à AvioraPay um canal para enviar a defesa de um MED. Contestar, portanto, não é um envio formal ao BACEN. A decisão oficial chega à AvioraPay pelo canal do MED e aparece no caso (
status,money_outcome) e nos webhooks acima.
#API
Todas as rotas usam a sua chave de API e só enxergam os seus casos. O caminho canônico
é /v1/med/cases; /v1/infractions é um alias que atende as mesmas rotas.
apikey: ak_live_...| Método | Caminho | O que faz |
|---|---|---|
GET | /v1/med/cases | Lista seus casos MED (paginação por cursor). |
GET | /v1/med/cases/{id} | Consulta um caso. |
POST | /v1/med/cases/{id}/appeal | Contesta um caso open. |
POST | /v1/med/cases/{id}/accept | Aceita um caso open. |
GET | /v1/med/cases/{id}/evidence | Lista as evidências do caso. |
POST | /v1/med/cases/{id}/evidence | Pede uma URL de upload para uma nova evidência. |
POST | /v1/med/cases/{id}/evidence/{evidence_id}/complete | Confirma o upload de uma evidência. |
O {id} do caso aceita o id med_….
#O objeto med_case
{
"id": "med_3f1c2b9e-0d7a-4a55-9a51-6f0e3c1d2b10",
"object": "med_case",
"type": "med",
"status": "open",
"amount": 15000,
"currency": "BRL",
"charge_id": "ch_6b0f4c1e-2a8d-4c1b-9d0e-1f2a3b4c5d6e",
"reason": "Pagador relata golpe",
"response_deadline": "2026-09-30T23:59:59.000Z",
"evidence_requested_at": null,
"appeal_reason": null,
"appealed_at": null,
"evidence_count": 0,
"money_outcome": null,
"opened_at": "2026-09-23T14:02:11.000Z",
"closed_at": null,
"created_at": "2026-09-23T14:02:12.000Z",
"updated_at": "2026-09-23T14:02:12.000Z"
}| Campo | Descrição |
|---|---|
id | Id do caso, med_…. |
object | Sempre med_case. |
type | med para um MED Pix. Outros tipos de disputa (chargeback, dispute, fraud) usam o mesmo objeto. |
status | open, appealed, accepted, rejected ou closed (estados). |
amount | Valor contestado, na menor unidade da moeda (centavos para BRL). |
currency | Código ISO 4217, ex.: BRL. |
charge_id | A cobrança contestada, ch_…. |
reason | O motivo alegado, como aparece no seu painel. Pode ser null. |
response_deadline | Prazo para responder, ou null quando desconhecido (prazos). |
evidence_requested_at | Quando a plataforma te pediu evidências, ou null. |
appeal_reason | A justificativa que você enviou ao contestar, ou null. |
appealed_at | Quando você contestou, ou null. |
evidence_count | Quantidade de evidências com upload confirmado. |
money_outcome | refunded_to_merchant, closed_without_refund, lost, ou null enquanto não há desfecho. |
opened_at | Quando o MED foi aberto. |
closed_at | Quando o caso chegou a um estado final, ou null. |
created_at / updated_at | Datas do registro (ISO 8601, UTC). |
#Listar casos
curl "https://aviorapay.app/v1/med/cases?status=open&limit=25" \
-H "apikey: $AVIORAPAY_API_KEY"| Parâmetro | Descrição |
|---|---|
limit | De 1 a 100. Padrão 25. |
starting_after | Um id med_…: devolve os casos depois dele (o next_cursor da página anterior). Precisa ser um caso seu. |
status | open, appealed, accepted, rejected ou closed. |
type | med, chargeback, dispute ou fraud. Sem ele, vêm todos os tipos. |
charge_id | Só os casos desta cobrança (ch_…). |
created_after / created_before | Data-hora ISO 8601 (limites exclusivos). |
{
"object": "list",
"data": [
{ "id": "med_3f1c2b9e-0d7a-4a55-9a51-6f0e3c1d2b10", "object": "med_case", "status": "open", "...": "..." }
],
"has_more": true,
"next_cursor": "med_3f1c2b9e-0d7a-4a55-9a51-6f0e3c1d2b10"
}Os casos vêm do mais novo para o mais antigo (created_at decrescente). Enquanto
has_more for true, passe next_cursor como starting_after para ler a próxima página.
Valor desconhecido em status, type ou limit, ou data malformada, devolve 400 —
filtro nunca é ignorado em silêncio.
#Consultar um caso
curl "https://aviorapay.app/v1/med/cases/med_3f1c2b9e-0d7a-4a55-9a51-6f0e3c1d2b10" \
-H "apikey: $AVIORAPAY_API_KEY"Devolve o objeto med_case, ou 404 se o caso não existe na sua
conta.
#Contestar um caso
curl -X POST "https://aviorapay.app/v1/med/cases/med_3f1c2b9e-0d7a-4a55-9a51-6f0e3c1d2b10/appeal" \
-H "apikey: $AVIORAPAY_API_KEY" \
-H "Content-Type: application/json" \
-d '{"reason": "Pedido #8812 entregue em 20/09/2026; comprovante de entrega assinado em anexo."}'reason é obrigatório, de 5 a 2.000 caracteres. Devolve 200 com o caso atualizado
(status: "appealed", com appeal_reason e appealed_at preenchidos). Só um caso open
pode ser contestado: qualquer outro status devolve 400.
#Aceitar um caso
curl -X POST "https://aviorapay.app/v1/med/cases/med_3f1c2b9e-0d7a-4a55-9a51-6f0e3c1d2b10/accept" \
-H "apikey: $AVIORAPAY_API_KEY"Sem corpo. Devolve 200 com o caso atualizado (status: "accepted",
money_outcome: "lost"). Só um caso open pode ser aceito: qualquer outro status devolve
400. Aceitar é definitivo.
#Enviar evidências
O arquivo vai direto para o armazenamento, por uma URL assinada de vida curta; ele
nunca passa pelo corpo da API. Limites: 10 MB por arquivo; tipos image/png,
image/jpeg, image/webp, image/gif e application/pdf.
Passo 1 — peça uma URL de upload. Declare o nome do arquivo, o tamanho exato em bytes e o tipo:
curl -X POST "https://aviorapay.app/v1/med/cases/med_3f1c2b9e-0d7a-4a55-9a51-6f0e3c1d2b10/evidence" \
-H "apikey: $AVIORAPAY_API_KEY" \
-H "Content-Type: application/json" \
-d '{"file_name": "comprovante-entrega.pdf", "file_size": 52341, "mime_type": "application/pdf"}'201 Created:
{
"id": "0b8e6f52-7c3d-4e1a-9f40-2d6c8a1b5e77",
"object": "med_case_evidence_upload",
"upload_url": "https://storage.example.com/…?X-Amz-Signature=…",
"upload_method": "PUT",
"upload_headers": { "Content-Type": "application/pdf", "Content-Length": "52341" },
"expires_in": 900
}Passo 2 — faça o PUT do arquivo em upload_url em até expires_in segundos
(15 minutos), com exatamente os cabeçalhos de upload_headers. A assinatura amarra o
tipo e o tamanho: outro Content-Type, ou um arquivo de outro tamanho, é recusado pelo
armazenamento. Não mande sua chave de API (nem cabeçalho Authorization) para essa
URL — a própria URL é a credencial.
curl -X PUT "$UPLOAD_URL" \
-H "Content-Type: application/pdf" \
--data-binary @comprovante-entrega.pdf(O curl --data-binary já envia o Content-Length do arquivo; ele precisa bater com o
file_size do passo 1.)
Passo 3 — confirme o upload. A AvioraPay confere que o arquivo chegou de fato e marca a evidência como enviada:
curl -X POST "https://aviorapay.app/v1/med/cases/med_3f1c2b9e-0d7a-4a55-9a51-6f0e3c1d2b10/evidence/0b8e6f52-7c3d-4e1a-9f40-2d6c8a1b5e77/complete" \
-H "apikey: $AVIORAPAY_API_KEY"200 OK com o objeto da evidência:
{
"id": "0b8e6f52-7c3d-4e1a-9f40-2d6c8a1b5e77",
"object": "med_case_evidence",
"file_name": "comprovante-entrega.pdf",
"file_size": 52341,
"mime_type": "application/pdf",
"uploaded_by": "merchant",
"created_at": "2026-09-23T15:10:02.000Z",
"uploaded_at": "2026-09-23T15:10:09.000Z",
"view_url": "https://storage.example.com/…?X-Amz-Signature=…"
}O complete é idempotente: chamar de novo para um arquivo já confirmado devolve a mesma
evidência. Se o PUT ainda não aconteceu (ou a URL expirou), ele devolve 409 com
error.code EVIDENCE_NOT_UPLOADED — peça uma URL nova (passo 1) e envie de novo.
Evidência nunca confirmada não existe para a API: não é listada e não conta em
evidence_count.
Os SDKs oficiais fazem os três passos numa chamada só — veja SDKs.
#Listar evidências
curl "https://aviorapay.app/v1/med/cases/med_3f1c2b9e-0d7a-4a55-9a51-6f0e3c1d2b10/evidence" \
-H "apikey: $AVIORAPAY_API_KEY"{
"object": "list",
"data": [
{ "id": "0b8e6f52-7c3d-4e1a-9f40-2d6c8a1b5e77", "object": "med_case_evidence", "file_name": "comprovante-entrega.pdf", "...": "..." }
]
}Só vêm as evidências confirmadas, da mais nova para a mais antiga. uploaded_by é
merchant (você), partner ou platform (a equipe da AvioraPay). view_url é um link
assinado de download válido por 5 minutos — liste de novo para obter um link novo.
#Erros
Os erros seguem o formato padrão. Os específicos de casos MED:
| HTTP | error.code | Quando |
|---|---|---|
400 | invalid_request | Filtro inválido (status, type, limit, data), starting_after que não é um caso seu, reason com menos de 5 ou mais de 2.000 caracteres, file_name / file_size / mime_type ausente ou inválido, arquivo acima de 10 MB ou de tipo não aceito, ou contestar/aceitar um caso que não está open. |
404 | not_found | O caso (ou a evidência) não existe na sua conta. |
409 | EVIDENCE_NOT_UPLOADED | complete chamado antes de o arquivo chegar ao armazenamento (ou depois de a URL expirar). |
409 | EVIDENCE_SIZE_MISMATCH | O tamanho do arquivo armazenado difere do file_size declarado. |
503 | service_unavailable | O armazenamento de documentos está indisponível; o envio de evidências fica suspenso. Tente mais tarde. |
#SDKs
Os SDKs oficiais expõem as mesmas rotas como o recurso medCases (med_cases no Python,
MedCases no .NET), mais um atalho uploadEvidence que faz os três passos do upload:
import { AvioraPayClient } from '@aviorapay/node';
import { readFile } from 'node:fs/promises';
const client = new AvioraPayClient({ apiKey: process.env.AVIORAPAY_API_KEY! });
const { data, has_more, next_cursor } = await client.medCases.list({ status: 'open' });
for (const medCase of data) {
await client.medCases.uploadEvidence(medCase.id, await readFile('comprovante-entrega.pdf'), {
fileName: 'comprovante-entrega.pdf',
mimeType: 'application/pdf',
});
await client.medCases.appeal(medCase.id, 'Pedido entregue; comprovante assinado em anexo.');
}Veja SDKs oficiais para a instalação e as outras linguagens.
#Campos de metadata da transação
Uma cobrança em disputa também pode trazer blocos informativos no metadata. Eles são
informativos e não autoritativos: espelham o que foi informado sobre o caso, são somente
leitura, podem mudar a cada evento do caso e o formato pode mudar. Não construa lógica em
cima deles — use o objeto med_case acima.
#metadata.med — presente numa cobrança em disputa
| Campo | Significado |
|---|---|
referenceId | O end_to_end_id (E2E) do Pix original contestado. |
status | Estado do caso como informado: created | delivered | closed | canceled. |
result | Resultado do julgamento, quando fechado: agreed (perdido) | disagreed (ganho). |
method | Motivo alegado: scam | unauthorized | coercion | invasion | other. |
reason | Descrição textual do caso. |
payerName / payerDocument | Identidade de quem abriu o MED, quando informada. |
openedAt / updatedAt / closedAt | Datas do caso como informadas (abertura / atualização / fechamento). |
lastEventAt | Quando a AvioraPay processou o evento mais recente deste caso. |
amountMismatch | true quando o valor informado divergiu do valor da cobrança. Nesse caso, por segurança, nenhuma mudança de status é aplicada e o caso vai para tratamento manual. |
Outras chaves podem aparecer neste bloco; ignore as que você não reconhece.
#metadata.failure — presente numa cobrança expired / cancelled / failed
| Campo | Significado |
|---|---|
reason | Motivo textual da falha informado pelo provedor. |
providerErrorCode | Código/status do provedor (ex.: expired, canceled) — o que decidiu o estado final. |
ttlSeconds | Tempo de vida do QR Pix em segundos (pix.expires_at − criação), quando a cobrança tinha validade. Ausente quando não tinha. |
at | Quando a falha foi registrada (ISO 8601). |
#Relacionados
- Corpos e assinaturas dos webhooks de MED: Webhooks.
- Estorno voluntário (diferente do MED): veja a pergunta sobre estorno em Pix e a referência da API.