> ## Documentation Index
> Fetch the complete documentation index at: https://docs.purin.cash/llms.txt
> Use this file to discover all available pages before exploring further.

# Disputas

> O cliente contestou o PIX. Como acompanhar, responder com provas e saber o resultado, tudo pela API.

Disputa é a contestação de um PIX pelo pagador (MED, o Mecanismo Especial de Devolução). O comprador aciona o banco dele dizendo, por exemplo, que não recebeu o produto, e o valor daquela venda fica retido no seu saldo até o banco decidir.

Enquanto a disputa está aberta ou depois que ela é perdida, o valor aparece em `disputeBlocked` na [carteira](https://docs.purin.cash/guias/carteira.md) e sai do `withdrawable`. Não dá pra sacar o que está contestado.

Tudo o que o painel faz com disputa a API também faz: listar, consultar, responder com provas e receber o resultado por webhook. A chave precisa da permissão **Gerenciar disputas** (`disputas.gerenciar`).

## O fluxo

**1. Chega o webhook dispute.opened**

A disputa acabou de abrir. O corpo traz o `disputeId`, o `orderCode` e o `endToEndId` da venda. Veja o [corpo dos eventos](https://docs.purin.cash/webhooks/visao-geral.md#corpo-dos-eventos-de-contestacao).

**2. Você junta as provas da venda**

Comprovante de entrega, log de acesso, conversa com o comprador. Use o `orderCode` para achar a venda do seu lado.

**3. Envia a defesa**

`POST /v1/disputes/{id}/evidence` com os arquivos e a sua explicação. A defesa vai direto ao banco do pagador.

**4. Acompanha o resultado**

`dispute.under_review` quando o banco começa a analisar, e depois `dispute.won` (o valor volta ao saldo) ou `dispute.lost` (o valor volta ao pagador).

## Status

| `status` | `statusLabel` | O que significa | Aceita resposta |
| - | - | - | - |
| `aberta` | aberta | O pagador contestou e o valor está retido | Sim |
| `pendente` | em análise | A defesa chegou e o banco está analisando | Não |
| `resolvida` | ganha | Decidida a seu favor. O valor volta ao saldo | Não |
| `perdida` | perdida | Decidida a favor do pagador | Não |

Não precisa replicar essa regra do seu lado: o campo `canRespond` já diz se a disputa aceita resposta agora.

> [!NOTE]
>
> Disputa nasce de uma transação real, então não existe em sandbox. Com chave `ps_test_`, a lista volta vazia com `"sandbox": true`, a consulta responde `404` e o envio de provas responde `400`.

## Listando

```bash theme={null}
curl "https://api.purin.cash/v1/disputes?status=aberta,pendente&limit=50" \
  -H "Authorization: Bearer $PURINCASH_KEY"
```

```json Resposta 200 theme={null}
{
  "disputes": [
    {
      "id": "665f1c2ab9e8d40012a4c789",
      "code": "DSP-7KQ2ZD",
      "status": "aberta",
      "statusLabel": "aberta",
      "canRespond": true,
      "amount": 49.9,
      "currency": "BRL",
      "reason": "Produto não recebido",
      "providerDisputeId": "8f1e2d3c-4b5a-6978-8c9d-0e1f2a3b4c5d",
      "endToEndId": "E00000000202610101432abcdef123456",
      "orderCode": "PED-1234",
      "buyer": "joao_silva",
      "buyerDiscordId": "123456789012345678",
      "product": "Plano Pro",
      "subAccountId": "",
      "defense": { "sentAt": null, "failedAt": null, "failReason": "" },
      "evidences": [],
      "resolvedAt": null,
      "createdAt": "2026-10-10T14:32:10.000Z",
      "updatedAt": "2026-10-10T14:32:10.000Z"
    }
  ],
  "total": 1,
  "limit": 50,
  "offset": 0
}
```

**status** `string`

Um status ou vários separados por vírgula: `aberta,pendente`. Valor fora de `aberta`, `pendente`, `resolvida` e `perdida` responde `400`.

**orderCode** `string`

Só as disputas desse pedido.

**endToEndId** `string`

Só as disputas desse PIX.

**from** `string`

Abertas a partir desta data (ISO 8601).

**to** `string`

Abertas até esta data (ISO 8601).

**limit** `integer` (default: 50)

De 1 a 100. Use com `offset` para paginar.

### Campos que valem atenção

**canRespond** `boolean`

`true` quando a disputa aceita resposta agora. Pode vir `false` numa disputa `aberta` nos primeiros minutos, enquanto o banco ainda não registrou a contestação.

**providerDisputeId** `string`

Identificador da contestação no banco. Vem vazio logo depois da abertura.

**defense** `object`

A situação da sua defesa.

**campos**

**sentAt** `string | null`

Quando a defesa chegou ao banco.

**failedAt** `string | null`

Quando a última tentativa de envio falhou.

**failReason** `string`

O motivo dessa falha. Vazio quando não falhou.

**subAccountId** `string`

A [subconta](https://docs.purin.cash/guias/subcontas.md) da venda contestada, quando houver. O valor fica retido nela.

> [!TIP]
>
> Mesmo com o webhook, vale rodar a listagem com `status=aberta` uma vez por dia. Disputa tem prazo, e perder por não ter respondido é o pior jeito de perder.

## Respondendo

Só disputa com `canRespond: true` aceita resposta. Você pode mandar arquivos, links e um texto explicando o caso, em qualquer combinação. Um dos três é obrigatório.

```bash theme={null}
curl -X POST https://api.purin.cash/v1/disputes/665f1c2ab9e8d40012a4c789/evidence \
  -H "Authorization: Bearer $PURINCASH_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "files": [
      { "name": "entrega-pedido-1234.pdf", "data": "data:application/pdf;base64,JVBERi0xLjQK..." }
    ],
    "textForPdf": "O comprador recebeu o produto em 10/10/2026 às 14:35, como mostra o comprovante anexo. O acesso foi usado no mesmo dia."
  }'
```

```json Resposta 200 theme={null}
{
  "uploaded": 2,
  "sentToBank": true
}
```

**files** `array`

Até 5 arquivos de até 5 MB cada, em PDF, PNG, JPEG ou WebP. Cada item é `{ "name", "data" }`, com `data` em data URL base64. A gente guarda o arquivo e anexa à defesa.

**documents** `array`

Até 10 links públicos de provas, cada um como `{ "url", "description" }`. Link sem `http` ou `https` é descartado.

**textForPdf** `string`

A sua explicação do caso. Vira um PDF anexado à defesa.

**uploaded** `integer`

Quantas provas entraram neste envio: arquivos, links válidos e o PDF do texto.

**sentToBank** `boolean`

`true` quando a defesa já foi entregue ao banco. `false` quando ficou registrada aqui e a nossa equipe encaminha.

> [!WARNING]
>
> **Prefira `files`.** Vários bancos recebem a defesa só como arquivo: o que vai em `files` segue anexado, e o que vai em `documents` aparece citado no texto. Se o seu comprovante está numa página, gere um PDF e mande como arquivo.

Para transformar um arquivo em data URL no Node:

```js theme={null}
import fs from "node:fs";

const data = "data:application/pdf;base64," + fs.readFileSync("entrega.pdf").toString("base64");
```

O corpo inteiro da requisição pode ter até 25 MB. Enquanto a disputa aceitar resposta, você pode enviar de novo: cada envio soma provas, não substitui as anteriores.

## Consultando uma disputa

```bash theme={null}
curl https://api.purin.cash/v1/disputes/665f1c2ab9e8d40012a4c789 \
  -H "Authorization: Bearer $PURINCASH_KEY"
```

Volta a mesma disputa da listagem, com as provas já enviadas e o campo `history`, a linha do tempo dos status. Cada item corresponde a um webhook enviado:

```json theme={null}
{
  "id": "665f1c2ab9e8d40012a4c789",
  "status": "pendente",
  "statusLabel": "em análise",
  "canRespond": false,
  "defense": { "sentAt": "2026-10-10T16:05:00.000Z", "failedAt": null, "failReason": "" },
  "history": [
    { "event": "dispute.opened", "from": null, "to": "aberta", "at": "2026-10-10T14:33:00.000Z" },
    { "event": "dispute.under_review", "from": "aberta", "to": "pendente", "at": "2026-10-10T16:10:00.000Z" }
  ]
}
```

Os links das provas são assinados e expiram. Não guarde a URL: guarde o `id` e consulte de novo quando precisar.

Disputa que não é da sua conta responde `404`, e não `403`. É proposital: a API não confirma a existência de recurso de terceiro.

## Webhooks

Quatro eventos acompanham a disputa: `dispute.opened`, `dispute.under_review`, `dispute.won` e `dispute.lost`. Eles chegam nas mesmas URLs de webhook da conta, com o mesmo corpo, mudando só o `event` e o `status`. O corpo completo está em [Como funcionam os webhooks](https://docs.purin.cash/webhooks/visao-geral.md#corpo-dos-eventos-de-contestacao).

O `X-Webhook-Id` desses eventos é `evento:disputeId`, por exemplo `dispute.opened:665f1c2ab9e8d40012a4c789`. Cada mudança de status de cada disputa tem a sua chave, então duas disputas do mesmo pedido nunca se confundem na sua deduplicação.

## O que costuma funcionar como prova

> [card] Prova de entrega
>
> Log de acesso, e-mail de entrega com data e hora, código de rastreio, print do sistema mostrando a liberação.

> [card] Prova de aceite
>
> Termos aceitos, confirmação de recebimento, conversa em que o comprador reconhece que recebeu.

> [card] Identidade do comprador
>
> O `endToEndId` da transação, o e-mail usado na compra, o `externalId` que amarra ao usuário da sua base.

> [card] Contexto de uso
>
> Histórico mostrando que a conta foi usada depois da compra. É o que mais derruba "produto não recebido".

> [!TIP]
>
> Junte as provas de forma programática assim que o pagamento confirma, não quando a disputa chega. Trinta dias depois o log já rotacionou e o print não existe mais.

## Erros

| Código | Motivo |
| - | - |
| `400` | Disputa que não está mais `aberta`, corpo sem `files`, `documents` nem `textForPdf`, nenhum arquivo ou link válido, mais de 5 arquivos, arquivo acima de 5 MB, `status` inválido no filtro, ID em formato inválido, ou envio em sandbox |
| `404` | Disputa inexistente, de outra conta, ou consulta em sandbox |
| `409` | A disputa ainda não pode ser respondida (`canRespond: false`). Tente em alguns minutos |
| `502` | O banco recusou ou não respondeu. O campo `detail` traz o motivo. Tente de novo |


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.