API reference

API

Query the dataset over REST or read-only SQL, with no key required to start.

The whole dataset is a machine surface. Read it over plain HTTP or send read-only SQL. No key is required to start. For higher limits, pay a cent over x402 at /v0/test/x402 or create an organization key.

Spec and playground#

The whole surface is described as OpenAPI 3.1, and callable from the browser.

PlaygroundPick an endpoint, fill the parameters, send it, copy the curl. No key needed.
openapi.yamlThe spec. Feed it to a client generator, an HTTP client, or an agent.
openapi.jsonThe same document, for tooling that defaults to JSON.

Both spec forms are generated from one source, so they cannot disagree, and a test compares every documented path and query parameter against the worker's own handlers — a route renamed there fails the build here rather than leaving the spec describing an API that no longer exists.

bash
# Generate a typed client
npx openapi-typescript https://www.roundhouse.studio/docs/api/openapi.yaml -o roundhouse.d.ts

Base URL#

Live today on this origin: https://www.roundhouse.studio/api/v0/*. The api.roundhouse.studio subdomain is not bound yet — every example below works against /api/v0/... now and will keep working once it is.

The bare roundhouse.studio host redirects to www, so follow redirects (curl -L).

REST endpoints#

Per-entity and per-settlement reads. All return JSON.

http
GET /v0/flows?limit=50                     live flow of funds (newest first)
GET /v0/transactions?limit=50              global settlement feed
GET /v0/agents                             indexed ERC-8004 agents
GET /v0/agents/<wallet>                    per-agent settlement stats
GET /v0/merchants/<wallet>                 per-merchant settlement stats
GET /v0/entities/<wallet>/settlements      raw per-entity settlements
GET /v0/graph?window=500&wallet=<0x…>      reputation graph: entity nodes + payer→payee edges
GET /v0/endpoints?q=<search>               indexed x402 service directory
GET /v0/unified                            capability catalogue: jobs, and their canonical shape
GET /v0/unified/<slug>                     one capability's offers and its going rate
GET /v0/unified/<slug>/recommend           shortlist endpoints for a job, by your own criteria
GET /v0/leaderboard?limit=25               agents ranked by trust
GET /v0/facilitators                       relayers, per chain
GET /v0/wallets                            every address the index has seen
GET /v0/agents/<agentId>/feedback          ERC-8004 feedback for one agent
GET /v0/test/x402                          x402 test drive: pay $0.01, get an API key

POST /v0/kya                               verify and store a KYA attestation
POST /v0/kya/verify                        verify one without storing it
GET  /v0/kya/<digest>                      read one attestation
GET  /v0/kya/agents/<wallet>               attestations by one signer

The /v0/kya/* endpoints are the KYA memo — a signed statement whose digest the payment's own EIP-3009 nonce commits to. Reads re-derive the digest from the stored document, so a record that resolves is proof rather than a lookup.

Cursor-paginated lists carry a next_before — an opaque token encoding the whole sort key, tiebreaker included, so a page boundary landing inside a group of rows that share a timestamp does not skip the rest of them. Pass it back as ?before=; don't parse it or build one by hand. A null next_before is the end of the list.

/v0/leaderboard is the exception: it is a ranked list, so it pages by ?offset= and returns next_offset. A rank is a position, and score is nullable — a cursor has nothing stable to compare against.

/v0/graph takes either ?window=N (row count, includes today's live tip) or ?days=N (whole days only, so an answer is stable for the day and cacheable until midnight).

Note

Aggregate figures are per-entity by design. Global volume totals live only on the /stats page — see the data model.

An empty array is a real answer; a failed read is not. A rejected query returns 503 upstream_unavailable naming the resource, never 200 with an empty list.

Read-only SQL#

For questions the fixed endpoints don't cover, POST /v0/sql runs a single SELECT over the public dataset and returns rows as JSON.

bash
curl -sL https://www.roundhouse.studio/api/v0/sql \
  -H 'content-type: application/json' \
  -d '{"sql":"select wallet, inbound_usd, inbound_count from mv_entity_rollups order by inbound_usd desc limit 10"}'

# → {"rows":[...], "row_count":10, "duration_ms":205, "authenticated":false, "tier":"anonymous"}

What you can query#

Only the public data-layer tables, the same data the REST endpoints serve:

text
chains                chain_tokens          settlements
entities              agents                agent_feedback
facilitators          fee_proxies           external_resources
settlement_corrections      settlement_sync_state
mv_entity_rollups     mv_entity_daily       mv_global_daily

Column notes: mv_entity_rollups has no display_name — read names from entities. An unbounded aggregate or wide join over the full settlements table can hit the 8-second timeout; filter on block_time or read the pre-computed mv_* views. Full column reference: the data model.

Sandbox (enforced in the database)#

  • A single statement, SELECT or WITH only. No semicolons or comments.
  • Runs as a role that can read only the tables above. The platform's own tables (users, listings, receipts) and Postgres internals (pg_catalog, information_schema, session/config functions) are unreachable.
  • Read-only transaction, 8-second timeout, hard 300-row cap.

Query shapes to copy: SQL over the index.

API keys and authentication#

Send a key as a bearer token (or x-api-key):

bash
curl -sL https://www.roundhouse.studio/api/v0/sql \
  -H 'authorization: Bearer rh_live_...' \
  -H 'content-type: application/json' \
  -d '{"sql":"select day, volume_usd from mv_global_daily order by day desc limit 30"}'

There are two ways to get one.

Pay a cent over x402 — no account#

GET /v0/test/x402 is a paywalled test resource. Unpaid it answers 402 with standard x402 payment requirements for $0.01 USDC on Base; paid, it settles on-chain and returns a trial key raising your SQL limit to 240/min for 30 days.

One request proves your client, your wallet, and our settlement path all work — and the payment is indexed like any other, so it shows up in Flow.

bash
# See the challenge
curl -isL https://www.roundhouse.studio/api/v0/test/x402
javascript
// Or let a standard x402 client handle it
import { wrapFetchWithPayment } from 'x402-fetch';

const pay = wrapFetchWithPayment(fetch, account);
const { api_key, message } = await pay(
  'https://www.roundhouse.studio/api/v0/test/x402',
).then((r) => r.json());
// → "Payment settled and your Roundhouse API key is live. Copy it now …"

The key is shown exactly once — we store only its hash. The paying wallet needs USDC but no ETH: the facilitator covers gas. Step by step: your first x402 payment.

Create an organization key — no expiry#

Sign in and create keys under Team → API keys. These never expire and are metered in Query Units.

Limits#

TierSQL queries/minQuery UnitsExpiry
Anonymous30 per IP
Trial (x402, $0.01)240 per keynot metered30 days
Organization key120 per org1 QU per querynone

A trial key is owned by the wallet that paid for it, so it holds no Query Unit balance and can never spend an organization's. The SQL sandbox is identical across all three tiers.

Using the Roundhouse facilitator#

A facilitator verifies an x402 payment payload and submits the settlement on-chain. Roundhouse runs a first-party one: it pays the gas (your payers need USDC only), it records failed attempts a chain indexer can never see, and settlements through it are indexed at full confidence with the paid resource attached. Point your paywall at it:

bash
X402_FACILITATOR_URL="https://x402.roundhouse.studio"
X402_FACILITATOR_FORMAT="standard"
http
GET  /              service descriptor — endpoints, chains, dependencies
GET  /supported     payment kinds, signers, fee payers per chain
POST /verify        verify a payment payload against your requirements
POST /settle        settle a verified payment on-chain
GET  /test/x402     the facilitator's own $0.01 test resource

Bodies are { x402Version, paymentPayload, paymentRequirements }, where paymentRequirements is what you published.

Warning

paymentPayload is the decoded object, not the base64 string. Base64 is only the X-PAYMENT header's encoding — a facilitator reads x402Version, scheme and network off the envelope, so handing it a string answers 500 No facilitator registered for x402 version: undefined.

More: facilitators and charge for your API.

Query Units and plans#

Usage is metered in Query Units (QU). Every organization starts with 1M QU on the free tier. See Billing for your balance and plan. Paid plans with higher allowances are coming soon.

Errors#

text
400 invalid_query      failed the SQL sandbox checks (detail explains why)
401 invalid_api_key    key not found, revoked, or expired
402 insufficient_qu    organization is out of Query Units
429 rate_limited       too many requests this minute
400 query_failed       the query ran but errored (timeout, bad column, …)

# GET /v0/test/x402 only
402 payment_malformed  no EIP-3009 authorization + signature in the header
402 payment_invalid    the facilitator rejected the payment (detail says why)
402 payment_failed     verified, but settlement failed on-chain
409 key_already_issued that settlement already bought a key — pay again
500 key_issue_failed   settled but key issuance broke; the tx hash is in the body

409 key_already_issued is the one people misread: your payment succeeded, and a key was already minted against that settlement. It is not a duplicate charge — it is a refusal to mint twice for one payment. Every other failure mode: payment errors.

Next steps#

In this section