API documentation
Base URL: https://recourse.so · All requests and responses are JSON. Authenticate with an API key from your dashboard: Authorization: Bearer rk_live_…
Authentication
Create keys in the dashboard. Keys are shown once at creation and stored hashed. Revoke any key instantly from the dashboard. Requests without a valid key are rejected with 401 when key enforcement is on (RECOURSE_REQUIRE_KEYS=1; development mode is open).
POST /disputes
Submit a dispute bundle for adjudication. Synchronous: the ruling returns on the response (SLA: minutes on clean bundles).
Request body
| Field | Type | Required | Meaning |
|---|---|---|---|
rulepack | string | ✔ | Doctrine to adjudicate under. Currently marketplace-contractor-v1 (see GET /rulepacks). |
currency | string | ✔ | ISO 4217, e.g. USD. |
amount_minor | integer | ✔ | Disputed amount in minor units (cents). The ruling's money instruction always sums exactly to this. |
terms | object | ✔ | { format: "text" | "lcp", content: string } — the agreed terms. |
claim | object | ✔ | { by: "claimant" | "respondent", claimed_breach: string, remedy_sought?: string }. |
log | array | ✔ | Chronological events: { at: ISO8601, actor: "claimant"|"respondent"|"platform"|"system", kind: "message"|"delivery"|"payment"|"revision_request"|"status_change"|"other", content: string }. |
deliverables | array | ✔ | { name, description?, content?, url? } — inline text for textual work; URLs for binary artifacts (URL-only evidence may trigger escalation). |
settlement | object | Which rail holds the disputed funds — { rail: "escrow_webhook" } (default), stripe_connect, tempo, or x402. See Settlement rails. | |
external_ref | string | Your reference for the transaction; echoed on webhooks. | |
metadata | object | Free-form strings; never used in adjudication. |
Example
curl https://recourse.so/disputes \
-H "authorization: Bearer rk_live_..." \
-H "content-type: application/json" \
-d '{
"rulepack": "marketplace-contractor-v1",
"currency": "USD",
"amount_minor": 60000,
"terms": { "format": "text", "content": "Logo package, one revision round, 10 days, $600." },
"claim": { "by": "claimant", "claimed_breach": "Work is not professional quality." },
"log": [
{ "at": "2026-06-12T16:30:00Z", "actor": "respondent", "kind": "delivery", "content": "Drafts delivered per spec." },
{ "at": "2026-06-13T09:00:00Z", "actor": "claimant", "kind": "message", "content": "Not what I imagined. Refund me." }
],
"deliverables": [{ "name": "logo-package", "description": "SVG/AI/PNG per spec" }]
}'
Response 201
{
"id": "dsp_4b81e2...",
"status": "adjudicated", // or "escalated"
"ruling": {
"fault_allocation": { "claimant_pct": 75, "respondent_pct": 25 },
"money_instruction": { "type": "split", "to_respondent_minor": 45000, "to_claimant_minor": 15000, "currency": "USD" },
"summary": "…plain-language ruling…",
"reasoning": [ { "rule_id": "MC-2", "finding": "…", "application": "…" } ],
"confidence": 0.84,
"escalate": false,
"tier": 1,
"engine": "recourse-t1/claude-opus-5"
}
}
Errors: 400 invalid bundle (message names the field) · 401 bad key · 502 adjudication failed (recorded; retry or escalate).
GET /disputes/:id
Retrieve a stored dispute and its ruling. Escalated disputes return the Tier 1 draft until the Recourse bench issues the final ruling, which replaces it.
GET /disputes
List disputes (id, status, summary, escalated flag), newest first.
GET /rulepacks
The published doctrine, machine-readable: rules, silence-in-terms defaults, and escalation triggers for each vertical rule-pack. The human-readable form is at /doctrine.
GET /health
Liveness + engine mode (claude or mock).
Escalation — who judges what
When a bundle trips an escalation trigger (published per rule-pack: low engine confidence, amount above ceiling, contradictory or uninspectable evidence, fraud allegations), the response comes back status: "escalated" with the engine's draft ruling attached, and the case enters the Recourse Review Bench — our internal queue, staffed by Recourse.
- Tier 2 (enhanced review): a Recourse reviewer examines the bundle with a deeper engine pass and confirms or overrides the draft.
- Tier 3 (human panel): independent adjudicators contracted by Recourse — never your staff, never ours with a conflict. Panelists are conflict-screened per case and excluded where any relationship with a party or the platform exists.
Your team is never asked to judge. Your only integration point is the escrow webhook: the draft instruction arrives flagged escalated: true (your escrow partner holds funds); the bench's final ruling follows as a superseding instruction with escalated: false.
Settlement rails — enforcement
Judgment is rail-agnostic; enforcement is an adapter. The bundle's settlement field names the rail holding the disputed funds, and the ruling's webhook carries a rail-specific rail_instruction ready to execute. Recourse never holds funds, keys, or balances on any rail.
| Rail | Funds held by | Instruction shape | Status |
|---|---|---|---|
escrow_webhook (default) | Fiat escrow partner (Trustap/Tazapay/Shieldpay class) | Direct split against held funds | Live vs reference receiver |
stripe_connect | Platform's Stripe account (delayed transfer / separate charge) | transfer + refund steps your Stripe integration executes verbatim | Spec-stable |
tempo | Holding contract / wallet on Tempo (Stripe's payments L1) | Stablecoin transfer_intents with the dispute id in the memo | Spec-stable |
x402 | x402 facilitator holding the agent-payment authorization | settle.splits the facilitator executes — the dispute layer the x402 docs tell developers to build | Spec-stable |
"Spec-stable" = payload formats are frozen and exercised end-to-end against the reference receiver; live execution activates with partner onboarding. Name Recourse as resolver in your escrow contract, Connect flow, or x402 payment terms once — every transaction inherits the venue.
POST /witness/inbound
The email-witness ingest (early access — see the case study). Your email provider POSTs each message routed to witness@ / dispute@ here; the venue tracks the thread lifecycle (requested → witnessed → disputed), assembles the dispute bundle from the correspondence, and queues the ruling reply. Protect with the x-witness-secret header (WITNESS_INBOUND_SECRET).
{ "message_id": "<abc@mail.example>", "in_reply_to": "<...>", "references": ["<...>"],
"from": "Dana <dana@client.example>", "to": ["leo@studio.example", "dispute@recourse.so"],
"cc": ["witness@recourse.so"], "subject": "Re: Brand refresh — scope & quote",
"date": "2026-07-30T09:00:00Z", "text": "Filing a dispute...\n\nAmount: 700 EUR",
"attachments": [{ "filename": "logo-package.zip" }] }
A dispute email with no Amount: line yields a declaratory ruling. Outbound replies queue in the venue outbox and deliver via WITNESS_OUTBOUND_WEBHOOK (ESP-agnostic, same pattern as escrow enforcement).
Webhooks — wire format
One event type: ruling.money_instruction, HMAC-SHA256 signed, at-least-once delivery. Full spec with per-rail payloads and verification reference: integrations/escrow/WEBHOOK-SPEC.md in the integration kit.
POST {your registered endpoint}
recourse-timestamp: 1754460000
recourse-signature: hex(hmac_sha256(secret, timestamp + "." + body))
{ "type": "ruling.money_instruction", "dispute_id": "dsp_…", "escalated": false,
"instruction": { "type": "split", "to_respondent_minor": 45000, "to_claimant_minor": 15000, "currency": "USD" },
"rail_instruction": { "rail": "tempo", "network": "tempo-testnet", "transfer_intents": [
{ "to": "0xPROVIDER", "amount_minor": 45000, "asset": "USDC", "memo": "recourse:dsp_…" },
{ "to": "0xBUYER", "amount_minor": 15000, "asset": "USDC", "memo": "recourse:dsp_…" } ] } }
Billing
Free trial: every account starts with 25 free rulings, any tier — no card required. Metered usage begins only when the credits are exhausted. The playground needs no account at all.
Pay as you go (default): usage is metered per ruling at the published rate card — $4.00 Tier 1, $20.00 Tier 2, $150.00 Tier 3 — no minimum, no commitment, invoiced monthly from your dashboard. Pay by card via Stripe Checkout, or in USDC via Bridge settling on Tempo.
Volume & enterprise: annual commitments with reserved capacity price Tier 1 at $1–2, and add liability-backed rulings, DSA/ODS readiness, and shadow-pilot onboarding — talk to us.
The platform pays; disputants never do; the fee is never a percentage of the disputed value.