> ## 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 API Guide

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](#overview)
* [Authentication](#authentication)
* [The Dispute Object](#the-dispute-object)
* [Dispute Status Lifecycle](#dispute-status-lifecycle)
* [Dispute Reason Codes](#dispute-reason-codes)
* [Endpoints](#endpoints)
  * [List Disputes](#list-disputes)
  * [Get Dispute](#get-dispute)
  * [Update Evidence](#update-evidence)
  * [Close Dispute](#close-dispute)
* [Uploading Evidence Files](#uploading-evidence-files)
* [Webhook Events](#webhook-events)
* [Error Reference](#error-reference)
* [End-to-End Testing Guide](#end-to-end-testing-guide)

***

## Overview

```text theme={null}
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:

1. Receives the `charge.dispute.created` event.
2. Creates a `Dispute` record linked to your `payment_intent_id` and `charge_id`.
3. 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).

```text theme={null}
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

```json theme={null}
{
  "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](#dispute-status-lifecycle).                                          |
| `reason`                 | string         | Reason code provided by the card network. See [Dispute Reason Codes](#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

```text theme={null}
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

```text theme={null}
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_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**

```http theme={null}
GET /v1/disputes?limit=20
Authorization: Bearer fsk_...
```

**Response**

```json theme={null}
{
  "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**

```http theme={null}
GET /v1/disputes?payment_intent=fpi_01jx3q7ab1c8d2e3f4g5h6j7k8
Authorization: Bearer fsk_...
```

**Example — paginate forward**

```http theme={null}
GET /v1/disputes?limit=10&starting_after=fdsp_01jx3q8kt1b9a7n2c4d5e6f7g8
Authorization: Bearer fsk_...
```

***

### Get Dispute

```text theme={null}
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**

```http theme={null}
GET /v1/disputes/fdsp_01jx3q8kt1b9a7n2c4d5e6f7g8
Authorization: Bearer fsk_...
```

**Response**

```json theme={null}
{
  "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

```text theme={null}
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**

```json theme={null}
{
  "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](#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)**

```http theme={null}
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**

```http theme={null}
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`.

```json theme={null}
{
  "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

```text theme={null}
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**

```http theme={null}
POST /v1/disputes/fdsp_01jx3q8kt1b9a7n2c4d5e6f7g8/close
Authorization: Bearer fsk_...
```

**Response**

Returns the updated `Dispute` object. `status` will be `lost`.

```json theme={null}
{
  "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`.

```http theme={null}
POST /v1/files
Authorization: Bearer fsk_...
Content-Type: multipart/form-data

file=@receipt.pdf
purpose=dispute_evidence
```

**Response**

```json theme={null}
{
  "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.

```json theme={null}
{
  "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:

```json theme={null}
{
  "code": "validation_error",
  "detail": "Cannot submit evidence for dispute in 'under_review' status"
}
```

Or for field-level validation errors:

```json theme={null}
{
  "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

```http theme={null}
GET /v1/disputes
Authorization: Bearer fsk_...
```

Find the new dispute. Then fetch the full object:

```http theme={null}
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

```http theme={null}
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:

```http theme={null}
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:**

```http theme={null}
GET /v1/disputes/fdsp_doesnotexist
```

Expected: `404`.
