# API

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

Section: API reference
Source: https://www.roundhouse.studio/docs/api

---

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.

| | |
| --- | --- |
| [**Playground**](https://www.roundhouse.studio/docs/api/playground) | Pick an endpoint, fill the parameters, send it, copy the curl. No key needed. |
| [`openapi.yaml`](https://www.roundhouse.studio/docs/api/openapi.yaml) | The spec. Feed it to a client generator, an HTTP client, or an agent. |
| [`openapi.json`](https://www.roundhouse.studio/docs/api/openapi.json) | The 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](https://www.roundhouse.studio/docs/identity/kya) — 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`](https://www.roundhouse.studio/stats) page — see [the data model](https://www.roundhouse.studio/docs/data/data-model#the-aggregation-rule).

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](https://www.roundhouse.studio/docs/data/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](https://www.roundhouse.studio/docs/data/sql).

## 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](https://www.roundhouse.studio/flows).

```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](https://www.roundhouse.studio/docs/guides/first-x402-payment).

### Create an organization key — no expiry

Sign in and create keys under [Team → API keys](https://www.roundhouse.studio/dashboard/team). These never expire and are metered
in Query Units.

## Limits

| Tier | SQL queries/min | Query Units | Expiry |
| --- | --- | --- | --- |
| Anonymous | 30 per IP | — | — |
| Trial (x402, $0.01) | 240 per key | not metered | 30 days |
| Organization key | 120 per org | 1 QU per query | none |

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](https://www.roundhouse.studio/docs/x402/facilitators) and [charge for your API](https://www.roundhouse.studio/docs/guides/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](https://www.roundhouse.studio/dashboard/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](https://www.roundhouse.studio/docs/x402/errors).

## Next steps

- [Query the index](https://www.roundhouse.studio/docs/get-started/query-the-index) — which read path to use
- [SQL over the index](https://www.roundhouse.studio/docs/data/sql) — the queries
- [The data model](https://www.roundhouse.studio/docs/data/data-model) — every column
- [`/llms.txt`](https://www.roundhouse.studio/llms.txt) — the agent reference for this whole origin

---

Every page in these docs is available as markdown at its own URL plus `.md`.
Full index: https://www.roundhouse.studio/docs.md
