Auto-expiry and refunds
No path leaves funds held. The sweep is what guarantees it.
Every warrant has a deadline, and a warrant nobody resolves goes held → expired → refunded on its own.
POST /internal/jobs/expire
→ { "expired_count": 3, "expired": ["wrt_…", "wrt_…", "wrt_…"] }The deadline is required
deadline is mandatory on every warrant and must be in the future — a past one
is refused at open with 400 deadline_in_past.
There is no "no deadline" option, deliberately. A hold with no expiry is a hold that can outlive the team that created it.
What the sweep does
POST /internal/jobs/expire finds overdue warrants and moves them through
expired to refunded. The money is back in available before the sweep
returns.
This is the invariant the product rests on: no path leaves funds held forever. Not a missed notification, not an escalation nobody worked, not an agent that stopped calling back.
The sweep is a worker route, not a public one
/internal/* sits behind X-Warrant-Worker and is scheduled, not called by
integrators. Absent or wrong secret is 401; a deployment with no secret
configured is 403, because no credential could open it.
Refunding early
You do not have to wait for the deadline.
curl -sS https://api.gowarrant.xyz/v1/warrants/$WARRANT_ID/refund \
-b "warrant_session=$WARRANT_SESSION" \
-H "Idempotency-Key: $(uuidgen)"Needs a person, like every settlement. An API key gets 403
user_session_required.
A policy can also refund automatically — alwaysRefund on a reason code like
source_not_found returns the hold without anyone looking, because a source
that never existed is not a judgement call.
expired and refunded are both recorded
The trace keeps both transitions, so the record distinguishes "a person decided not to pay" from "the clock ran out". Those are different facts about a supplier relationship and collapsing them would lose the more interesting one.
The escalation's resolution reads expired in that case — and it is the one
resolution a person cannot choose. It is what the sweep records, not an option
on the screen.
Choosing a deadline
Long enough that a person can realistically decide, short enough that the money
is not committed pointlessly. If escalations routinely expire, the deadline is
too short or the queue is not being worked — and the trace will tell you which,
because opened_at and the resolution are both on it.
Amounts
A refund returns the full amount to available. A partial release returns only the unreleased share — see partial release.
"8400000" 8.40 USDC
8400000 rejected — a JSON number
"8.40" rejected — not the smallest unit