Every payout is a small state machine. Knowing exactly which states are legal — and how the mock advances between them — makes it easy to write a polling loop, react to a webhook, or assert against the response in a test.Documentation Index
Fetch the complete documentation index at: https://docs.clevis.dev/llms.txt
Use this file to discover all available pages before exploring further.
States
| Status | Terminal? | Meaning |
|---|---|---|
PENDING | no | Accepted, queued, not yet sent to “bank rails” |
PROCESSING | no | ”In flight” to the beneficiary bank |
PAID | yes | Funds “delivered” |
REJECTED | yes | Not paid — see status_detail / status_code |
CANCELLED | yes | Cancelled by caller before completion |
PAID, REJECTED, and CANCELLED are terminal — once a payout enters one of
these, its status never changes. A cancel call on a terminal payout returns
409 PAYOUT_NOT_CANCELLABLE.
The full diagram
Transitions in detail
Create → PENDING
POST /v1/payouts returns 201 with status: "PENDING". The mock
schedules the rest of the state machine on a timer (default 2 seconds
per step, overridable via processing.step_seconds).Some magic triggers skip this step and return
201 with a terminal status already applied — typically MOCK_REJECT_INTAKE_,
amount-ending-in-.99, and document_id == "00000000000".PENDING → PROCESSING
The payout has “left the queue” and is “in flight”. Default route always
goes through this state, even when the final outcome is REJECTED or
CANCELLED — observers see the full lifecycle.
PROCESSING → REJECTED
Bank-side reject.
status_code is one of:"301"— beneficiary bank rejected the transfer"302"— insufficient funds in payout account
status_detail ends with "(mock trigger)." when the reject was forced
by a magic trigger.When transitions actually fire
The mock advances payouts two ways, both routed through the same state machine code:- Per-payout timer scheduled at create time. Default cadence: every
2 seconds (server-configurable via
PAYOUTS_MOCK_STEP_SECONDS). Override per request withprocessing.step_seconds(clamped to[0, 60]). - Lazy advance on read.
GET /v1/payouts/{id}evaluates “where would this be by wall clock now?” and applies pending transitions before responding. This is why tests don’t have to depend on a real clock.
processing.step_seconds: 0 collapses the timer to “apply all transitions
synchronously before returning the create response.” The 201 already
reflects the terminal state. This is the recommended setting for tests.
Reading the result
The full lifecycle is preserved on the resource:estimated_settlement_at is set at create time to created_at + N * step_seconds,
where N is the length of the planned route. It’s a hint, not a guarantee —
read status and status_history for the truth.
Cancelling
200 OKwith the cancelledPayoutif the payout wasPENDINGorPROCESSING409 PAYOUT_NOT_CANCELLABLEif the payout was already terminal404 PAYOUT_NOT_FOUNDif the id doesn’t exist for your API key
The mock allows cancelling from
PROCESSING, which a real provider may not.
This is intentional — it makes the cancellation path easy to exercise in
demos.Idle “stuck” state
TheMOCK_STUCK_ external_id prefix advances a payout to PROCESSING and
then never settles. Use it to exercise your polling-loop timeout / “stale
payout” UX without actually waiting for a real provider to misbehave. See
Magic triggers.