Pay an API with conditional release
Hold, check, settle — the whole loop in two calls.
Two calls: open holds the money against a spec, verify checks the deliverable and lets the policy settle it.
export WARRANT_API_KEY=wr_test_8f0c1b4e2b1a4d599d0e3a2f7c1b9e44Everything here runs against sandbox with a wr_test_ key.
1. Open
curl -sS https://api.gowarrant.xyz/v1/warrants \
-H "Authorization: Bearer $WARRANT_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"amount": "8400000",
"asset": "USDC",
"payee": "api.exampledata.io",
"deadline": "2026-08-16T12:00:00.000Z",
"spec": {
"clauses": [
{ "kind": "http.status", "equals": 200 },
{ "kind": "data.freshness", "maxAgeSeconds": 60 }
]
}
}'"8400000" is 8.40 USDC. It is a string of the smallest unit — send
8400000 as a JSON number, or "8.40", and you get 400 validation_failed.
This is the mistake most first integrations make.
The response comes back "state": "held". The money is committed and has not
moved.
2. Verify
curl -sS https://api.gowarrant.xyz/v1/warrants/$WARRANT_ID/verify \
-H "Authorization: Bearer $WARRANT_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{ "source_url": "https://api.exampledata.io/invoices/8812" }'The engine fetches the source, evaluates every clause, and hands the score to your policy. The response is the warrant in whatever state the policy chose:
state | Meaning |
|---|---|
released | Clean pass. The payee is paid. |
partially_released | A share paid, the rest returned. |
escalated | A person has to decide. Funds still held. |
refunded | Nothing paid; the hold returned. |
Verify is not a request to pay
It is a request to decide. escalated is a legitimate, successful outcome —
it means the policy did its job and the result needs a person. Treat it as a
normal branch, not an error.
In TypeScript
import { Warrant, usdc } from "@gowarrant/sdk";
const warrant = new Warrant();
const w = await warrant.open({
amount: usdc("8.40"),
payee: "api.exampledata.io",
spec: {
clauses: [
{ kind: "http.status", equals: 200 },
{ kind: "data.freshness", maxAgeSeconds: 60 },
],
},
deadline: new Date(Date.now() + 3_600_000),
});
const after = await warrant.verify(w.id, {
source_url: "https://api.exampledata.io/invoices/8812",
});
switch (after.state) {
case "released": break; // paid
case "partially_released": break; // paid in part
case "escalated": break; // waiting on a person
case "refunded": break; // not paid
}usdc("8.40") returns the Amount type. Passing 8.40 does not compile, which
is the point of the helper.
Try the spec first
Before spending anything, run the clauses against a sample response:
curl -sS https://api.gowarrant.xyz/bench/run \
-H "Content-Type: application/json" \
-d '{
"clauses": [{ "kind": "data.freshness", "maxAgeSeconds": 60 }],
"deliverable": {
"reached": true,
"status": 200,
"generated_at": "2026-08-15T11:00:00.000Z"
},
"now": "2026-08-15T12:00:00.000Z"
}'No key, nothing held. score here is a fraction 0–1; a stored score elsewhere
is basis points 0–10000.
What you get afterwards
Every transition leaves a trace, and a settled warrant has a
public receipt at /t/:id you can send the payee. They can read why they were
or were not paid without an account here.