Disputes API Guide - Flex Documentation
Documentation Index
Fetch the complete documentation index at: /llms.txt
Use this file to discover all available pages before exploring further.
Disputes (chargebacks) occur when a cardholder challenges a charge with their bank. Flex automatically syncs disputes the moment they are filed and exposes a full API for listing, retrieving, submitting evidence, and closing disputes.
Table of Contents
- Overview
- Authentication
- The Dispute Object
- Dispute Status Lifecycle
- Dispute Reason Codes
- Endpoints
- Uploading Evidence Files
- Webhook Events
- Error Reference
- End-to-End Testing Guide
Overview
Cardholder files dispute with their bank
│
▼
charge.dispute.created → Flex syncs dispute
│
▼
Partner receives webhook event: charge.dispute.created
│
├─── Partner calls GET /v1/disputes/:id (inspect dispute)
│
├─── Partner calls POST /v1/disputes/:id (submit evidence)
│ └─ submit: false → save draft, do not submit yet
│ └─ submit: true → submit evidence immediately
│
└─── Partner calls POST /v1/disputes/:id/close (accept loss)
When a dispute is opened, Flex:
- Receives the
charge.dispute.createdevent. - Creates a
Disputerecord linked to yourpayment_intent_idandcharge_id. - Forwards a
charge.dispute.createdevent to your registered webhook endpoint.
You have until evidence_details.due_by to respond. Submitting evidence with submit: true sends it immediately and increments submission_count. You may re-submit with updated evidence before the deadline. Alternatively, POST /v1/disputes/:id/close concedes the dispute immediately.
Authentication
All requests require a bearer token (API key).
Authorization: Bearer fsk_...
Your API key is scoped to your partner account. Disputes for other partners are never visible regardless of the ID used — they return 404.
The Dispute Object
{
"dispute": {
"dispute_id": "fdsp_01jx3q8kt1b9a7n2c4d5e6f7g8",
"payment_intent_id": "fpi_01jx3q7ab1c8d2e3f4g5h6j7k8",
"charge_id": "fch_01jx3q6ab1c7d2e3f4g5h6j7k8",
"amount": 4999,
"currency": "usd",
"status": "needs_response",
"reason": "fraudulent",
"is_charge_refundable": false,
"metadata": {},
"created_at": "2026-02-10T14:30:00Z",
"test_mode": false,
"evidence_details": {
"due_by": "2026-03-01T23:59:59Z",
"has_evidence": false,
"past_due": false,
"submission_count": 0
},
"balance_transactions": [
{
"balance_transaction_id": "fbt_01jx3qabc123",
"amount": -4999,
"fee": -1500,
"net": -6499,
"status": "available",
"type": "adjustment",
"created_at": "2026-02-10T15:00:00Z"
}
],
"payment_method_details": {
"type": "card",
"card": {
"brand": "visa",
"network_reason_code": "10.4"
}
}
}
}
Field Reference
| Field | Type | Description |
|---|---|---|
dispute_id |
string | Flex dispute ID (fdsp_ prefix). Use this in all API calls. |
payment_intent_id |
string | Flex payment intent ID (fpi_) linked to this dispute. |
charge_id |
string | |
| null | Flex charge ID (fch_) linked to this dispute. |
|
amount |
integer | Disputed amount in cents (e.g. 4999 = $49.99). |
currency |
string | Three-letter ISO currency code (e.g. "usd"). |
status |
string | Current dispute status. See Dispute Status Lifecycle. |
reason |
string | Reason code provided by the card network. See Dispute Reason Codes. |
is_charge_refundable |
boolean | Whether the underlying charge can be refunded. |
metadata |
object | Arbitrary key-value metadata. |
created_at |
string | ISO 8601 timestamp when the dispute was created. |
test_mode |
boolean | true if this is a test-mode dispute. |
evidence_details |
object | |
| null | Evidence submission deadline and status. See below. | |
balance_transactions |
array | Balance transactions associated with this dispute (withdrawals and reinstatements). Only populated on single-dispute GET. |
payment_method_details |
object | |
| null | Card details for the disputed payment. Only populated on single-dispute GET. |
evidence_details Fields
| Field | Type | Description |
|---|---|---|
due_by |
string | |
| null | ISO 8601 deadline for submitting evidence. null if no deadline has been set yet. |
|
has_evidence |
boolean | true if any evidence has been saved (draft or submitted). |
past_due |
boolean | true if the submission deadline has passed. |
submission_count |
integer | Number of times evidence has been submitted. |
balance_transactions Fields (per item)
| Field | Type | Description |
|---|---|---|
balance_transaction_id |
string | Flex balance transaction ID (fbt_ prefix). |
amount |
integer | Amount in cents. Negative = debit from your balance. |
fee |
integer | Dispute fee in cents (charged by the card network). |
net |
integer | Net impact on your balance (amount + fee). |
status |
string | pending or available. |
type |
string | "adjustment" for dispute debit or reinstatement credit. |
created_at |
string | |
| null | ISO 8601 timestamp. |
payment_method_details.card Fields
| Field | Type | Description |
|---|---|---|
brand |
string | |
| null | Card brand: "visa", "mastercard", "amex", "discover", etc. |
|
network_reason_code |
string | |
| null | Card network reason code (e.g. "10.4" for Visa, "4853" for Mastercard). |
Dispute Status Lifecycle
needs_response ──submit evidence──► under_review ──► won
│ │
│ │
└─────────close (accept loss)──► lost ◄───────────┘
| Status | Description | Can Submit Evidence | Can Close |
|---|---|---|---|
needs_response |
Dispute is open and awaiting your response. | ✅ | ✅ |
warning_needs_response |
Inquiry (pre-chargeback) needing response. | ✅ | ✅ |
under_review |
Evidence submitted; issuer reviewing. | ❌ | ❌ |
warning_under_review |
Inquiry under review. | ❌ | ❌ |
won |
Dispute resolved in your favour. | ❌ | ❌ |
lost |
Dispute resolved against you. Funds forfeited. | ❌ | ❌ |
warning_closed |
Inquiry closed without becoming a chargeback. | ❌ | ❌ |
Attempting to submit evidence or close a dispute in a non-actionable status returns 422.
Dispute Reason Codes
| Value | Description |
|---|---|
fraudulent |
Customer claims they did not authorise the charge. Most common. |
product_not_received |
Customer claims they did not receive the product/service. |
product_unacceptable |
Customer claims the product/service was defective or not as described. |
credit_not_processed |
Customer claims a refund they were promised was never processed. |
duplicate |
Customer claims they were charged twice for the same transaction. |
subscription_canceled |
Customer claims they were charged after cancelling a subscription. |
unrecognized |
Customer does not recognise the charge. |
bank_cannot_process |
Bank returned the charge for technical reasons. |
debit_not_authorized |
Customer claims the debit was not authorised. |
customer_initiated |
Customer-initiated dispute. |
check_returned |
Check was returned. |
incorrect_account_details |
Account details were incorrect. |
insufficient_funds |
Insufficient funds. |
general |
General / uncategorised reason. |
Endpoints
List Disputes
GET /v1/disputes
Returns disputes for your account in reverse chronological order (newest first).Query Parameters
| Parameter | Type | Description |
|---|---|---|
limit |
integer | Number of disputes to return. Default 20, max 100. |
starting_after |
string | Cursor for forward pagination. Pass the last dispute_id from the previous page. |
ending_before |
string | Cursor for backward pagination. Pass the first dispute_id from the current page. |
charge |
string | Filter by Flex charge ID (fch_...). |
payment_intent |
string | Filter by Flex payment intent ID (fpi_...). |
Note:
balance_transactionsandpayment_method_detailsare not populated in list responses. UseGET /v1/disputes/:idto get the full dispute.
Example — list all disputes
GET /v1/disputes?limit=20
Authorization: Bearer fsk_...
Response
{
"disputes": [
{
"dispute_id": "fdsp_01jx3q8kt1b9a7n2c4d5e6f7g8",
"amount": 4999,
"currency": "usd",
"status": "needs_response",
"reason": "fraudulent",
"created_at": "2026-02-10T14:30:00Z",
"evidence_details": {
"due_by": "2026-03-01T23:59:59Z",
"has_evidence": false,
"past_due": false,
"submission_count": 0
},
"balance_transactions": null,
"payment_method_details": null,
...
}
]
}
Example — filter by payment intent
GET /v1/disputes?payment_intent=fpi_01jx3q7ab1c8d2e3f4g5h6j7k8
Authorization: Bearer fsk_...
Example — paginate forward
GET /v1/disputes?limit=10&starting_after=fdsp_01jx3q8kt1b9a7n2c4d5e6f7g8
Authorization: Bearer fsk_...
Get Dispute
GET /v1/disputes/:dispute_id
Retrieves a single dispute by its Flex ID. This is the only endpoint that populates balance_transactions and payment_method_details.Path Parameters
| Parameter | Description |
|---|---|
dispute_id |
Flex dispute ID (fdsp_...). |
Example
GET /v1/disputes/fdsp_01jx3q8kt1b9a7n2c4d5e6f7g8
Authorization: Bearer fsk_...
Response
{
"dispute": {
"dispute_id": "fdsp_01jx3q8kt1b9a7n2c4d5e6f7g8",
"payment_intent_id": "fpi_01jx3q7ab1c8d2e3f4g5h6j7k8",
"charge_id": "fch_01jx3q6ab1c7d2e3f4g5h6j7k8",
"amount": 4999,
"currency": "usd",
"status": "needs_response",
"reason": "fraudulent",
"is_charge_refundable": false,
"metadata": {},
"created_at": "2026-02-10T14:30:00Z",
"test_mode": false,
"evidence_details": {
"due_by": "2026-03-01T23:59:59Z",
"has_evidence": false,
"past_due": false,
"submission_count": 0
},
"balance_transactions": [
{
"balance_transaction_id": "fbt_01jx3qabc123",
"amount": -4999,
"fee": -1500,
"net": -6499,
"status": "available",
"type": "adjustment",
"created_at": "2026-02-10T15:00:00Z"
}
],
"payment_method_details": {
"type": "card",
"card": {
"brand": "visa",
"network_reason_code": "10.4"
}
}
}
}
Errors
| Status | When |
|---|---|
404 |
Dispute not found or belongs to a different partner. |
Update Evidence
POST /v1/disputes/:dispute_id
Saves evidence for a dispute. Set submit: true to submit evidence immediately. Set submit: false (or omit it) to save a draft without submitting.The dispute must be in needs_response or warning_needs_response status.Path Parameters
| Parameter | Description |
|---|---|
dispute_id |
Flex dispute ID (fdsp_...). |
Request Body
{
"dispute": {
"evidence": { ... },
"submit": true
}
}
Evidence Fields
All fields are optional. Include only what is relevant to your dispute reason.Text fields
| Field | Description |
|---|---|
customer_name |
Full name of the customer as it appears in your records. |
customer_email_address |
Email address of the customer. |
customer_purchase_ip |
IP address the customer used when making the purchase. |
billing_address |
Billing address as entered during checkout. |
product_description |
Description of the product or service. Be specific. |
service_date |
Date the service was provided (YYYY-MM-DD). |
shipping_address |
Address where the product was shipped. |
shipping_carrier |
Carrier name (e.g. "UPS", "FedEx"). |
shipping_date |
Date the product was shipped (YYYY-MM-DD). |
shipping_tracking_number |
Tracking number for the shipment. |
duplicate_charge_id |
The Flex charge ID of the original, legitimate charge (for duplicate disputes). |
duplicate_charge_explanation |
Explanation of why the charges are different (for duplicate disputes). |
cancellation_policy_disclosure |
Explanation of how the cancellation policy was disclosed to the customer. |
cancellation_rebuttal |
Rebuttal to the customer’s cancellation claim. |
refund_policy_disclosure |
Explanation of how the refund policy was disclosed. |
refund_refusal_explanation |
Explanation of why a refund was not issued. |
access_activity_log |
Log of customer access to the service (relevant for product_not_received). |
uncategorized_text |
Any additional evidence or context. |
File fields — use a Flex File ID (ffile_... from POST /v1/files)Files must be uploaded with purpose dispute_evidence before use. See Uploading Evidence Files.
| Field | Description |
|---|---|
receipt |
Receipt or order confirmation. |
customer_communication |
Email/chat correspondence with the customer. |
customer_signature |
Signed delivery confirmation or service agreement. |
cancellation_policy |
Copy of your cancellation policy. |
refund_policy |
Copy of your refund policy. |
duplicate_charge_documentation |
Documentation showing the original charge was legitimate. |
service_documentation |
Proof the service was delivered. |
shipping_documentation |
Shipping confirmation or carrier proof of delivery. |
uncategorized_file |
Any additional supporting document. |
Example — save a draft (no submission)
POST /v1/disputes/fdsp_01jx3q8kt1b9a7n2c4d5e6f7g8
Authorization: Bearer fsk_...
Content-Type: application/json
{
"dispute": {
"evidence": {
"customer_name": "Jane Doe",
"customer_email_address": "jane@example.com",
"product_description": "HSA-eligible blood pressure monitor, shipped 2026-01-15",
"shipping_tracking_number": "1Z999AA10123456784",
"shipping_carrier": "UPS",
"shipping_date": "2026-01-15",
"receipt": "ffile_01jx3qabc000000000000000001"
},
"submit": false
}
}
Example — submit evidence
POST /v1/disputes/fdsp_01jx3q8kt1b9a7n2c4d5e6f7g8
Authorization: Bearer fsk_...
Content-Type: application/json
{
"dispute": {
"evidence": {
"customer_name": "Jane Doe",
"customer_email_address": "jane@example.com",
"product_description": "HSA-eligible blood pressure monitor, shipped 2026-01-15",
"shipping_tracking_number": "1Z999AA10123456784",
"shipping_carrier": "UPS",
"shipping_date": "2026-01-15",
"receipt": "ffile_01jx3qabc000000000000000001",
"shipping_documentation": "ffile_01jx3qabc000000000000000002"
},
"submit": true
}
}
ResponseReturns the updated Dispute object with refreshed evidence_details.submission_count.
{
"dispute": {
"dispute_id": "fdsp_01jx3q8kt1b9a7n2c4d5e6f7g8",
"status": "under_review",
"evidence_details": {
"due_by": "2026-03-01T23:59:59Z",
"has_evidence": true,
"past_due": false,
"submission_count": 1
},
...
}
}
Errors
| Status | When |
|---|---|
404 |
Dispute not found or belongs to a different partner. |
422 |
Dispute is not in needs_response / warning_needs_response status. |
404 |
A referenced file ID (ffile_...) does not exist. |
422 |
A referenced file was not uploaded with dispute_evidence purpose. |
Close Dispute
POST /v1/disputes/:dispute_id/close
Accepts the dispute and concedes the funds. Use this when you do not intend to contest the dispute. This action is irreversible.The dispute must be in needs_response or warning_needs_response status.Path Parameters
Request BodyNo body required.Example
POST /v1/disputes/fdsp_01jx3q8kt1b9a7n2c4d5e6f7g8/close
Authorization: Bearer fsk_...
ResponseReturns the updated Dispute object. status will be lost.
{
"dispute": {
"dispute_id": "fdsp_01jx3q8kt1b9a7n2c4d5e6f7g8",
"status": "lost",
...
}
}
Errors
| Status | When |
|---|---|
404 |
Dispute not found or belongs to a different partner. |
422 |
Dispute is not in needs_response / warning_needs_response status. |
Uploading Evidence Files
Before referencing a file in an evidence submission, upload it using the Files API with purpose dispute_evidence.
POST /v1/files
Authorization: Bearer fsk_...
Content-Type: multipart/form-data
file=@receipt.pdf
purpose=dispute_evidence
Response
{
"file": {
"file_id": "ffile_01jx3qabc000000000000000001",
"filename": "receipt.pdf",
"purpose": "dispute_evidence",
"size": 48320,
"created_at": "2026-02-10T14:00:00Z"
}
}
Use the returned file_id (ffile_...) in evidence file fields. Attempting to use a file with a different purpose (e.g. identity_document) will return a 422 with a clear error message indicating which field is invalid and what purpose was found vs expected.
Webhook Events
Configure a webhook endpoint in your Flex dashboard to receive dispute lifecycle events.
charge.dispute.created
Fired when a dispute is opened against one of your charges.
{
"event_id": "fevt_01jx3q8kt1b9a7n2c4d5e6f7g8",
"event_type": "charge.dispute.created",
"event_dt": 1770000000,
"object": {
"dispute_id": "fdsp_01jx3q8kt1b9a7n2c4d5e6f7g8",
"payment_intent_id": "fpi_01jx3q7ab1c8d2e3f4g5h6j7k8",
"charge_id": "fch_01jx3q6ab1c7d2e3f4g5h6j7k8",
"amount": 4999,
"currency": "usd",
"status": "needs_response",
"reason": "fraudulent",
"evidence_details": {
"due_by": "2026-03-01T23:59:59Z",
"has_evidence": false,
"past_due": false,
"submission_count": 0
}
}
}
Recommended action: Alert your team and begin gathering evidence.
charge.dispute.updated
Fired when a dispute’s status, evidence, or deadline changes (including when you submit evidence via the API).Recommended action: Refresh your local dispute record.
charge.dispute.closed
Fired when a dispute is resolved — either won, lost, or warning_closed.Recommended action: Update your records; if lost, the funds have been debited.
charge.dispute.funds_withdrawn
Fired when funds are withdrawn from your balance to cover the disputed amount. A balance transaction is added to the dispute.
charge.dispute.funds_reinstated
Fired when a previously disputed amount is returned to your balance (dispute won).All webhook payloads include a flex-signature header for verification. See the Webhooks documentation for signature verification details.
Error Reference
All errors follow a standard format:
{
"code": "validation_error",
"detail": "Cannot submit evidence for dispute in 'under_review' status"
}
Or for field-level validation errors:
{
"detail": [
{
"loc": ["evidence", "receipt"],
"msg": "File ffile_01xxx in receipt has purpose identity_document, expected dispute_evidence",
"type": "value_error"
}
]
}
| HTTP Status | code |
When |
|---|---|---|
401 |
unauthorized |
Missing or invalid API key. |
404 |
not_found |
Dispute or file not found (or belongs to another partner). |
422 |
validation_error |
Invalid status transition or file validation failure. |
500 |
server_error |
Internal error — contact support with the request ID. |
End-to-End Testing Guide
Prerequisites
- Flex API key (test mode)
- Test card numbers (see below)
- A registered webhook endpoint to receive events
Step 1 — Trigger a dispute
Create a checkout session and complete payment using an auto-dispute test card:
| Card Number | Dispute Reason | Notes |
|---|---|---|
4000 0000 0000 0259 |
fraudulent |
Automatically disputed after charge |
4000 0000 0000 2685 |
product_not_received |
Automatically disputed after charge |
Use 4111 1111 1111 1111 as a valid non-disputing card for comparison.After completing payment:
- The charge is automatically disputed (test mode only)
- Flex receives the
charge.dispute.createdevent - Your webhook endpoint receives the
charge.dispute.createdevent
Step 2 — Inspect the dispute
GET /v1/disputes
Authorization: Bearer fsk_...
Find the new dispute. Then fetch the full object:
GET /v1/disputes/fdsp_...
Authorization: Bearer fsk_...
Verify:
status: "needs_response"evidence_details.due_byis a future timestampevidence_details.submission_count: 0balance_transactionscontains one debit entry
Step 3 — Submit evidence
POST /v1/disputes/fdsp_...
Authorization: Bearer fsk_...
Content-Type: application/json
{
"dispute": {
"evidence": {
"customer_name": "Test Customer",
"product_description": "HSA-eligible item, delivered",
"uncategorized_text": "This charge is legitimate."
},
"submit": true
}
}
Verify:
- Response
evidence_details.submission_count: 1 - Response
statuschanges to"under_review"
Step 4 — Close a second dispute
Create another charge with the auto-dispute card. Then:
POST /v1/disputes/fdsp_.../close
Authorization: Bearer fsk_...
Verify:
- Response
status: "lost"
Step 5 — Verify multi-tenant isolation
Using a different partner’s API key, attempt to access the dispute IDs from Steps 2–4. All should return 404.
Step 6 — Test error cases
**Invalid status transition:**Try POST /v1/disputes/fdsp_.../close on a dispute that is already lost or under_review. Expected: 422 with message "Cannot close dispute in '...' status".
**File with wrong purpose:**Upload a file with purpose=identity_document, then reference it in an evidence field. Expected: 422 with message indicating which field has the wrong file purpose.
Non-existent dispute:
GET /v1/disputes/fdsp_doesnotexist
Expected: 404.