API reference

The HTTP API behind the app, the SDK and the MCP server: sign-in, reads, live events, and prepare → sign → submit.

Base URL and envelope

The API answers at https://api-production-b0c5e.up.railway.app. Every response is JSON in one envelope:

{ "data": { }, "meta": { "requestId": "…", "mode": "live", "asOf": "2026-10-16T09:12:04Z", "slot": "…" } }

or, on failure:

{ "error": { "code": "COVERAGE_TOO_LOW", "message": "…", "retryable": false, "details": { } }, "meta": { "requestId": "…", "mode": "live" } }

Amounts are decimal strings in USDC base units (six decimals). Addresses are base58. Timestamps are ISO 8601 in UTC, to the second. Hashes are hex.

The TypeScript client in @bonded/contract (bundled with the SDK) wraps every endpoint below with its types: createBondedClient({ baseUrl: 'https://api-production-b0c5e.up.railway.app' }).

Sign in

Sign-in proves a wallet holds its key, with one signed message and no transaction.

RequestBodyReturns
POST /v1/auth/challenge{ wallet }{ challengeId, message, expiresAt }
POST /v1/auth/verify{ challengeId, signatureBase64 }the Session, and a session cookie
GET /v1/session—the current Session
DELETE /v1/session—{ signedOut: true }
  1. Ask for a challenge for the wallet. The message begins with Bonded off-chain authentication v1, names the site's origin and the wallet, and expires within minutes.
  2. Sign the exact message bytes with the wallet's key (Ed25519) and send the signature in base64.
  3. The API sets an HttpOnly bonded_session cookie and returns the session: { id, wallet, identities, expiresAt, csrfToken }.

identities lists the roles this wallet holds, each { role, subjectId, wallet }: admin of an operator, merchant, session_key of a vault, guardian, arbiter of an escrow.

Every request that changes something (POST, DELETE) carries the session's csrfToken in an X-CSRF-Token header. Clients that aren't browsers keep the cookie in their own cookie jar; the SDK does this for you.

Reads

GET /healthz, GET /v1/protocol and GET /v1/operators/{id}/public are public. Every other read needs a session, and returns only what that session's roles may see.

RequestReturns
GET /v1/protocolthe deployment: program id and cluster genesis hash (voucherDomain), USDC mint, protocol parameters and configured fees
GET /v1/operators/{id}the operator overview: operator, vaults, bond, lines, recent activity
GET /v1/operators/{id}/publicwhat merchants read before extending credit: coverage, tabs settled, defaults
GET /v1/vaults/{id}a vault with its merchant rules, approvals and payments
GET /v1/bonds/{id}a bond with its lines and default events
GET /v1/lines?operatorId=&vaultId=&merchantId=&cursor=&limit=credit lines, paginated; an agent's session key lists its own vault's lines with vaultId
GET /v1/lines/{id}one line with its operator's public profile and its merchant
GET /v1/merchants/{id}the merchant overview: lines to it, operators' coverage, open claims, escrow jobs, settlements
GET /v1/default-events/{id}a default event with its claims
GET /v1/escrows/{id}an escrow job with the actions each role may take now
GET /v1/activity?operatorId=&vaultId=&lineId=&merchantId=&cursor=&limit=the activity feed, newest first

Paginated responses are { items, nextCursor }; pass nextCursor back as cursor for the next page.

meta.asOf and meta.slot say how fresh a read is: reads come from the indexer's projection of the chain, which follows each confirmed transaction within seconds.

Actions: prepare, sign, submit

Every change on chain goes through three calls. The API never holds a user's key.

POST /v1/transactions/prepare   { action, signerWallet, idempotencyKey }
  → { preparedId, transactionBase64, messageHash, blockhash, lastValidBlockHeight,
      requiredSignerRoles, accounts, simulation: { allowed, summary, error }, expiresAt }

(sign transactionBase64 in the wallet)

POST /v1/transactions/submit    { preparedId, signedTransactionBase64, idempotencyKey }
  → { id, preparedId, state, signature, error, updatedAt }

GET  /v1/transactions/{id}      → the same status, until state is confirmed, failed or expired
  • Prepare builds the transaction for the action, with the signer as fee payer and a compute budget at the front (a limit sized from the simulation and a priority fee, 1,000 micro-lamports per unit by default), and simulates it. When simulation.allowed is false, simulation.error carries the program's refusal (for example COVERAGE_TOO_LOW); don't sign it.
  • Sign the exact bytes. A wallet may add ComputeBudget instructions (a priority fee) and Lighthouse guard instructions after the prepared ones; the API accepts those and refuses any other change.
  • Submit verifies the signed transaction against the preparation and sends it. state moves from submitted to confirmed (or failed, with the program's error). A preparation expires with its blockhash, about a minute after prepare. Its key then answers 409 DEADLINE_PASSED with details.prepareAgain: true: prepare again under a new key.
  • Idempotency. idempotencyKey is yours, up to 128 characters. While a preparation is valid, the same key returns it; use a distinct key for the submit (the app uses <key>:submit). Retrying either call with its key never sends an action twice.

The action is one object with a kind. The ones an operator, merchant or agent sends:

KindWho signsFields
create_operatorthe admin walletguardian (address or null)
create_vaultadminoperatorId, agentId, policy
set_policyadminvaultId, policy
fund_vaultadminvaultId, amount
set_merchant_ruleadminvaultId, merchantId, perTransactionCap, windowBudget, quoteRequired
rotate_session_keyadminvaultId, sessionKey
withdrawadminvaultId, amount, destinationTokenAccount
freezeadmin or guardianvaultId
unfreezeadminvaultId
post_bondadminoperatorId, amount, sourceTokenAccount
open_lineadminbondId, vaultId, merchantId, limit, expiresAt
resize_lineadminlineId, limit
renew_lineadminlineId, expiresAt
request_withdrawaladminbondId, amount
finalize_withdrawaladminbondId, destinationTokenAccount
refill_floatanyonevaultId
paysession keyvaultId, merchantId, amount, intentHash, optional nonce, approvalId, quote
pay_escrowsession keyvaultId, merchantId, quote, optional approvalId
accept, disputesession key or adminescrowId
settle_tabmerchantlineId, voucher
file_claimmerchantdefaultEventId, lineId, voucher
mark_deliveredmerchantescrowId, deliverableHash
release_after_window, refund_on_timeout, execute_default_payoutanyone, once dueescrowId / defaultEventId

A policy is { sessionKey, floatCap, windowSeconds, windowBudget, cosigner, cosignThreshold, guardian }. The full list of kinds, protocol administration included, is in the whitepaper's instruction reference.

Live events

GET /v1/events?topics=operator:{id},merchant:{id} is a server-sent event stream for a signed-in session. Topics: protocol, operator:{id}, vault:{id}, line:{id}, merchant:{id}, bond:{id}, default:{id}, escrow:{id}; a session subscribes only to topics its roles may read.

Each event's data is { id, type, topics, at, activity?, resources, cursor }:

  • activity: something happened (a payment, a settlement, a claim); activity carries it.
  • invalidate: the listed resources changed; read them again.
  • reset: the stream can't replay what you missed; read everything again.

Reconnect with after=<cursor> (or the Last-Event-ID header browsers send) to resume where you left off.

Errors

CodeMeans
INVALID_INPUTA field is missing or malformed; details names it.
UNAUTHORIZEDNo session, or it expired. Sign in again.
FORBIDDENThe session's roles don't allow this read or action.
NOT_FOUNDNo such account.
CONFLICT, IDEMPOTENCY_CONFLICTThe state changed under you, or a key was reused for different content.
RATE_LIMITEDToo many requests; wait for Retry-After.
INDEXER_BEHINDThe read model hasn't caught up with the chain yet; retry shortly.
CONFIGURATION_REQUIREDThe deployment isn't initialized for this action yet.
SIMULATION_ONLYThe API is answering from fixtures; its transactions can't be signed or submitted.
INTERNAL_ERRORSomething failed on our side; retryable says whether to retry.
VAULT_FROZENThe vault is frozen.
WINDOW_BUDGET_EXCEEDED, PER_TRANSACTION_CAP_EXCEEDEDThe payment is over the vault's window budget or the merchant's cap.
APPROVAL_REQUIRED, APPROVAL_EXPIRED, APPROVAL_USEDThe payment needs a co-signer's approval, or the approval can't be used.
QUOTE_REQUIREDThis merchant takes only quoted payments.
VOUCHER_EXPIRED, VOUCHER_REPLAYA voucher is past its expiry, or not newer than the last one settled.
INSUFFICIENT_FUNDSThe paying account holds too little.
COVERAGE_TOO_LOWThe bond wouldn't cover its lines at the operator's minimum coverage.
WITHDRAWAL_COOLING_DOWNThe bond withdrawal hasn't unlocked yet.
DEADLINE_NOT_REACHED, DEADLINE_PASSED, INVALID_TRANSITIONAn escrow or default step is early, late, or not allowed from the job's current state.

retryable: true means the request may succeed later. It succeeds unchanged, except when details.prepareAgain is set: then prepare the action again under a new key.

The service's values

APIhttps://api-production-b0c5e.up.railway.app
Network…
Program—
Genesis hash—
USDC mint—

NextFees and settlement