Documentation Index
Fetch the complete documentation index at: https://docs.withflex.com/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
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.created event.
- Creates a
Dispute record linked to your payment_intent_id and charge_id.
- Forwards a
charge.dispute.created event 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
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_transactions and payment_method_details are not populated in list responses. Use GET /v1/disputes/:id to 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
}
}
Response
Returns 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
| Parameter | Description |
|---|
dispute_id | Flex dispute ID (fdsp_...). |
Request Body
No body required.
Example
POST /v1/disputes/fdsp_01jx3q8kt1b9a7n2c4d5e6f7g8/close
Authorization: Bearer fsk_...
Response
Returns 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.created event
- Your webhook endpoint receives the
charge.dispute.created event
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_by is a future timestamp
evidence_details.submission_count: 0
balance_transactions contains 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
status changes 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:
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.