kamiPay LogokamiPay Docs

Pay-Out MED Claims

Open a MED claim to recover a Pix pay-out you sent to a fraudulent destination, and track it until it is resolved

The MED (Mecanismo Especial de Devolução) is the Central Bank of Brazil's mechanism for recovering Pix funds that were sent as the result of fraud. These endpoints let you open a MED claim over one of your own pay-outs and follow it until it is resolved, without going through kamiPay support.

This is for pay-outs you sent. MED claims opened against a charge you received are handled by kamiPay and are not part of this API.

A claim is irreversible, and there is only one per pay-out. The Central Bank accepts a single MED per transaction, and it cannot be reopened with a different reason. Review reason and details before sending. Do not send a second request for a pay-out you have already claimed, unless the first submission failed in a way that is safe to retry. For a tracked claim, a second request is rejected with 409.

How a claim works

You send POST /v2/payout_meds, and kamiPay validates the request. A valid claim is accepted with 202 Accepted in one of two ways, depending on the pay-out. You cannot choose between them, so your integration must handle both:

  • Tracked claim — the response carries a med_case_id.
    1. kamiPay records the claim before contacting the provider, then submits it in the background. While that happens the claim is in the dispatching status.
    2. You follow the claim with GET /v2/payout_meds/{med_case_id}.
  • Claim handled by kamiPay — the response carries med_case_id: null. The kamiPay team files and follows up the claim for you. It cannot be tracked through this API: to learn how it ends, contact kamiPay support. See the response.

There is no webhook for MED claims. Poll the claim status endpoint to follow a claim. The process is measured in days, not seconds: a few requests per day per open claim is enough.

Claim statuses

These statuses apply to tracked claims. status_id moves forward only — a claim never goes back to an earlier status.

status_idMeaningTerminal
dispatchingThe claim is recorded and being submitted to the provider. See outcome_hint.no
openThe provider registered the claim with the Central Bank.no
trackingThe Central Bank is tracing where the funds went.no
under_analysisWaiting for the institution that received the funds to analyse the claim.no
analysedThe claim has been analysed; the refund, if any, has not started yet.no
refundingA refund is in progress.no
closedThe claim is finished. Check money_refunded and refunded_amount for what came back.yes
cancelledThe claim ended without a decision on the merits. See below.yes

A claim that goes from dispatching straight to cancelled was rejected by the provider when it was submitted. It never reached analysis, and it cannot be reopened through this API: a new POST for the same pay-out is rejected with 409 (already_exists). Contact kamiPay support.

A finished claim does not mean the money came back. A claim can be decided in your favour and still return only part of the amount — or nothing, if the receiving account was already emptied. Always read money_refunded and refunded_amount; do not infer the outcome from status_id or result_id.

Before you open a claim

A claim is accepted only when all of the following hold:

  • The pay-out is yours. A pay-out that belongs to another account is answered exactly like one that does not exist (404), so its existence is never disclosed.
  • It is within the Central Bank's window. The Central Bank accepts claims up to 80 days after the transaction. To leave room for processing, open yours within 79 days of the pay-out.
  • For a tracked claim, the pay-out has an end-to-end id. The claim is filed against the Pix transaction itself, so the pay-out must have been processed by the bank. A claim handled by kamiPay does not need it.
  • No claim exists yet for that pay-out.

Include the payer's details in your pay-outs. A claim that identifies who ordered the payment is stronger before the institution that analyses it. If the pay-out was sent without payer_document, the claim is still accepted, but the response carries a warning. See Paying Pix.

Open a claim

POST /v2/payout_meds

Authenticate with the same credentials you use to send pay-outs.

Request Body

You must provide exactly one identifier for the pay-out. Sending both returns 422, so that a claim is never opened on a pay-out other than the one you meant:

NameTypeDescription
kamipay_idstringThe kamiPay id of the pay-out. Send this or payout_external_id, not both.
payout_external_idstringThe external_id you sent when you created the pay-out. Send this or kamipay_id, not both.
reasonstringRequired. Why you are claiming. One of the values in the table below.
detailsstringRequired. What happened, in your own words. Between 10 and 4000 characters.
external_idstringOptional. Your own reference for this claim (max 128 chars). Must be unique across your claims.
filesarrayNot supported yet. Sending it returns 422.

reason values

ValueWhen to use it
scamThe account holder was deceived into authorising the payment.
account_takeoverSomeone else took control of the account and ordered the payment.
coercionThe account holder was forced to make the payment.
credentials_stolenThe payment was made with stolen credentials.
other_fraudFraud that does not fit the categories above.
unknownFraud is suspected but the circumstances are not known yet.

details is read by a person at the bank that analyses the claim. kamiPay embeds it in a fixed template that also carries the transaction data, so you do not need to repeat amounts, dates or ids. Line breaks are collapsed into spaces.

Example Request

const url = `${baseURL}/v2/payout_meds`;

const body = {
  "payout_external_id": "payment-unique-id-001",
  "reason": "scam",
  "details": "The customer reports they were contacted by someone impersonating the bank and instructed to transfer the funds.",
  "external_id": "claim-2026-0001"
};

const response = await fetch(url, {
  method: "POST",
  headers: {
    Authorization: `Bearer ${access_token}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify(body)
});
import requests

url = f"{base_url}/v2/payout_meds"

body = {
    "payout_external_id": "payment-unique-id-001",
    "reason": "scam",
    "details": "The customer reports they were contacted by someone impersonating the bank and instructed to transfer the funds.",
    "external_id": "claim-2026-0001",
}

headers = {
    'Authorization': f'Bearer {access_token}',
    'Content-Type': 'application/json',
}

response = requests.post(url, json=body, headers=headers)

Response

A successful request returns 202 Accepted, in one of the two ways a claim can be accepted.

The claim is already recorded; the submission to the provider happens in the background.

{
  "med_case_id": "3f8b2c1e-7d4a-4f9b-8e21-6c5d0a9b1f47",
  "status_id": "dispatching",
  "message": "El reclamo quedó registrado y se está enviando al proveedor."
}

When the pay-out was sent without payer details, the response also carries a warning:

{
  "med_case_id": "3f8b2c1e-7d4a-4f9b-8e21-6c5d0a9b1f47",
  "status_id": "dispatching",
  "message": "El reclamo quedó registrado y se está enviando al proveedor.",
  "warning": "El pay-out no tiene datos del pagador, así que el reclamo va sin identificar quién ordenó el pago y queda más débil. Mandá `payer_name` y `payer_document` en el POST del pago para los próximos."
}
FieldTypeDescription
med_case_idstringIdentifier of the claim. Use it with Get a claim.
status_idstringAlways dispatching at this point.
messagestringHuman-readable confirmation.
warningstringOnly present when the pay-out has no payer details. The claim is accepted anyway.

For some pay-outs the claim is filed and followed up by the kamiPay team. The request is accepted, but no trackable claim is created: med_case_id is null, and the claim does not appear in List your claims. To learn how it ends, contact kamiPay support.

{
  "outcome": "manual",
  "handling": "manual",
  "med_case_id": null,
  "message": "Recibimos tu reclamo. Este pay-out no admite seguimiento por API: lo gestiona el equipo de kamiPay, y para conocer su estado contactá a soporte."
}
FieldTypeDescription
outcomestringAlways manual in this response. Branch on it, or on med_case_id being null.
handlingstringAlways manual in this response.
med_case_idnullThere is no claim to follow through this API.
messagestringHuman-readable confirmation.

Do not send the claim again. The claim is already with the kamiPay team, and a second request for the same pay-out is rejected with 409 (manual_claim_already_sent). To follow up or correct a claim, contact kamiPay support.

The pay-out does not exist, or it does not belong to your account.

{
  "detail": {
    "reason": "payout_not_found",
    "message": "No encontramos ese pay-out."
  }
}

A claim already exists for this pay-out. med_case_id points to it:

{
  "detail": {
    "reason": "already_exists",
    "med_case_id": "3f8b2c1e-7d4a-4f9b-8e21-6c5d0a9b1f47",
    "status_id": "under_analysis",
    "message": "Ya existe un reclamo para este pay-out."
  }
}

If two requests for the same pay-out race each other, the one that loses also gets already_exists, but without med_case_id. Use List your claims to find it.

The external_id you sent is already used by another of your claims:

{
  "detail": {
    "reason": "duplicate_external_id",
    "message": "Ya usaste ese `external_id` en otro reclamo. Mandá uno distinto, o sacalo y te devolvemos el `med_case_id`."
  }
}

A claim for this pay-out was already received by the kamiPay team. It does not need to be sent again:

{
  "detail": {
    "reason": "manual_claim_already_sent",
    "message": "Ya recibimos tu reclamo por este pay-out y lo está gestionando el equipo de kamiPay. No hace falta reenviarlo: para conocer su estado, contactá a soporte."
  }
}

A previous request for this pay-out is still being processed, and it is not confirmed yet. Send the same request again in a few minutes: if the first one was registered, you get manual_claim_already_sent; if it failed, the new one goes through.

{
  "detail": {
    "reason": "manual_claim_in_progress",
    "message": "Hay un reclamo por este pay-out que todavía se está procesando. Volvé a enviarlo en unos minutos: si ya quedó registrado, te lo vamos a confirmar."
  }
}

The pay-out cannot be claimed. reason says why:

{
  "detail": {
    "reason": "outside_window",
    "message": "..."
  }
}
reasonMeaning
outside_windowThe pay-out is older than the claim window.
payout_date_unknownThe pay-out has no date, so the window cannot be checked.
e2e_not_resolvedThe pay-out has no end-to-end id yet. Tracked claims only.
evidence_upload_unavailableThe request included files, which are not supported yet. Send the claim without them.

A malformed body — for example a missing reason, an unknown reason value, details shorter than 10 characters, or neither or both pay-out identifiers — returns 422 with the standard validation error list.

Too many requests. This endpoint accepts 20 requests per minute per IP address. Wait a minute before retrying.

The claim could not be confirmed as registered. You can send the same request again after a few minutes. The first attempt may still have reached the kamiPay team, so a retry can produce a second claim for the same pay-out. If that matters for this pay-out, check with kamiPay support before retrying.

{
  "detail": {
    "reason": "claim_not_confirmed",
    "message": "No pudimos confirmar el registro de tu reclamo. Intentá de nuevo en unos minutos."
  }
}

Branch on reason, not on message. reason is stable and safe to use in your code. message is meant for people, is currently in Spanish, and may change.

Get a claim

GET /v2/payout_meds/{med_case_id}

Returns the current state of one of your claims. This is how you follow a claim after opening it.

Path Parameters

NameTypeDescription
med_case_idstringThe med_case_id returned when the claim was opened. Must be a valid UUID; a malformed value returns 422.

Example Request

const url = `${baseURL}/v2/payout_meds/${medCaseId}`;

const response = await fetch(url, {
  headers: { Authorization: `Bearer ${access_token}` },
});
import requests

url = f"{base_url}/v2/payout_meds/{med_case_id}"
headers = {'Authorization': f'Bearer {access_token}'}

response = requests.get(url, headers=headers)

Response

{
  "med_case_id": "3f8b2c1e-7d4a-4f9b-8e21-6c5d0a9b1f47",
  "kamipay_id": "txc_01kr3m9p7n2s4d8h6e5b3t7w1k",
  "e2e_id": "E27084098202609151454hsbAxK6FzAM",
  "status_id": "closed",
  "result_id": null,
  "reason": "scam",
  "money_refunded": true,
  "refunded_amount": "1250.00",
  "external_id": "claim-2026-0001",
  "created_at": "2026-09-15T14:54:12.381204+00:00"
}

The claim does not exist, or it does not belong to your account.

{
  "detail": {
    "reason": "not_found",
    "message": "No encontramos ese reclamo."
  }
}

Too many requests. This endpoint accepts 60 requests per minute per IP address.

Claim Fields

FieldTypeDescription
med_case_idstringIdentifier of the claim.
kamipay_idstringThe pay-out the claim is about.
e2e_idstringEnd-to-end id of the Pix transaction being claimed.
status_idstringWhere the claim is. See Claim statuses.
result_idstring | nullThe decision on the claim, when the provider reports it: agreed (in your favour) or disagreed. It is often null — do not rely on it to know whether money came back.
reasonstringThe reason you sent when opening the claim.
money_refundedbooleanWhether any money has come back so far. It stays false until a refund arrives, so on an open claim false means not yet, not never.
refunded_amountstring | nullHow much came back, in BRL. null until a refund arrives. It can be less than the pay-out amount.
external_idstring | nullYour own reference for the claim, as sent.
created_atstringWhen the claim was opened (ISO 8601).
outcome_hintobjectOnly present while status_id is dispatching. See below.

Claim fields never include the text submitted to the Central Bank nor any personal data of third parties.

While a claim is dispatching

While status_id is dispatching, the response adds an outcome_hint that tells you whether it is safe to open the claim again:

{
  "outcome_hint": {
    "retryable": true,
    "message": "El envío al proveedor no se pudo completar. Podés volver a reclamar este pay-out."
  }
}
retryableWhat it meansWhat to do
trueThe provider rejected the submission before it reached the Central Bank.Send the same POST again. See below.
falsekamiPay has no confirmation from the provider yet.Do not retry. If the claim was registered, a second request could duplicate it. Keep polling.

Retrying a retryable claim reuses the same claim. The new POST returns 202 with the same med_case_id, and the claim takes the reason and details of the new request — so a retry is also your chance to correct them. external_id is replaced only if you send one; otherwise the claim keeps the one it had. As soon as the retry is accepted, retryable goes back to false.

If another retry for the same pay-out is already in progress, the request is rejected with 409 (already_exists), carrying the med_case_id and status_id: dispatching. Do not send it again; keep polling.

List your claims

GET /v2/payout_meds

Returns your claims, newest first.

Query Parameters

NameTypeDescription
limitintegerOptional. How many claims to return. Default 50, minimum 1, maximum 200.

Example Request

const url = `${baseURL}/v2/payout_meds?limit=20`;

const response = await fetch(url, {
  headers: { Authorization: `Bearer ${access_token}` },
});
import requests

url = f"{base_url}/v2/payout_meds"
headers = {'Authorization': f'Bearer {access_token}'}

response = requests.get(url, headers=headers, params={'limit': 20})

Response

200 with an array of claims, each with the same fields as Get a claim. An account with no claims gets an empty array. This endpoint accepts 60 requests per minute per IP address.

[
  {
    "med_case_id": "3f8b2c1e-7d4a-4f9b-8e21-6c5d0a9b1f47",
    "kamipay_id": "txc_01kr3m9p7n2s4d8h6e5b3t7w1k",
    "e2e_id": "E27084098202609151454hsbAxK6FzAM",
    "status_id": "under_analysis",
    "result_id": null,
    "reason": "scam",
    "money_refunded": false,
    "refunded_amount": null,
    "external_id": "claim-2026-0001",
    "created_at": "2026-09-15T14:54:12.381204+00:00"
  }
]

On this page