docs three endpoints, one response shape
Documentation
Paid per call. Nothing to sign up for — payment is the authentication.
01 payment
How paying works
Arbiter uses x402: an unpaid request is answered with
402 Payment Required and the terms; the client pays and retries. There are
no API keys and no accounts.
# 1. Ask without paying POST https://arbiter-hs23.onrender.com/v1/judge/transaction → 402 Payment Required → PAYMENT-REQUIRED: <base64 terms: amount, asset, payTo, network> # 2. Client signs a USDC payment and retries with proof POST https://arbiter-hs23.onrender.com/v1/judge/transaction X-PAYMENT: <signed payload> → 200 OK, verdict body
The SDK does both steps. You call one method and never see the 402.
| Setting | Value |
|---|---|
| Network | Algorand mainnet |
| Asset | USDC · ASA 31566704 |
| Facilitator | https://facilitator.goplausible.xyz |
| Pays to | GBRO5EM4JM57PDPS4A533V7JZNWFWB7S6IRT5UZNQYPPUN7XEEGJLEHW6I |
Your paying account needs USDC and must be opted in to ASA 31566704. On Algorand a transfer to an account that has not opted in is rejected outright, so an un-opted-in payer simply cannot pay. Fees are sponsored by the facilitator, so the payer does not need ALGO.
02 surface
Endpoints
| Route | Price | Answers |
|---|---|---|
POST /v1/judge/transaction |
$0.002 |
Is this transaction safe to sign? |
POST /v1/judge/counterparty |
$0.01 |
Is this address who I think it is, and can it receive? |
POST /v1/judge/human |
$0.25 |
What would a person say about this? |
GET /v1/judge/human/:taskId |
free |
Collect a human verdict you already paid for |
Transaction — Algorand
{
"chain": "algorand",
"transaction": "<base64 unsigned txn>", // or an array for an atomic group
"signer": "<58-char address>" // optional; enables self-harm detection
}
Transaction — EVM
{
"chain": "evm",
"chainId": 1, // 1, 8453, 42161, 137, 11155111, 84532
"transaction": {
"to": "0x…",
"data": "0x…", // omit for a plain value transfer
"value": "0"
}
}
Counterparty
{
"address": "<58-char address>",
"expectedAsset": "31566704", // enables the opt-in check
"amount": "250.00",
"claimedIdentity": "acme.algo" // detects a swapped payment address
}
Human
{
"question": "Does this photo show a package at a front door?",
"attachments": ["https://…"],
"options": ["yes", "no", "unclear"], // omit for free text
"quorum": 3,
"waitSeconds": 60 // long-poll; 0 returns immediately
}
If reviewers do not finish inside waitSeconds you get a pending verdict and a
taskId. Collect it later from the free retrieval endpoint — timing out never
costs a second payment.
03 contract
The verdict
Every route returns this. Integrate the shape once.
{
"decision": "block", // allow | warn | block | escalate
"risk": 100, // 0–100
"confidence": 1, // 0–1
"findings": [{
"code": "txn.rekey_to_third_party",
"severity": "critical", // info | low | medium | high | critical
"title": "…",
"detail": "…",
"source": "arbiter:decoder"
}],
"evidence": { /* route-specific: the decoded txn, the account record */ },
"ttlSeconds": 60,
"meta": { "latencyMs": 359, "degraded": false }
}
| Decision | What to do |
|---|---|
| ALLOW | Nothing blocking was found. Proceed. |
| WARN | Proceed only if your operator accepts the listed risks. |
| BLOCK | Do not proceed. Acting on this would likely cause loss. |
| ESCALATE | Not enough evidence to decide. Not an approval. Ask a human. |
Two behaviours worth relying on. confidence below 0.4 returns
escalate rather than allow, so a thin-evidence verdict can never
be mistaken for a clean bill of health. And meta.degraded is true when an
upstream was unreachable — the verdict is still useful, because the decode rules that
catch the critical severities do not depend on the network, but it is partial.
04 clients
Clients
| Package | For |
|---|---|
@arbiterlabs/sdk | TypeScript. Pays x402 automatically. |
@arbiterlabs/mcp | Any MCP host. |
@arbiterlabs/langchain | LangChain.js tools. |
@arbiterlabs/eliza | ElizaOS plugin. |
@arbiterlabs/proxy | Local paying sidecar for non-TypeScript agents. |
arbiter-crewai | CrewAI, via the sidecar. |
Spend limits are part of the client
An agent stuck in a retry loop against a paid endpoint is a wallet-draining bug, so the cap is enforced in the payment selector — before anything is signed — rather than left to the integrator to remember.
const arbiter = new ArbiterClient({
baseUrl: "https://arbiter-hs23.onrender.com",
privateKey: process.env.ALGO_KEY,
maxPricePerCallUsd: 0.05, // refuse any single call above this
maxTotalSpendUsd: 10, // lifetime cap for this client
});
Python and other languages
Paying x402 on Algorand requires an AVM scheme client, which today exists only in TypeScript — the published Python packages ship none. Run the sidecar instead; it holds the key, pays, and re-exposes the judgments unpriced on loopback.
ARBITER_URL=https://arbiter-hs23.onrender.com \ ARBITER_PRIVATE_KEY=… \ npx arbiter-proxy
05 failure modes
Errors
| Status | Meaning |
|---|---|
402 | No payment attached, or the payment was not accepted. Usually the payer holds no USDC or is not opted in. |
400 | Request body did not match the schema. The response lists the offending fields — you have already been charged, so it names what to fix rather than telling you to retry. |
404 | Unknown route, or a human taskId that does not exist. |
409 | Reviewer submission rejected — already answered, or the task closed. |
500 | The verdict could not be produced. This call should not have been charged. |
06 supply side
Reviewer API
Unpriced. Reviewers are the supply side and get paid, so charging them to see the queue would be backwards. Most people should just use the app.
| Route | Does |
|---|---|
POST /v1/work/register | Register; returns a bearer token, shown once. |
GET /v1/work/queue | Open questions you have not answered. |
POST /v1/work/:taskId/submit | Answer, with a rationale. |
GET /v1/work/earnings | USDC owed and settled. |