Pular para o conteúdo
AVIORAPAYdocs
PTEN
Ir para o painel

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

text
                 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

  1. Um caso MED é criado (med_case, id med_…) ligado à cobrança contestada (charge_id, ch_…). Ele começa em open.
  2. 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.
  3. A cobrança vira disputed. O status público da cobrança (vocabulário de status) passa de paid para disputed. Uma cobrança disputed nã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 para paid.
  4. Você é avisado, por todos os canais abaixo.

#Como você é avisado

CanalO que chega
Webhook med_case.createdO objeto med_case completo quando o caso abre, com response_deadline quando o prazo é conhecido.
Webhook med_case.updatedO 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.resolvedO destino final do dinheiro: refunded_to_merchant, closed_without_refund ou lost.
E-mailE-mails "MED aberto" e "MED resolvido" para o contato da sua conta.
PainelDisputas e casos MED, com o valor, o motivo alegado e as ações abaixo.
APIGET /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.failed e charge.refunded não são enviados quando um MED abre, é ganho ou é perdido. Assine med_case.created, med_case.updated e med.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, e med_case.updated para accepted / 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 com GET /v1/med/cases (por exemplo uma vez por dia, filtrando por created_after) e considere status + money_outcome da API como o estado final.

#Estados do caso

statusSignificadoEfeito no dinheiro
openMED recém-aberto. Aguardando você aceitar ou contestar.Valor retido.
appealedVocê contestou (enviou sua defesa); aguardando o desfecho.Valor segue retido até o desfecho.
acceptedVocê aceitou a perda sem contestar.Valor segue debitado (perda assumida).
rejectedA contestação não prosperou — o MED foi mantido.Valor segue debitado (perda definitiva).
closedO 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_outcomeSignificado
refunded_to_merchantO valor retido voltou para o seu saldo (decisão a seu favor, ou reclamação retirada).
closed_without_refundO caso foi encerrado, mas o valor não voltou para o seu saldo.
lostO caso terminou em perda (accepted ou rejected); o valor segue debitado.

closed sozinho não quer dizer que você ganhou. Leia sempre money_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 para appealed.
  • Aceitar (POST /v1/med/cases/{id}/accept) — você desiste de contestar. O caso vai para accepted e 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.

text
apikey: ak_live_...
MétodoCaminhoO que faz
GET/v1/med/casesLista seus casos MED (paginação por cursor).
GET/v1/med/cases/{id}Consulta um caso.
POST/v1/med/cases/{id}/appealContesta um caso open.
POST/v1/med/cases/{id}/acceptAceita um caso open.
GET/v1/med/cases/{id}/evidenceLista as evidências do caso.
POST/v1/med/cases/{id}/evidencePede uma URL de upload para uma nova evidência.
POST/v1/med/cases/{id}/evidence/{evidence_id}/completeConfirma o upload de uma evidência.

O {id} do caso aceita o id med_….

#O objeto med_case

json
{
  "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"
}
CampoDescrição
idId do caso, med_….
objectSempre med_case.
typemed para um MED Pix. Outros tipos de disputa (chargeback, dispute, fraud) usam o mesmo objeto.
statusopen, appealed, accepted, rejected ou closed (estados).
amountValor contestado, na menor unidade da moeda (centavos para BRL).
currencyCódigo ISO 4217, ex.: BRL.
charge_idA cobrança contestada, ch_….
reasonO motivo alegado, como aparece no seu painel. Pode ser null.
response_deadlinePrazo para responder, ou null quando desconhecido (prazos).
evidence_requested_atQuando a plataforma te pediu evidências, ou null.
appeal_reasonA justificativa que você enviou ao contestar, ou null.
appealed_atQuando você contestou, ou null.
evidence_countQuantidade de evidências com upload confirmado.
money_outcomerefunded_to_merchant, closed_without_refund, lost, ou null enquanto não há desfecho.
opened_atQuando o MED foi aberto.
closed_atQuando o caso chegou a um estado final, ou null.
created_at / updated_atDatas do registro (ISO 8601, UTC).

#Listar casos

bash
curl "https://aviorapay.app/v1/med/cases?status=open&limit=25" \
  -H "apikey: $AVIORAPAY_API_KEY"
ParâmetroDescrição
limitDe 1 a 100. Padrão 25.
starting_afterUm id med_…: devolve os casos depois dele (o next_cursor da página anterior). Precisa ser um caso seu.
statusopen, appealed, accepted, rejected ou closed.
typemed, chargeback, dispute ou fraud. Sem ele, vêm todos os tipos.
charge_idSó os casos desta cobrança (ch_…).
created_after / created_beforeData-hora ISO 8601 (limites exclusivos).
json
{
  "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

bash
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

bash
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

bash
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:

bash
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:

json
{
  "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.

bash
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:

bash
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:

json
{
  "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

bash
curl "https://aviorapay.app/v1/med/cases/med_3f1c2b9e-0d7a-4a55-9a51-6f0e3c1d2b10/evidence" \
  -H "apikey: $AVIORAPAY_API_KEY"
json
{
  "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:

HTTPerror.codeQuando
400invalid_requestFiltro 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.
404not_foundO caso (ou a evidência) não existe na sua conta.
409EVIDENCE_NOT_UPLOADEDcomplete chamado antes de o arquivo chegar ao armazenamento (ou depois de a URL expirar).
409EVIDENCE_SIZE_MISMATCHO tamanho do arquivo armazenado difere do file_size declarado.
503service_unavailableO 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:

ts
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

CampoSignificado
referenceIdO end_to_end_id (E2E) do Pix original contestado.
statusEstado do caso como informado: created | delivered | closed | canceled.
resultResultado do julgamento, quando fechado: agreed (perdido) | disagreed (ganho).
methodMotivo alegado: scam | unauthorized | coercion | invasion | other.
reasonDescrição textual do caso.
payerName / payerDocumentIdentidade de quem abriu o MED, quando informada.
openedAt / updatedAt / closedAtDatas do caso como informadas (abertura / atualização / fechamento).
lastEventAtQuando a AvioraPay processou o evento mais recente deste caso.
amountMismatchtrue 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

CampoSignificado
reasonMotivo textual da falha informado pelo provedor.
providerErrorCodeCódigo/status do provedor (ex.: expired, canceled) — o que decidiu o estado final.
ttlSecondsTempo de vida do QR Pix em segundos (pix.expires_at − criação), quando a cobrança tinha validade. Ausente quando não tinha.
atQuando 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.