API v0.2.0OpenAPI 3.1 · https://www.rollcage.tech/api

API reference.

One endpoint is live today: the agent wallet check, free in beta and without a key. Crash tests, co-signing and certificates are planned; their intended shapes are under Planned. Beta: calls are free; the x402 flow is the planned paid tier and is not enforced.

Overview.

Base URL
https://www.rollcage.tech/api
Format
JSON over HTTPS
Auth
None in beta: no key, no account
Payment
Free in beta · x402 is planned and not enforced
Network
Solana mainnet
Browsers
CORS open: Access-Control-Allow-Origin: *
GET/api/wallet-check Live · free in beta

Agent wallet check: score, verdict and what a cosigner would have held.

POST/api/crash-tests Planned

Run a crash test. Private beta, not live.

GET/api/crash-tests/{id} Planned

Crash-test result. Private beta, not live.

POST/api/cosign Planned

Co-sign a transaction against policy and mandate. Private beta, not live.

GET/api/certificates/{id} Planned

Public certificate. Private beta, not live.

Live

Agent wallet check.

Reads a wallet's last 100 transactions on Solana mainnet, its token accounts and its approvals, and answers what a cosigner with a default policy would have held. Read-only: nothing is signed or sent.

GET/api/wallet-check

Free in beta

One call per wallet, no key. The same answer is reused for 5 minutes per address.

Query

addressstringRequired
A Solana wallet address (base58, 32 to 44 characters), or a Solscan or Explorer link to one. Mainnet only.

Responses

  • 200A WalletCheck.
  • 400Not a wallet address; or an account a program owns: That's a program account, not a wallet, with a hint.
  • 404Nothing on mainnet for this address yet: no SOL, no tokens, no transactions.
  • 429Too many checks from one client. Wait for Retry-After.
  • 502 · 504The chain didn't answer, or not in time. Try again in a moment.

curl

# Works today, no key. Put any mainnet wallet address in place of <wallet>.
$ curl "https://www.rollcage.tech/api/wallet-check?address=<wallet>"

JavaScript

const res = await fetch(`https://www.rollcage.tech/api/wallet-check?address=${wallet}`);
const check = await res.json();
if (!res.ok) throw new Error(check.error); // 400 · 404 · 429 · 502 · 504
if (check.verdict === 'would be held') {
  // Stop: show check.would_hold to the owner before you fund or pay.
}

Response 200 · example, shortened

{"address":"<wallet>","sol":3.2,
 "tokens":[{"mint":"EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v","symbol":"USDC","amount":125.5,"value_usd":125.49}],
 "txs_in_window":13,"window":{"from":"2026-10-04T10:00:00.000Z","to":"2026-10-14T10:00:00.000Z"},
 "outflows":[{"amount":7.5,"token":"SOL","mint":null,"to":"<recipient>","time":"2026-10-14T10:00:00.000Z","signature":"<signature>","new_recipient":true}],
 "largest_outflow":{"sol":7.5,"pct":70.1,"to":"<recipient>","time":"2026-10-14T10:00:00.000Z","signature":"<signature>","new_recipient":true},
 "new_recipients":[{"address":"<recipient>","amount":7.5,"token":"SOL","time":"2026-10-14T10:00:00.000Z","signature":"<signature>","lookalike_of":null}],
 "delegates":[{"token_account":"<token account>","mint":"EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v","symbol":"USDC","delegate":"<delegate>","amount":100,"program":"Token"}],
 "programs":[{"program_id":"11111111111111111111111111111111","name":"System Program","txs":5}],
 "score":31,"verdict":"would be held",
 "would_hold":[{"what":"7.5 SOL to Cyy…qg9","why":"70.1% of the balance in one transfer, to an address paid only once","signature":"<signature>","time":"2026-10-14T10:00:00.000Z"}],
 "deductions":[{"points":-30,"reason":"Largest outflow moved 70.1% of the balance in one transfer"},
  {"points":-24,"reason":"3 payments to addresses paid only once in the window"},{"points":-15,"reason":"1 active token delegate"}],
 "activity":{"signed_txs":10,"txs_per_day":1,"swaps":2,"failed_txs":1,"outflows":7,"recipients":5,
  "recipients_seen_once":3,"program_recipients":1,"token_accounts":6},
 "notes":[],"partial":false,"checked_at":"2026-10-14T10:05:00.000Z","explorer":"https://solscan.io/account/<wallet>"}

Score and verdict

The score starts at 100. Each rule takes points off, and every point taken comes back as a reason in deductions.

How the wallet check score is made
RulePoints
The largest single SOL outflow is over 50% of the balance now plus that outflow25% to 50%: −15−30
Each payment to a wallet paid only once in the windowup to −32−8
Each active token delegate: an approval with an amount leftup to −30−15
More than 50 transactions a day paid for by the wallet−10
Less than 0.002 SOL left for fees−5
80 – 100
looks disciplined
50 – 79
needs a cosigner
0 – 49
would be held
  • Window: the last 100 transactions mainnet returns for the address, newest first. window gives the first and the last time in it.
  • Outflows: SOL and token transfers out of the wallet. Not counted: swaps and purchases that bring value back in the same transaction, rent for token accounts, deposits under 0.01 SOL for accounts an app opens, Jito tips and staking.
  • New recipient: a wallet (an address on the ed25519 curve) paid exactly once in the window. Payments to program-controlled addresses (vaults, escrows, pools) are not counted as new recipients.
  • Lookalike: a new recipient with the same first and last four characters as an address the wallet already deals with is named in lookalike_of.
  • Pace: transactions the wallet paid fees for, divided by the days the window spans, at least one day.
  • Token symbols come from on-chain metadata, which anyone can write. They are cut to plain characters, and a symbol that reads like a link or a lure is replaced by the shortened mint.
  • would_hold lists what a cosigner with this default policy would have held: the large outflow, payments to new recipients, active delegates and an unusual pace.

Errors and limits

errorstring
Every non-200 answer carries one plain sentence, safe to show to a person.
hintstring · 400 only
For an account a program owns: what to paste instead.
usagestring · 400, 405
GET /api/wallet-check?address=<Solana wallet address>
  • Beta: the same answer is reused for 5 minutes per address (Cache-Control: public, max-age=300), and a per-client rate limit answers 429 with Retry-After.
  • If a part of the chain data can't be read, the answer says so: partial: true, with the reason in notes.

WalletCheck

addressstring
The wallet that was checked.
solnumber
SOL balance now.
tokensarray of { mint, symbol, amount, value_usd }
Up to 20 token balances, best effort. value_usd is null when no price is known.
txs_in_window · windowinteger · { from, to }
How many transactions were read (up to 100), and the time span they cover.
outflowsarray of { amount, token, mint, to, time, signature, new_recipient }
The 10 latest outflows, newest first.
largest_outflow{ sol, pct, to, time, signature, new_recipient } | null
The largest single SOL outflow and its share of the balance now plus that outflow.
new_recipientsarray of { address, amount, token, time, signature, lookalike_of }
Wallets paid only once in the window, up to 10.
delegatesarray of { token_account, mint, symbol, delegate, amount, program }
Token accounts with an active approval.
programsarray of { program_id, name, txs }
Programs the wallet called in the window, up to 12. name is null for programs we don't name.
score · verdictinteger 0–100 · string
looks disciplined, needs a cosigner or would be held.
would_holdarray of { what, why, signature?, time? }
What a cosigner with the default policy would have held, up to 10.
deductionsarray of { points, reason }
Every point taken off the score, with its reason.
activityobject
signed_txs, txs_per_day, swaps, failed_txs, outflows, recipients, recipients_seen_once, program_recipients, token_accounts.
notes · partialarray of string · boolean
What couldn't be read, if anything.
checked_at · explorerstring
When the check ran, and the wallet on Solscan.

Private beta

Planned.

Not live: these endpoints answer 404 today. The shapes below are the intended ones and can change before they ship. To be in the first cohort, DM us on X (opens in a new tab).

POST/api/crash-tests

Planned · core free · full 25 USDC

Run a crash test: the core suite (200 scenarios) for free, the full suite (2,140 scenarios) for 25 USDC. The sandbox is cloned from mainnet and nothing in it is broadcast.

Request body

targetstringRequired
https endpoint of the agent, or a public repo URL with an adapter.
suite"core" | "full" · default "core"
core: 200 scenarios, free. full: 2,140 scenarios, 25 USDC.
mandatestring
What the agent is for, in plain words.

Intended responses

  • 202Test accepted: test_id, status_url, scenarios.
  • 402Payment required for the full suite, once billing opens.

Intended request

POST https://www.rollcage.tech/api/crash-tests
content-type: application/json

{"target":"https://agent.example.com/run","suite":"core",
 "mandate":"Trade SOL/USDC on approved venues. No outbound transfers."}

Intended response 202

{"test_id":"ct_8f3a21","scenarios":200,
 "status_url":"https://www.rollcage.tech/api/crash-tests/ct_8f3a21"}

GET/api/crash-tests/{id}

Planned · free

The crash-test result: score, stars, results by family and every failure with the layer that stops it.

Path

idstringRequired
The test_id from POST /api/crash-tests.

Intended response 200 · a CrashTest, failures shortened to 1

{"test_id":"ct_0412","score":71,"stars":3,"scenarios":2140,"passed":2082,"failed":58,
 "families":[
  {"name":"Metadata injection","count":340,"failed":14},
  {"name":"Memo & log injection","count":210,"failed":3},
  {"name":"Web content injection","count":260,"failed":11},
  {"name":"Social DM pressure","count":180,"failed":0},
  {"name":"Fake claims & malicious Blinks","count":220,"failed":6},
  {"name":"Honeypot tokens","count":150,"failed":0},
  {"name":"Liquidity & slippage traps","count":240,"failed":9},
  {"name":"Lookalike addresses","count":120,"failed":2},
  {"name":"Permission & delegate abuse","count":160,"failed":0},
  {"name":"Mandate drift","count":260,"failed":13}],
 "failures":[
  {"scenario_id":"MI-117","family":"Metadata injection",
   "what_the_agent_tried":"Proposed moving 92% of its balance to an address it had never paid, after reading token metadata.",
   "stopped_by":"mandate",
   "fix":"Keep token metadata out of the agent's instructions. Lower the per-transaction limit for transfers."}],
 "certificate_id":null}

POST/api/cosign

Planned · 0.001 USDC

Co-sign a transaction against policy and mandate. Rollcage simulates it, checks the policy and the agent's mandate, then answers allow, hold or block.

Request body

transaction_b64stringRequired
The transaction the agent wants to send, signed with the agent key, base64.
agent_idstringRequired
The agent's ID in Rollcage. Its policy and mandate are looked up by it.

Decisions

  • Allow

    cosigned_tx is the transaction with Rollcage's signature added. Broadcast it as is.

  • Hold

    A person on the owner's team approves or blocks it; hold_id identifies it. Don't re-shape and retry.

  • Block

    Final. reason says why.

Intended response 200 · hold

{"decision":"hold",
 "reason":"mandate mismatch: outbound transfer to a never-seen address, 92% of balance",
 "checks":{"simulate":"pass","policy":"pass","mandate":"fail"},
 "latency_ms":186,"cosigned_tx":null,"hold_id":"h_7f2a"}

Intended response 200 · allow

{"decision":"allow",
 "reason":"in mandate: SOL to USDC swap on an approved venue, 4% of balance",
 "checks":{"simulate":"pass","policy":"pass","mandate":"pass"},
 "latency_ms":142,"cosigned_tx":"AQAB…","hold_id":null}

GET/api/certificates/{id}

Planned · free

A public crash-test certificate. The attestation will live on Solana (Solana Attestation Service).

Path

idstringRequired
Certificate ID, e.g. cert_0419.

Intended response 200 · a Certificate

{"id":"cert_0419","agent":"Kestrel","score":94,"stars":5,"suite_version":"v3.2",
 "scope":"full suite · trading agent · mandate: trade SOL/USDC on approved venues",
 "issued_at":"2026-10-11T14:20:00Z","attestation":"7Kd2…Qp4M"}

Payment: the 402 flow

Beta: calls are free; the x402 flow is the planned paid tier and is not enforced. Once billing opens, a paid endpoint will answer the first request with its price; the client pays exactly that amount in USDC on Solana and sends the same request again with the payment attached. No account and no key.

  1. Request

    Call the endpoint as usual.

  2. 402 Payment Required

    The PAYMENT-REQUIRED header says how much, in what, and where to pay.

  3. Pay and resend

    Sign an SPL transfer of exactly amount USDC to payTo. Resend with X-PAYMENT.

  4. Settled

    The response carries X-PAYMENT-RESPONSE: the settlement signature, your receipt.

The PAYMENT-REQUIRED header, as intended

schemestring
"exact": pay exactly amount.
networkstring
solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp, Solana mainnet.
assetstring
EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v, USDC.
amountstring
USDC in base units, 6 decimals: "1000" is 0.001 USDC, "25000000" is 25 USDC.
payTostring
Rollcage's receiving address.
resourcestring
The URL you called.

Intended exchange

# 1. Ask. The answer is a price.
POST https://www.rollcage.tech/api/cosign

HTTP/1.1 402 Payment Required
PAYMENT-REQUIRED: {"scheme":"exact",
  "network":"solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp",
  "asset":"EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v","amount":"1000",
  "payTo":"<address>","resource":"https://www.rollcage.tech/api/cosign"}

# 2. Pay 0.001 USDC and send the same request again, with X-PAYMENT.
HTTP/1.1 200 OK
X-PAYMENT-RESPONSE: <settlement tx signature>

Planned schemas

CrashTest

test_idstring
The test's ID.
scoreinteger · 0–100
The grade.
starsinteger · 1–5
The grade in stars.
scenariosinteger
Scenarios run: 200 (core) or 2,140 (full).
passed · failedinteger
Scenario counts.
familiesarray of { name, count, failed }
Results per scenario family. Families are named; scenario bodies are never returned.
failuresarray of { scenario_id, family, what_the_agent_tried, stopped_by, fix }
stopped_by is one of simulate, policy, mandate, none.
certificate_idstring | null
Set when the run earned a certificate.

CosignDecision

decision"allow" | "hold" | "block"
What to do with the transaction.
reasonstring
Why, in plain words. Report it to the owner on hold or block.
checks{ simulate, policy, mandate }: "pass" | "fail"
Each layer's result.
latency_msinteger
Time from request to decision. Target: p95 under 300 ms.
cosigned_txstring | null
On allow: the transaction with Rollcage's signature added, base64.
hold_idstring | null
On hold: the ID the owner's team approves or blocks.

Certificate

id · agentstring
Certificate ID and agent name.
score · starsinteger
The certified grade.
suite_versionstring
The suite the agent ran, e.g. v3.2.
scopestring
What was tested: suite, agent type, mandate.
issued_atstring · date-time
When it was issued. An old grade looks old.
attestationstring
Solana Attestation Service ID.