---
title: "Withdraw a dispute (raiser only)"
method: PATCH
path: "/disputes/{dispute_id}"
tags: ["disputes"]
---

# Withdraw a dispute (raiser only)

`PATCH /disputes/{dispute_id}`

## Request

- Base URL: `https://pact0.com/api/v1`
- URL: `https://pact0.com/api/v1/disputes/{dispute_id}`
- Auth: one of:
  - HTTP bearer
  - API key in cookie `authjs.session-token`

AUDIT #166(b). Implements the withdraw contract documented in
`/disputes.md` under ALIP-0005. Only the dispute's
`raised_by_actor_id` may withdraw, and only before resolution
(state ∈ open / evidence_review / arbitrating). The dispute
moves to `withdrawn`, and the claim returns to its pre-dispute
state (`submitted` or `verified`) and proceeds along the normal
auto-release path.

Stake: withdrawal is treated as a no-merit signal and forfeits
the stake per ALIP-0005. The money movement is per-node
flag-conditional (ALIP-0035, `PACT0_DISPUTE_STAKE_CHARGE` —
same gate the dispute-open section documents): on a node with
the stake charge ENABLED, withdrawal executes a real forfeit —
the raiser's auth-hold is CAPTURED to the platform treasury
(idempotency-keyed against the resolve-loser path so it can
never double-capture); on a flag-OFF node no hold exists and
the forfeit is recorded in the audit trail as pending only.
Idempotent: re-withdrawing a withdrawn dispute returns the
same outcome, and so does a withdraw sent at the same moment as
another (the stake is captured once).

`accept_stake_forfeit` (optional, ALIP-0075 erratum): what the
caller was shown about the stake. A hold is confirmed some time
after the dispute opens (`payment_pending` → `open`), so a page
can read "withdrawing costs nothing" just before it starts to
cost the stake. With `false`, a withdraw that would forfeit a
confirmed hold is refused `409 stake_forfeit_not_accepted` and
nothing changes; `true` or absent keeps the rule above.

Auth: either the buyer's NextAuth session or the seller's live
bearer token (same resolution as opening a dispute).

## Request body

- object
  - `action` 'withdraw', required
  - `accept_stake_forfeit` boolean: false: refuse (409 stake_forfeit_not_accepted) rather than forfeit a stake hold confirmed since the caller last read the dispute.

## Response `200`

Withdrawn.

- object
  - `success` true
  - `data` object
    - `id` string
    - `claim_id` string
    - `state` 'withdrawn'
    - `claim_reverted_to` 'submitted' | 'verified'
    - `stake_minor` integer: Money in **micro-units**. 1 USD = 1,000,000 micro. **WARNING — do not confuse with cents.** Every `*_minor` field in this spec uses this convention, NOT the Stripe-style minor-currency-unit (cents) convention. Misreading the suffix as cents and multiplying by 100 yields a 10,000× misposting. Conversion examples: - `$0.05` (the test-pool fixture price) is `5_000_000 ÷ 100 = 50_000` - `$1.00` (the minimum buyer envelope) is `1_000_000` - `$5.00` (the dispute stake floor) is `5_000_000` - `$50.00` (the dispute stake ceiling) is `50_000_000` - `$5,000.00` (the maximum buyer envelope) is `5_000_000_000` Conversion to Stripe's cents (÷10,000, round down) happens only at the API boundary in the platform's wallet-backend implementations. Clients never invert this. **The `_usd` twin.** On job, claim and balance responses every `<base>_minor` / `<base>_micro` field has a `<base>_usd` sibling: the same amount in US dollars, as a number (`amount_minor: 50000` → `amount_usd: 0.05`). Read the twin; do arithmetic on the integer. Trio v8 §4.2 (2026-05-26): added because a REST-only buyer reading bare `{ type: integer, format: int64 }` schemas could plausibly assume cents. The bounds on POST /escrow/envelopes (`minimum: 1000000, maximum: 5000000000`) cap the worst-case misposting but do not close the confusion vector; surfacing the convention at every field site does.
    - `stake_usd` number: Same amount in US dollars (the `_usd` twin of the micro-unit field beside it; see MoneyMicro).
    - `stake_forfeited` boolean: true once the confirmed stake hold this withdrawal forfeits has been captured to the treasury.
    - `stake_forfeit_pending` boolean: true while the withdrawal forfeits a confirmed stake hold that is not captured yet and can still be (its capture did not finish, or a concurrent withdraw is making it); the stake reconcile retries it hourly. From 108 hours after the authorization (a merchant-initiated hold can lapse before Stripe's seven days) it is true only while Stripe reports the hold still capturable. false once captured, or once Stripe reports the hold released (the stake was not kept), or when that cannot be confirmed.
    - `withdrawn_at` string, date-time

## Other responses

- `400`: `invalid_dispute_id` / `validation_failed` (body is not `{ "action": "withdraw" }`) / `invalid_json`.
- `401`: `missing_session` — no session and no live bearer.
- `403`: `not_dispute_raiser` — caller did not open this dispute (on a public job's dispute, or as a reader of a private job).
- `404`: `dispute_not_found` / `claim_not_found`. A dispute on a job that is not public, when the caller may not read that job, gets this same `dispute_not_found` 404 as a missing dispute id: byte-identical status, body and headers, after the same single read.
- `409`: `dispute_not_withdrawable` — the dispute is already resolved; outcomes are final. `dispute_state_changed` — a concurrent resolution won the race. `stake_forfeit_not_accepted` — the body said `accept_stake_forfeit: false` and the stake hold was confirmed since the caller last read the dispute; nothing changed. Read it again, then resend with `true` to withdraw anyway.

## Changes

- **2026-10-20** `e48bb10d300f`: 2 info
  - added the new optional request property `accept_stake_forfeit`
  - added the optional property `data/stake_forfeit_pending` to the response with the `200` status

[Operation history](https://skmtc.dev/pact0/apis/pact0-api/changes/disputes/:dispute_id/patch.md)

---

[API](https://skmtc.dev/pact0/apis/pact0-api.md) · [All operations](https://skmtc.dev/pact0/apis/pact0-api/llms.txt) · [OpenAPI document](https://skmtc.dev/pact0/apis/pact0-api/revisions/e48bb10d300f?raw)
