API reference

MCP server

Connect an MCP client to the settlement index, gated by the same API key as the REST endpoints.

Roundhouse serves the Model Context Protocol at POST /v0/mcp. It exposes the /v0 read endpoints as tools, so a client that speaks MCP reads the settlement index without anyone writing a wrapper for it.

There is no separate account, quota or key. A tool call is forwarded to the endpoint the tool wraps and answered by that endpoint's own gate — the same API key check, the same per-minute limit shared across your organisation's keys, the same Query Unit charge on run_sql. Calling list_flows over MCP and GET /v0/flows over HTTP are the same request as far as access and metering are concerned.

Connect#

json
{
  "mcpServers": {
    "roundhouse": {
      "type": "http",
      "url": "https://api.roundhouseai.io/v0/mcp",
      "headers": { "Authorization": "Bearer rh_live_…" }
    }
  }
}

x-api-key: rh_live_… works in place of the Authorization header if your client sets that instead.

Keep the key out of the config file#

Claude Code expands ${VAR} in both headers and URLs, so the config can be committed and shared with no secret in it:

json
{
  "mcpServers": {
    "roundhouse": {
      "type": "http",
      "url": "https://api.roundhouseai.io/v0/mcp",
      "headers": { "Authorization": "Bearer ${ROUNDHOUSE_API_KEY}" }
    }
  }
}

Or add it in one line, without editing a file at all:

bash
claude mcp add --transport http roundhouse https://api.roundhouseai.io/v0/mcp \
  --header "Authorization: Bearer $ROUNDHOUSE_API_KEY"

Variable expansion is a Claude Code feature. Other clients may take the literal string, so use the plain form above unless you know yours expands it. For a rotating or short-lived credential, Claude Code's headersHelper runs a script per connection and re-runs it on a 401.

Why the key is not in the URL#

A URL you could paste whole would be an easier setup, and it is not offered on purpose. The MCP authorization spec is explicit: "Access tokens MUST NOT be included in the URI query string" — clients must send Authorization: Bearer. A query-string credential also lands in access logs, shell history and anything anyone screenshots, and unlike a header a URL is the part people paste into chat. Roundhouse applies the same rule to its own operator endpoints.

Which clients work#

ClientWorks todayHow
Claude CodeYes--header, a headers object, headersHelper, or ${VAR}
Cursor, Windsurf, MCP SDKsYesA headers object in the client's config
Anything with fetchYesSend the header yourself
Claude app (iOS, Android, web)Yes, by signing inIts custom connector runs the OAuth flow below

Signing in with a Roundhouse login#

Some clients have nowhere to put a header. Claude's Add custom connector form takes a Name and a URL, and authenticates through a sign-in flow rather than a credential you paste. For those, Roundhouse speaks OAuth 2.1: paste the endpoint URL, press connect, and sign in with the same account you use for the dashboard.

Which OAuth client setting to pick#

Newer versions of that form ask how Claude should identify itself to the sign-in service, as an OAuth client choice. It matters, because two of the three options need a feature of the sign-in service rather than of Roundhouse, and picking one that is not offered fails with "Couldn't register with <your connector's name>'s sign-in service" — which reads like a Roundhouse fault and is not one.

OptionPick it?
No client ID — register one automatically (DCR)Yes. Dynamic registration is enabled, so leave both fields blank
Use your own OAuth clientAlso works. Paste the client id and secret you were issued
Use Anthropic's hosted client metadata (CIMD)No. Roundhouse's sign-in service does not resolve hosted client metadata documents

Leave Authentication on Always required. Required when the server asks does not work here: it relies on an unauthenticated handshake to detect that no credential is needed, and Roundhouse answers initialize with a 401 precisely so that detection cannot mislead the client into never attempting sign-in.

What you are asked, on a Roundhouse consent screen:

  1. Which organisation the connection should use. Tool calls draw on that organisation's Query Unit balance and share its per-minute rate limit, exactly as one of its API keys would. You are asked because a login can belong to more than one, and the client has no way to know which you meant.
  2. What it may do. Reading the index and running SQL are separate permissions. The second spends Query Units, so it is off unless you tick it.

The connection then appears under Team → Connected apps with the client that asked for it, the address it sends you back to, and when it was last used. Revoking it there ends the app's session immediately; a request already in flight can still succeed for up to a minute, the same window a revoked API key has.

Two things worth knowing before you rely on it. The name an app shows on that screen is whatever it called itself when it registered, which is why the screen puts the redirect address next to it: the address is the part Roundhouse checked. And the rate limit is the organisation's, shared with its keys, so a connector is not a way to buy throughput.

An API key remains the simpler credential for any client that can set a header, and nothing about it changes.

If your client sends neither a header nor a sign-in#

The connection fails at initialize with a 401, before any tool runs. The response carries a WWW-Authenticate header naming where to discover the OAuth flow, so a client that can sign in starts there; one that can do neither cannot use this endpoint.

This is deliberate. initialize used to answer without a credential, so such a connector reported healthy, listed every tool, and failed only on the first real query — three steps after the mistake. Worse, Claude's connector form read that successful handshake as Authentication: None, detected and then never attempted OAuth at all.

Where the key comes from#

There is no MCP-specific credential and no separate MCP quota. The key is the same one the REST endpoints take, from the same two places:

Organisation keyCreated by a person in Team → API keys. Shown once, at creation — that page offers the MCP config block with the key already in it, so you can paste it straight into your client. Rate limit scales with your plan and is shared across your organisation's keys; run_sql is metered at 1 Query Unit per query on a paid plan. Revoke it from the same page.
Trial keyGET /v0/test/x402 mints one for $0.01 paid over x402. It registers a wallet identity, expires after 30 days, runs at a low per-minute limit, and does not cover run_sql. Meant for trying the API, not for running on it.

Both are rh_live_… strings. The endpoint resolves one by hashing it and looking up the key's organisation, tier and limit, so a revoked key stops working within a minute — the resolution is cached that long.

A third source is a Roundhouse login itself, through the OAuth flow above, for clients that can only authorise a remote server by opening a login window. That path issues no key: the credential is a short-lived token the client refreshes, and what it may do comes from the consent screen rather than from a string you paste. It draws on the same organisation balance and the same rate-limit bucket as that organisation's keys.

What a tool call is allowed to do#

initialize needs a credential, so every MCP session starts authenticated. Five tools then need nothing further — get_account, list_capabilities, get_capability, recommend_capability_offers and list_services — the same rule as REST, where discovering and pricing services never needs an account. Over REST those five are also reachable with no credential at all; on MCP the handshake comes first.

get_account is the one to call first. It is unmetered, answers without a key, and reports what your credential resolves to, which lane a metered call would take, what balance is left, and what every route costs in both Query Units and USDC. The point is that entitlement is knowable before a call rather than only from a refusal.

The tools#

Every tool is one /v0 endpoint, named after it, taking exactly the parameters it takes. The API reference is therefore also the tool reference, and tools/list returns each tool's arguments, caps and defaults straight from the same registry the OpenAPI spec is built from.

ToolWrapsKey
get_accountGET /v0/accountNo
list_flowsGET /v0/flowsYes
list_transactionsGET /v0/transactionsYes
list_entity_settlementsGET /v0/entities/{wallet}/settlementsYes
get_agentGET /v0/agents/{wallet}Yes
get_merchantGET /v0/merchants/{wallet}Yes
list_agentsGET /v0/agentsYes
list_walletsGET /v0/walletsYes
get_leaderboardGET /v0/leaderboardYes
list_agent_feedbackGET /v0/agents/{agentId}/feedbackYes
list_facilitatorsGET /v0/facilitatorsYes
get_graphGET /v0/graphYes
list_servicesGET /v0/endpointsNo
list_capabilitiesGET /v0/unifiedNo
get_capabilityGET /v0/unified/{slug}No
recommend_capability_offersGET /v0/unified/{slug}/recommendNo
run_sqlPOST /v0/sqlOrganisation

run_sql is the one paid tool. Any organisation credential runs it metered against that organisation's Query Unit balance, on any plan — 1 QU per started five seconds of query time, so nearly every query costs 1. A paid plan buys the longer 60-second tier, not the route itself. Anything else gets a refusal that says what to do, and which one depends on what you sent:

  • A trial key gets -32002 — it belongs to no organisation, so there is no balance to draw on. The way forward is an organisation key, or POST /v0/sql over plain HTTP where an x402 payment can be attached. The message says exactly that and deliberately does not repeat the HTTP route's own "resend this request with an X-PAYMENT header": a tool call carries no headers you control, so that is an instruction you could only fail at.
  • A paid organisation that has run out gets -32002 with insufficient_balance and a link to top up. It deliberately does not get x402 payment requirements: you have an account, and quietly charging a wallet instead of telling you to top up would be the wrong answer.
  • No credential at all, or a trial key with no organisation behind it, gets -32005, no_account. The MCP surface issues no x402 challenge and cannot pay one, so retrying is pointless until you sign in or configure a key.

get_account reports which of those you are in one field. On this surface lane is credit when metered calls draw down a balance, upgrade_required when an organisation resolved but its plan excludes a route, and no_account only when nothing resolved at all. Those last two were one value until September 2026, so a signed-in connection with an organisation and 2,094 Query Units was told it had no account behind it.

Each of those states the way forward in the error message. data.body carries the route's full response too, but some MCP clients discard error.data before a model sees it, so nothing actionable is put only there.

The paywalled GET endpoints (/v0/test/x402, /v0/topup, /v0/queries/{slug}/results) are deliberately not tools: a tool call carries no X-PAYMENT header, so they would be tools that can only fail. Use them over HTTP, where the exchange can complete.

What an error means#

A tool that ran and failed is a tool result with isError set, carrying the endpoint's own message. A caller who cannot proceed gets a JSON-RPC error instead, because the fix is yours rather than the model's:

CodeMeans
-32001No credential, or one that is unknown, revoked or expired. Sent as a real HTTP 401 with WWW-Authenticate, so an OAuth-capable client can start signing in from it
-32002Your plan does not include this tool, or your organisation is out of Query Units. The message says which, and names the top-up link where that is the answer
-32003Rate limited. The message gives the seconds to wait
-32004This connection was not granted the permission the tool needs. Re-authorise it
-32005No account behind this connection at all. This surface issues no x402 challenge and cannot pay one, so sign in or configure an organisation key — retrying refuses identically
-32601Unknown method. This server offers tools only
-32602Unknown tool, or an argument the endpoint does not take

Every one of those carries data with the route's own status and full response body, and every one also puts the way forward in the message. That duplication is deliberate: some MCP clients discard error.data before a model ever sees it, so a refusal whose only useful content was in data reads as a dead end.

An unrecognised argument is refused, not ignored: a filter that was silently dropped returns a list you believe is narrower than it is. Likewise, a failed read never comes back as an empty one — isError is set and the reason says so, because on an index of other people's payments "no rows" would otherwise read as "nothing ever happened".

Transport#

The endpoint is versioned with the endpoints it wraps. MCP's own protocolVersion negotiation covers the wire protocol — whether tools/call exists and what its envelope looks like. It says nothing about whether list_flows still accepts before, so the tool surface carries its own version in the path. A /v1 read API would arrive with a /v1/mcp.

Both published bases serve it, since the dashboard rewrite maps /api/v0/* to the worker's /v0/*:

https://api.roundhouseai.io/v0/mcpCanonical. Agent traffic belongs on the API host
https://roundhouseai.io/api/v0/mcpThrough the dashboard rewrite. Same worker

https://api.roundhouseai.io/mcp — the path most MCP servers use — answers 404 with the real URL in the body rather than a bare one, so trying it tells you where to go instead of looking like this service has no MCP server. It is not an alias and not a redirect.

Stateless Streamable HTTP: POST one JSON-RPC 2.0 message per request, no session id, no server-initiated stream. GET and DELETE answer 405 with Allow: POST. Batched arrays are refused — batching was removed from the protocol in revision 2025-06-18, and half-serving a batch where one message in five is rate limited tells you less than a refusal does.

The server implements 2025-06-18 and will echo back 2025-03-26 or 2024-11-05 if your client negotiates one of those.

Call it by hand#

bash
curl -sL -X POST "https://api.roundhouseai.io/v0/mcp" \
  -H 'content-type: application/json' \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' | jq '.result.tools[].name'
bash
curl -sL -X POST "https://api.roundhouseai.io/v0/mcp" \
  -H "authorization: Bearer $ROUNDHOUSE_KEY" \
  -H 'content-type: application/json' \
  -d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"list_flows","arguments":{"limit":5}}}' | jq