Skip to content

Card disputes

When one of your cards paid for something that never arrived, wasn’t as described, was charged twice or wasn’t yours, a card dispute (card_dispute, cdsp_…) asks the card network for the money back. It’s your business’s statement to the network, so ask the merchant for a refund first: the network expects you to have. A customer disputing a payment to you is covered under disputes.

Card purchasectx_… (a capture)unsubmitteddraft: evidence, filessubmittedonce, by a personwonmoney backlostloss_reason_textexpirednever submittedPOST …/disputesPOST …/submitpast the deadline
  1. Draft. POST /v1/card_transactions/{id}/disputes with a reason, any evidence, and submit: false. Only a capture (money a merchant took) can be disputed, never an authorisation, a refund or a push payment. One open dispute per purchase.
  2. Evidence. POST /v1/card_disputes/{id} adds evidence or changes the reason or amount. Fields you send replace what’s saved; null clears one.
  3. Files. POST /v1/card_disputes/{id}/files attaches a PDF or JPEG.
  4. Submit. POST /v1/card_disputes/{id}/submit. Only one submission is allowed, with nothing added after, so we check the evidence first.
  5. Decided. The network decides won or lost. A draft not submitted by the network’s deadline ends expired.

One step: submit: true on create (the default for a secret key) files and submits in one request, with the same checks as a submission. It suits a fraud dispute, which needs only an explanation.

Terminal window
curl https://api.tuppence.ai/v1/card_transactions/ctx_…/disputes \
-H "Authorization: Bearer $TUPPENCE_SECRET_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"reason": "not_received",
"submit": false,
"evidence": {
"product_type": "merchandise",
"product_description": "12 boxes of printer paper, order 4471",
"expected_at": 1790208000
}
}'
curl -X POST https://api.tuppence.ai/v1/card_disputes/cdsp_…/submit \
-H "Authorization: Bearer $TUPPENCE_SECRET_KEY"
Field or call
statusunsubmitted (draft), submitted, won, lost, expired
amountMinor units. Defaults to what the merchant took and hasn’t returned: refunds received and other disputes of the purchase are subtracted. More is 400 parameter_invalid (param: amount)
explanationTop-level shortcut for the evidence’s explanation: 10–2,000 characters, the words the network reads
prefill: trueFills evidence from your agent’s records (below). A draft created with no evidence is prefilled anyway
GET /v1/card_disputesNewest first; ?card=, ?status=. GET /v1/card_disputes/{id} reads one. Reads need card_transactions:read, writes card_transactions:write

A submission missing its reason’s required fields is refused, so we check first. Until they’re all there, submitting returns 400 dispute_evidence_incomplete. Its missing_evidence lists each missing field and, in plain words, what to ask for.

reasonRequired before submitting
fraudulentexplanation: you didn’t make this purchase and don’t know who did
no_valid_authorizationexplanation
not_receivedproduct_type, product_description, expected_at
canceledproduct_type, product_description, expected_at, cancellation_policy_provided, canceled_at, cancellation_reason. For goods (product_type: merchandise), also returned_at and return_status
merchandise_not_as_describedreceived_at, return_status, returned_at, return_description, explanation
service_not_as_describedreceived_at, canceled_at, cancellation_reason, explanation
duplicateExactly one proof it was paid already: original_transaction (the original ctx_…), or a file as card_statement, cash_receipt or check_image
otherproduct_type, product_description, explanation
Field typeFormat
Dates: expected_at, received_at, canceled_at, returned_atUnix seconds. returned_at after received_at
product_typemerchandise (goods) or service
return_statussuccessful (they took it back) or merchant_rejected
cancellation_policy_providedtrue or false
TextUp to 2,000 characters
FilesNot here: use POST /v1/card_disputes/{id}/files
{
"error": {
"type": "invalid_request_error",
"code": "dispute_evidence_incomplete",
"message": "Before a not received dispute can be submitted, add: when it should have arrived.",
"param": "evidence[expected_at]",
"missing_evidence": [{ "field": "expected_at", "label": "when it should have arrived" }],
"request_id": "req_…"
}
}

Drafted from your agent’s records. When one of your agents made the purchase, the draft starts from what it knows: what was ordered, from whom (its cart, or a person’s approval), when, and the mandate it bought under. The evidence then has drafted_from_trail: true. Check it before you submit.

Attach a PDF or JPEG up to 5 MB (the networks’ limit) to a draft, as multipart/form-data: a file part and, optionally, a field part naming the evidence field.

Terminal window
curl https://api.tuppence.ai/v1/card_disputes/cdsp_…/files \
-H "Authorization: Bearer $TUPPENCE_SECRET_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-F file=@delivery-tracking.pdf \
-F field=additional_documentation
  • field defaults to additional_documentation. A duplicate dispute can also take card_statement, cash_receipt or check_image. That is its one proof of payment, so it replaces any other.
  • One file per field; a new one replaces the last. Send a multi-page document as one PDF.
  • The file is stored on your account. We keep its File id (file_…), name, type and size in files, never the bytes.
  • We read the type from the file’s first bytes, not from what the upload claims.
  • MCP can’t attach files: attach them here or in the dashboard.
Network and reasonWindow after the purchase clears
Visa, no_valid_authorization65 days
Mastercard, duplicate80 days
Everything else110 days (a draft expires then)
Date
dispute_byThe network’s deadline less a 5-day margin (Unix seconds). Submit by then
ReminderA draft still unsubmitted 7 days before dispute_by: email to the account’s owners, a push and an alert
Too lateFiling or submitting after the network’s deadline: 400 dispute_window_closed, with dispute_by

We check the card networks’ rules before the dispute is filed, so any refusal comes from us, in plain words, and your draft is kept.

RuleRefusal
35 fraud disputes per card in 120 days. The networks reject more fraudulent disputes on one card400 dispute_not_allowed
Cancelled, then wait 15 days. canceled and service_not_as_described are rejected within 15 days of cancelling, because the merchant has until then to refund400 dispute_not_allowed, with available_at (the first moment it’s accepted)
Refunds are subtracted. Only what hasn’t come back400 parameter_invalid on amount
Only captures. Not an authorisation, refund or push payment400 parameter_invalid

The right to dispute fraud is never limited. Card limits act on cards, spend and provisional credit, never on filing a dispute.

Filing fraudulent cancels the card (as stolen). This is required, because while the card stays active every later fraud dispute on it is lost. In the same step we issue a replacement with the same spending policy, agent and limits (replacement_card on the dispute). A fraud replacement is always allowed once, whatever the card limits say. A single-use card is cancelled, not replaced.

The response also lists the card’s other purchases from the last 30 days as other_recent_purchases, so you can ask the cardholder whether those were theirs too.

In the UK and the EEA, for fraudulent and no_valid_authorization disputes, Tuppence credits the disputed amount to your cards’ balance while the network decides, usually by the next business day. This is provisional credit. The dispute’s provisional_credit shows amount and status (none, credited, reversed).

New accounts hold itOn Probation and Starter (and before review), it’s in your balance but purchases can’t spend it until the dispute is decided. Such a purchase is declined with funds_under_review. Established and Trusted can spend it at once. issuing_provisional_credit in your card limits says which applies
Always reversedWhen decided, won or lost, it’s taken back. A win is then credited separately (issuing_dispute), so nothing counts twice
balance_transactionsEach movement: type (issuing_dispute_provisional_credit, issuing_dispute_provisional_credit_reversal, issuing_dispute) and a signed amount
WonThe money returns to what your cards can spend; cashback earned on the purchase is reversed
Lost (or expired)loss_reason is the network’s code; loss_reason_text says it in plain words, e.g. “The purchase was approved with 3D Secure, so the network treats it as yours.”
No dispute feeThe network charges a fee for every card dispute. Tuppence pays it, never your business

A dispute is the business’s statement to the network, so a person submits it. An agent can do everything before that.

WhoCan
Agent, restricted key (rk_…)Create drafts, add evidence, attach files. submit defaults to false; submitting (POST …/submit or submit: true) is 403 dispute_submission_needs_person, and the draft is kept
Agent, MCPsubmit_card_dispute checks the evidence, opens an approval for a person and returns approval_id. If approved in the dashboard or app, it’s submitted as that person; if declined, the draft is unchanged. The approval waits until dispute_by or a week, whichever comes first. See MCP
A person (secret key, dashboard, app)File and submit, always

We email the account’s owners when a dispute is submitted, won, lost or expired, and send the 7-day reminder. For a connected account, the emails go to the platform’s owners and name the business.

Event (each carries the card_dispute)When
card_dispute.createdFiled: a draft, or filed and submitted
card_dispute.updatedEvidence, files, reason or amount changed
card_dispute.submittedSent to the card network
card_dispute.funds_reinstatedMoney back to your cards’ balance: provisional credit, or a win
card_dispute.funds_rescindedMoney taken back: usually the provisional credit, at resolution
card_dispute.closedDecided (won, lost), or expired

A platform’s Connect endpoint receives its connected accounts’ card dispute events, each with account. See Webhooks.

DoResult
explanation of winning_evidence or losing_evidenceA submitted dispute is decided at once: won or lost. Any other explanation stays submitted
POST /v1/test_helpers/card_disputes/{id}/close with outcomewon or lost decides a submitted dispute; expired runs a draft out of time

See Testing.

CodeHTTPMeans
dispute_evidence_incomplete400The reason’s evidence isn’t complete: see missing_evidence
dispute_window_closed400Too late for this reason on this card’s network. dispute_by was the date
dispute_not_allowed400A network rule refuses it now: within 15 days of cancelling (available_at says when), or the card’s 35 fraud disputes in 120 days
dispute_submission_needs_person403An agent’s key tried to submit. A person submits, or approves the agent’s request
invalid_state400Already submitted or decided, or the purchase already has an open dispute
parameter_invalid400Not a capture, or amount above what can be disputed

Docs assistant

Open as a page
Loading the assistant…