On this page
HTTP API
The routes of the hosted site and console, and the files of a vault's proof page, with who may call each one, what it takes, what it answers and how often. An agent pays through the SDK on chain, not over HTTP.
There is no public agent HTTP API. The routes under /api serve the hosted site and its console: sign-in, Payables, the browser extension, top-up, the Canary, the proof page's TEE check, health checks, scheduled jobs, Telegram and the alert links. The last table covers the three files a vault's proof page offers, under /proof instead. An agent pays from its own process through the SDK, which sends spend to the vault on NEAR with the agent's own key. See Let an agent pay.
How to read the tables#
Each row is one method of one route. Its anchor is http-api-, the method and the path after /api, such as POST /api/auth/verify. A proof file's anchor keeps proof and writes its dot as a dash, such as GET /proof/:vault/payments.csv.
- Route: the method and the path.
:vaultstands for a vault's account id. - Who may call: one of five callers.
- Public: no credential, or the code or token the request carries is the credential.
- A signed-in session: the
pb_sessioncookie that sign-in sets. The role the row names is the one the vault's membership row must hold, read from the database on every request. - An extension token: an
Authorization: Bearer pbx_...header, never a cookie. These routes send no CORS headers, so no other page can read their answers. CRON_SECRET: the bearer secret the scheduler sends. A secret shorter than 32 characters refuses every call.- The Telegram secret: the header Telegram sends with each update.
- "From a page at the same address" means the route compares the request's
Originwith its own host and answers403 bad_originwhen they differ. - Body: JSON, unless the row says form data or a query.
- Success: the status and the answer's fields. Every JSON answer under
/apiis sent withcache-control: no-store. The proof files say their own caching below. - Errors: every
{ error }code the route can answer, as its status and code. Each one is a row on Errors, with what it means and what to do next, anchored ashttp-<status>-<code>, for example429 rate_limited. - Rate limit: the most calls in a window, what the window counts, and the key of the limit in
LIMITS(apps/web/src/lib/ratelimit.ts). The limiter's429 rate_limitedcarries aRetry-Afterheader. An IPv6 address counts by its /64. When the limiter's database cannot be reached, the route answers503and does nothing.
The network of a call#
A route works on the network of the address it is called at: the server matches the request's host against the origins it serves, as Testnet and mainnet explains. A session, a sign-in challenge and an extension token each belong to the network they were made on. The routes that need a session answer 409 wrong_network for a vault registered on the other network, and the faucet answers 404 not_found instead.
The 1Click top-up routes are built for mainnet only and answer 409 mainnet_only elsewhere. The faucet serves testnet only. NEAR testnet is live today. On mainnet one small vault runs, v0.probarion.near, and no mainnet address is served: see What works today.
Sign-in#
Sign-in takes two calls. GET /api/auth/challenge returns a message with a fresh nonce for the wallet to sign as NEP-413, and keeps a copy in the pb_chal cookie. POST /api/auth/verify takes the wallet's answer. The server uses its own message, nonce and recipient, never the ones a body carries. It burns the nonce before it reads the signature, so a challenge is good once, success or not, and it checks on chain that publicKey is a full-access key of accountId.
The session is a stateless signed cookie that lasts 12 hours. POST /api/auth/logout clears it in the browser, but a copy someone kept still verifies until it expires. Membership is read from the database on every request, so removing an account from a vault takes effect at once.
| Route | Who may call | Body | Success | Errors | Rate limit |
|---|---|---|---|---|---|
GET /api/auth/challenge | Public | None | 200 with { message, recipient, nonce }, and the pb_chal cookie for 5 minutes | 429 rate_limited, 503 unavailable | 20 a minute per IP address (authChallenge) |
POST /api/auth/verify | Public, from a page at the same address, with the pb_chal cookie | JSON { accountId, publicKey, signature }: the wallet's NEP-413 answer, 2048 bytes at most | 200 with { accountId }, and the pb_session cookie for 12 hours | 403 bad_origin, 429 rate_limited, 401 challenge_missing_or_expired, 413 payload_too_large, 503 wrong_chain, 401 invalid_input, 401 bad_signature, 503 unavailable | 10 a minute per IP address (authVerify) |
POST /api/auth/logout | Public, from a page at the same address | None | 200 with { ok: true }. It clears pb_session and pb_chal. | 403 bad_origin, 503 unavailable | None |
Console#
POST /api/console/:vault/invoices runs one Payables cycle on an invoice, for the vault's owner or an approver. Payables is Live on testnet. The console finds the mandate whose purpose document the upload carries, then runs the SDK's process() on the invoice text. outcome is that cycle's SpendOutcome, which Spend outcomes explains. When status is unconfirmed, the payment was sent and its confirmation is pending: do not upload the invoice again.
Who signs a hosted payment
Your wallet signs nothing in this cycle. The console sends spend with the agent key the mandate is bound to, and anchors the ledger with the vault's ledger key: the two keys activated on the vault's Keys tab. The vault still runs every one of its checks, so the console can pay only what the mandate allows. See How a payment is checked.
| Route | Who may call | Body | Success | Errors | Rate limit |
|---|---|---|---|---|---|
POST /api/console/:vault/invoices | Signed-in session of the vault's owner or an approver, from a page at the same address | multipart/form-data: the invoice as file (PDF or .txt) or as text, and an optional purpose file. A Content-Length header is required: 4,259,840 bytes at most in all, and 4,194,304 bytes at most for each file. | 200 with { invoiceId, status, outcome, recorded, amountIssue, requestId }, and a notice when status is unconfirmed or awaiting_owner, or when the refusal of custom.invoice_number_duplicate is the outcome's code: then it says whether an upload can run again | 403 bad_origin, 401 unauthenticated, 403 forbidden, 409 wrong_network, 429 rate_limited, 411 length_required, 413 too_large, 400 invalid_input, 415 unsupported, 409 duplicate, 409 busy, 409 no_keys, 409 no_mandate, 409 key_unavailable, 503 key_unavailable, 503 chain_unavailable, 400 empty, 400 unreadable, 502 failed, 500 internal, 503 unavailable | 30 an hour per vault (invoiceUpload) |
Browser extension#
These routes let the browser extension pair with an account, read what waits for it, and queue invoices for the owner's Confirm. They are Preview: built and tested. The extension for Chrome that calls them is on the Chrome Web Store (opens in a new tab) (What works today).
A token is pbx_ and 43 more characters. The server keeps its hash and, as a hint, its last four characters, which the Browser extension page shows under Connected browsers. The signed-in Browser extension page (/extension) connects the extension or shows a pairing code, and POST /api/ext/pair exchanges that code for a token. A code is 8 letters and digits, shown as XXXX-XXXX, and is good once, for 10 minutes.
POST /api/ext/invoices queues one invoice under the mandate the member chose in the extension, for a vault where the token's account is the owner or an approver. The role is read again on each call, and a Canary vault takes no invoices. The server reads the invoice as the console upload does, then runs these checks in order. Each refusal stores nothing:
- the file cannot be read (
422 unreadable) or has no text (422 empty), or the code cannot read one clear total (422 amount_unclear); - the mandate is not on the vault as the chain lists it now (
404 mandate_not_found), or cannot take an invoice now (422 mandate_inactive); - the mandate's agent key or the vault's ledger key is not active in the console (
409 no_keys); - the payee the invoice names contradicts the mandate's, or the vault's free
check_spendview says it would refuse this payment now:422 refused_by_code, whoseruleismandate.payee_mismatchorcontract.check_spend.
When NEAR cannot be read for the mandates or the view, the answer is 503 chain_unavailable, and nothing is stored either. An invoice that passes these checks is stored in awaiting_owner with three checks: the hold extension.mandate_chosen, since no purpose document was matched; mandate.payee_mismatch as a pass; and contract.check_spend as a pass, or as a hold when the amount is above the mandate's approval limit and the vault would hold the payment for its approvers. The answer is 201. When the vault already holds the invoice, nothing new is stored, and the answer is 200 with duplicate: true and the stored invoice's status.
Who pays a queued invoice
The route never pays. It calls no model, opens no agent or ledger key and sends no transaction. A queued invoice is paid only after the owner's Confirm in the console: a NEP-413 signature from the owner's wallet, which the server checks against a full-access key of the vault's on-chain owner before it sends the spend. The vault still runs every one of its checks on that spend, and holds it for the approvers when it is above the mandate's approval limit. The owner can reject the invoice instead, and nothing is paid.
A token can do what these routes do and nothing more: read the inbox, queue an invoice and end itself. It cannot pay, approve, confirm, or change a mandate or a key.
| Route | Who may call | Body | Success | Errors | Rate limit |
|---|---|---|---|---|---|
POST /api/ext/pair | Public: the pairing code is the credential | JSON { code, label } with a Content-Length header, 1 KiB at most | 200 with { token, account, network, expires_at }. The token is in this answer only: the server keeps its hash and its last four characters. | 429 rate_limited, 400 invalid_input, 400 bad_code_format, 401 bad_code, 503 unavailable | 10 in 10 minutes per IP address and network (extPairPerIp); 300 in 10 minutes per network (extPairGlobal) |
GET /api/ext/inbox | Extension token: Authorization: Bearer pbx_... | None | 200 with { network, account, counts, vaults }. Each vault lists its awaiting invoices, its held spends and its mandates. | 401 bad_token, 429 rate_limited, 503 unavailable | 60 a minute per account and network (extInbox) |
POST /api/ext/invoices | Extension token: Authorization: Bearer pbx_..., of an account that is the vault's owner or an approver. The role is read again on every request, and a Canary vault takes none. | multipart/form-data: vault, the vault's account id, mandate, the id of the mandate the member chose, and the invoice as file (PDF or .txt). A Content-Length header is required: 4,210,688 bytes at most in all, and 4,194,304 bytes at most for the file. | 201 with { invoice_id, status, url }: status is awaiting_owner, and url opens the invoice in the console. Nothing is paid: the invoice waits there for the owner's wallet-signed Confirm. 200 with { invoice_id, status, duplicate, url } when the invoice passes every check and the vault already holds it (the same text once normalised, or the same invoice number for the same payee): duplicate is true, status is the stored invoice's, and nothing new is stored. | 401 bad_token, 429 rate_limited, 411 length_required, 413 too_large, 400 invalid_input, 403 not_member, 409 wrong_network, 415 unsupported, 422 unreadable, 422 empty, 422 amount_unclear, 503 chain_unavailable, 404 mandate_not_found, 422 mandate_inactive, 409 no_keys, 422 refused_by_code, 503 unavailable | 20 in 10 minutes per account and network (extInvoice) |
POST /api/ext/revoke | Extension token: Authorization: Bearer pbx_... | None | 204 with no body. The calling token ends at once. | 401 bad_token, 503 unavailable | None |
Top-up#
On mainnet the vault's Top-up tab is built to add Base USDC to the vault's own NEAR Intents account through 1Click. It has not been tried with a live quote yet. POST /api/topup/quote asks 1Click for a quote and answers only with one whose signature verifies against 1Click's key and that says what was asked. It builds the deposit transaction from that quote and the network's USDC, never from what the browser sent, and only when the chain names the signed-in account as the vault's owner. POST /api/topup/submit tells 1Click which transaction paid the deposit address. It only speeds things up, since 1Click also finds the deposit itself. GET /api/topup/status passes on 1Click's status word for the deposit.
Owner signs
The owner signs the deposit transaction that POST /api/topup/quote returns, in their own wallet: a storage_deposit and an ft_transfer of USDC to the 1Click deposit address. The route only builds it.
On testnet, POST /api/topup/faucet mints 25 test USDC into the vault, and your wallet signs nothing: the team's mint-only key signs the mint. The faucet is Preview: it is built and tested, and its key is set on probarion.xyz; on 2026-11-09 its button minted 25 test USDC into the testnet demo vault, demo.vaults.probarion.testnet. It works once a UTC day per vault, within daily caps per account and for all vaults together. Those day counters start again at midnight UTC, and a 429 from one of them gives the seconds until then as its Retry-After. Each press also counts against a press limit per account, before any read of the chain, and that limit's 429 gives the end of its own window instead. A refusal from the checks that run before the day counters does not use up the vault's day.
| Route | Who may call | Body | Success | Errors | Rate limit |
|---|---|---|---|---|---|
POST /api/topup/quote | Signed-in session of the vault's owner, from a page at the same address | JSON { vault, amountUsdc }, the amount as a decimal string | 200 with { quote, tx }: a 1Click quote whose signature checked out, and the deposit transaction for the owner to sign | 403 bad_origin, 400 invalid_input, 401 unauthenticated, 403 forbidden, 409 wrong_network, 409 mainnet_only, 503 not_configured, 429 rate_limited, 503 unavailable, 400 invalid_amount, 503 chain_unavailable, 409 asset_not_allowed, 502 quote_rejected, 502 quote_unavailable | 10 a minute per account and network (topupQuote) |
GET /api/topup/status | Signed-in session of the vault's owner | None. Query: vault and depositAddress | 200 with { status, updatedAt } as 1Click reports them. updatedAt may be missing. | 400 invalid_input, 401 unauthenticated, 403 forbidden, 409 wrong_network, 409 mainnet_only, 503 not_configured, 429 rate_limited, 503 unavailable, 502 status_unavailable | 30 a minute per account and network (topupStatus) |
POST /api/topup/submit | Signed-in session of the vault's owner, from a page at the same address | JSON { vault, txHash, depositAddress } | 200 with { ok: true }, even when 1Click refuses or cannot be reached | 403 bad_origin, 400 invalid_input, 401 unauthenticated, 403 forbidden, 409 wrong_network, 409 mainnet_only, 503 not_configured, 429 rate_limited, 503 unavailable | 10 a minute per account and network (topupSubmit) |
POST /api/topup/faucet | Signed-in session of the vault's owner, from a page at the same address | JSON { vault } | 200 with { status: "minted", txHash, amount: "25" }. 202 with { status: "unknown", txHash, detail } when the mint was sent and its outcome is not known. | 404 not_found, 403 bad_origin, 400 invalid_input, 401 unauthenticated, 503 not_configured, 429 rate_limited, 409 not_owner, 409 wrong_token, 503 unavailable, 502 mint_failed | 10 in 10 minutes per account (faucetTries); 1 a day per vault (faucetVault); 3 a day per account (faucetAccount); 50 a day in all (faucetGlobal) |
Canary#
POST /api/canary/attempt is the public Canary challenge, which is Live on testnet. Mainnet has no Canary vault, so the route answers 503 not_ready there. A visitor's text goes to a deliberately gullible model, then through the SDK's process() as an invoice does: code is what the code rules and the vault decided. raw says whether a payment the code refused was also sent to the vault unfiltered, which happens only when the vault's free dry run says it would refuse it too. An attempt is stored as hashes and labels, never as the visitor's text, and a visitor is told apart by a keyed hash of the pb_canary cookie and of the IP address. 429 budget carries a Retry-After with the seconds until that limit is over.
| Route | Who may call | Body | Success | Errors | Rate limit |
|---|---|---|---|---|---|
POST /api/canary/attempt | Public, from a page at the same address | JSON { text, via }: text is at most 4000 characters, and via is optional | 200 with status: "done", attemptId, techniques, proposal, code and raw | 403 bad_origin, 503 not_ready, 429 rate_limited, 400 invalid_input, 400 empty, 400 too_long, 429 budget, 409 busy, 503 closed, 502 failed, 503 unavailable | 5 in 10 minutes per IP address (canaryModelVisitor); 300 an hour in all (canaryModelGlobal). Past those, 6 attempts an hour per IP address, 60 an hour and 600 a day in all, and 2,000,000 model tokens a UTC day (CANARY_LIMITS), each answered with 429 budget |
TEE check#
GET /api/tee/check is the live TEE check on a vault's proof page. The visitor's browser draws a nonce of 32 random bytes and sends it as ?nonce=, with no cookie and no Referer, and nothing the visitor typed or uploaded. A call that another site's page makes in a browser, which the browser marks in Sec-Fetch-Site, answers 403 bad_origin before the rate limit, so it cannot spend the counter all visitors share. The server asks NEAR AI Cloud's public attestation report, with no API key, for that nonce and NEARAI_MODEL, the model Payables is set to use, or, when it is unset, for the SDK's preferred private models in order until one answers. On the report's first four model attestations at most, it checks that report_data[32..64] and request_nonce are the nonce, that report_data[0..32] is the signing address, O1 (Intel DCAP on the TDX quote) and O2 (NVIDIA NRAS on the GPU evidence). attestations_total says how many the report holds, and a pass over fewer than that says so. A check that cannot run is unavailable, not pass or fail: NEAR AI does not answer within 12 seconds, Intel or NVIDIA does not answer within the check's 24 seconds, the verifier does not load, or, for O2, the report carries no GPU evidence. The answer carries the few values of the report the browser needs to check both bindings again with its own nonce, and the card shows the worse of the browser's verdict and the server's. The check reads the TEE that serves the model now, not any past decision, and the TEE measurement is not pinned. Its rate limit is one counter for all visitors together, so it stores nothing about a visitor.
| Route | Who may call | Body | Success | Errors | Rate limit |
|---|---|---|---|---|---|
GET /api/tee/check | Public | None. Query: nonce, 64 lowercase hex characters, which the visitor's browser draws | 200 with { nonce, model, checked_at, elapsed_ms, checks, attestations, attestations_total }. Each of the checks nonce, signer, o1 and o2 is pass, fail or unavailable, with a detail. Each attestation holds signing_address, request_nonce, report_data, tcb_status and gpu, and nothing else of NEAR AI's report. attestations_total is how many model attestations the report holds, of which the first four at most are checked. | 403 bad_origin, 400 invalid_input, 429 rate_limited, 503 unavailable | 30 a minute in all (teeCheck) |
Health#
The health routes are for uptime monitors. They answer ok and, where they give one, a detail the server writes itself, never a third party's own text, and they have no { error } codes. GET /api/health calls no other service: it says whether this deployment can read its settings. The RPC and NEAR AI Cloud probes make one call each, of 5 seconds at most.
| Route | Who may call | Body | Success | Errors | Rate limit |
|---|---|---|---|---|---|
GET /api/health | Public | None | 200 with { ok: true, service, network, commit, time } | None of the error codes. 503 with ok: false and detail: "settings" when the deployment cannot read its settings. | None |
GET /api/health/rpc | Public | None. Query: provider, a 0-based index into the network's RPC list, 0 by default | 200 with { name, ok, latency_ms, detail, network, provider, providers } when the provider answers for the right chain with a fresh block | None of the error codes. 503 with ok: false when the probe fails or the settings cannot be read, and 404 with ok: false for an index the list does not have. | None. Each probe's answer is kept 30 seconds per server instance |
GET /api/health/nearai | Public | None | 200 with { name, ok, latency_ms, detail, configured } when NEAR AI Cloud's public model list answers with at least one model. configured says whether this deployment holds the model key. | None of the error codes. 503 with ok: false when the model list does not answer with a model. | None. Each probe's answer is kept 30 seconds per server instance |
Scheduled jobs#
The scheduler calls these routes with Authorization: Bearer and the CRON_SECRET, and any other call gets 401 unauthorized. vercel.json runs /api/cron/watch at 00:00 UTC and /api/cron/metrics at 00:10 UTC each day. The watch reads what is new on each vault registered with the app, network by network, pings the heartbeat monitor when every network was healthy, then sends those vaults' alerts. The metrics run computes the usage numbers and prunes the rate limiter's old rows. Both cover every network the deployment serves, mainnet first, and answer with counts only.
| Route | Who may call | Body | Success | Errors | Rate limit |
|---|---|---|---|---|---|
GET /api/cron/watch | CRON_SECRET, sent as Authorization: Bearer and the secret | None | 200 with { ok: true, networks, heartbeat } | 401 unauthorized, 503 unavailable. A run where a network's watch was not healthy is a 503 with ok: false and the same fields. | None |
GET /api/cron/metrics | CRON_SECRET, sent as Authorization: Bearer and the secret | None | 200 with { ok: true, networks, prune } | 401 unauthorized, 503 unavailable. When a network's numbers fail, the 503 also carries prune. | None |
Telegram#
Telegram calls POST /api/telegram/webhook for each update to the alerts bot. An owner or an approver opens the bot link from the vault's alerts page, Telegram sends /start <code>, and the chat is linked to the vault. /stop ends every alert of the chat. A wrong secret is a 401 before anything is read. Past that check and the rate limit, the route answers 200, so Telegram does not resend an update the route has read. A 429, or a 503 when the key or the limiter's database fails, comes before the update is read: a status Telegram retries, so the update is not dropped.
| Route | Who may call | Body | Success | Errors | Rate limit |
|---|---|---|---|---|---|
POST /api/telegram/webhook | Telegram secret: the x-telegram-bot-api-secret-token header that setWebhook was given | A Telegram update. The route acts on /start <code> and /stop only. | 200 with { ok: true } for every update past the secret check and the rate limit, even one it cannot act on | 503 unavailable, 503 not_configured, 401 unauthorized, 429 rate_limited | 120 a minute in all (telegramWebhook) |
Alert links#
An alert email's confirmation link opens a page whose button posts the code to POST /api/alerts/confirm. The code is good once, for 24 hours. The unsubscribe link, from the email's List-Unsubscribe header or the stop page's button, posts to POST /api/alerts/stop. Both routes take a POST only, because mail scanners open links and opening one must change nothing. Neither checks the origin: the code or the token is the credential.
| Route | Who may call | Body | Success | Errors | Rate limit |
|---|---|---|---|---|---|
POST /api/alerts/confirm | Public: the confirmation code from the email is the credential | Form data: code, 32 hex characters | 303 to /console/<vault>/alerts?verified=1. A code that is malformed, used, expired or unknown goes back to /alerts/confirm. | 429 rate_limited, 503 unavailable | 30 a minute per IP address (alertLink) |
POST /api/alerts/stop | Public: the keyed token in the link is the credential | Query: s and t from the link. The stop page adds the form field from=page. | 200 with { ok: true }, or with from=page a 303 back to /alerts/stop with done=1. The answer is the same whether or not the subscription existed or was on. | 400 invalid_link, 503 unavailable. With from=page, a failure is a 303 with failed=1 instead of the 503. | None: a valid token is all it needs |
Proof files#
A vault's proof page offers three downloads, served under /proof/<vault>/ rather than /api. Each one answers for a vault whose owner made its proof page public, or for a signed-in member of the vault, on the network of the address it is called at. Anyone else gets 404 not_found, the same answer as for a vault nobody registered, so a file says nothing about who is a customer. A browser does not reuse an answer: a public vault's answer is sent with cache-control: private, max-age=0, must-revalidate, a member's with private, no-store. The three share the snapshot limit, and each counts its own calls.
snapshot.json and ledger.jsonl are the files Verify a vault yourself runs the verifier on. payments.csv is the same vault for a spreadsheet: one row per decision of the ledger, newest first, built by the code that builds the page's Decisions cards. The download takes its own snapshot, as snapshot.json does, so it can be older or newer than the page's: compare its Chain read column, or the time in the file name, with the page's "snapshot taken" line. The testnet demo vault's payments-2026-11-09.csv (opens in a new tab) is the file for its committed snapshot.
Its columns are Date (UTC), Mandate, Label, Payee, Amount (USDC), Decision, Reason, Spend id, Status now, Chain read (UTC), Spend transaction, Explorer link, Invoice SHA-256 and Ledger seq. The header row is the same in every file. Three of them in more detail:
- Payee is the destination of the spend the chain binds to the decision. It is empty when the chain holds no spend for it: nothing was sent. When a rule refused an invoice because it named another account than the mandate's payee, the Reason names both.
- Status now is each spend's status on chain when the snapshot read it, and Chain read (UTC) is that time, on every row. A spend whose payment entry says held can read paid there: an approver approved it on chain since.
- Spend transaction is the tx that sent the spend, as its payment entry records it, and Explorer link opens it. For a spend the contract held and an approver approved later, that tx created the hold and moved no USDC. The ledger does not record the approve tx, so the file does not have it.
A spreadsheet may read a cell that starts with =, +, -, @, a tab or a carriage return as a formula. So the file puts an apostrophe before each of them that starts a cell, after any spaces or quotes: at the start of a field, and after a ; or a line break inside one, since Excel splits each line on ; in a locale whose list separator is the semicolon.
| Route | Who may call | Body | Success | Errors | Rate limit |
|---|---|---|---|---|---|
GET /proof/:vault/snapshot.json | Public, for a vault whose owner made its proof page public. Otherwise a signed-in member of the vault, read from the pb_session cookie; for anyone else the answer is 404 not_found, as for a vault nobody registered | None | 200 with a snapshot of the vault, its ledger and the chain data read for it, as application/json; charset=utf-8, saved as probarion-<vault>-snapshot.json: the file probarion verify --offline reads | 404 not_found, 429 rate_limited, 503 unavailable | 30 a minute per IP address (snapshot) |
GET /proof/:vault/ledger.jsonl | Public, for a vault whose owner made its proof page public. Otherwise a signed-in member of the vault, read from the pb_session cookie; for anyone else the answer is 404 not_found, as for a vault nobody registered | None | 200 with the vault's signed ledger, one entry per line, as application/x-ndjson; charset=utf-8, saved as probarion-<vault>-ledger.jsonl: the file --ledger reads. It reads the database only, never the chain. | 404 not_found, 429 rate_limited, 503 unavailable | 30 a minute per IP address (snapshot) |
GET /proof/:vault/payments.csv | Public, for a vault whose owner made its proof page public. Otherwise a signed-in member of the vault, read from the pb_session cookie; for anyone else the answer is 404 not_found, as for a vault nobody registered | None | 200 with one row per decision of the ledger, newest first, joined to its payment entry and to its spend's status on chain when the snapshot was taken, as text/csv; charset=utf-8 with a BOM and CRLF line ends, saved as <vault>-payments-<capture time>.csv | 404 not_found, 429 rate_limited, 503 unavailable | 30 a minute per IP address (snapshot) |
What these tables do not cover#
- An agent API or a webhook to your server. An agent pays through the SDK (SDK reference), or through the MCP server
@probarion/mcp, which is Live: it runs beside the agent and speaks MCP over stdio, so it adds no route here. Probarion calls no URL of yours: alerts go out by email and Telegram. - What the console's screens and the Browser extension page do besides the routes above. That runs as Next.js server actions under
apps/web/src/app/consoleandapps/web/src/app/extension, not as routes under/api. - The other public files outside
/api: the docs' own/docs/<page>.mdtwins,/llms.txt,/llms-full.txtand/docs/search-index.json.
Related pages#
- Errors: what each HTTP error code means and what to do next.
- Let an agent pay: the payment loop an agent runs, with no HTTP call to Probarion.
- Spend outcomes: how to read the
outcomeof a Payables cycle. - Security model: who holds which key, including the agent and ledger keys the console keeps sealed.
- What works today: what is Live, Preview and Planned.
- The sources:
apps/web/src/app/api(opens in a new tab),apps/web/src/lib/http.ts(opens in a new tab) andapps/web/src/lib/ratelimit.ts(opens in a new tab).
Built from 4ab41ca · reviewed
apps/web/src/app/api/auth/challenge/route.ts(opens in a new tab)apps/web/src/app/api/auth/verify/route.ts(opens in a new tab)apps/web/src/app/api/auth/logout/route.ts(opens in a new tab)apps/web/src/app/api/console/[vault]/invoices/route.ts(opens in a new tab)apps/web/src/app/api/ext/pair/route.ts(opens in a new tab)apps/web/src/app/api/ext/inbox/route.ts(opens in a new tab)apps/web/src/app/api/ext/revoke/route.ts(opens in a new tab)apps/web/src/app/api/ext/invoices/route.ts(opens in a new tab)apps/web/src/app/api/topup/quote/route.ts(opens in a new tab)apps/web/src/app/api/topup/status/route.ts(opens in a new tab)apps/web/src/app/api/topup/submit/route.ts(opens in a new tab)apps/web/src/app/api/topup/faucet/route.ts(opens in a new tab)apps/web/src/app/api/canary/attempt/route.ts(opens in a new tab)apps/web/src/app/api/health/route.ts(opens in a new tab)apps/web/src/app/api/health/rpc/route.ts(opens in a new tab)apps/web/src/app/api/health/nearai/route.ts(opens in a new tab)apps/web/src/app/api/cron/watch/route.ts(opens in a new tab)apps/web/src/app/api/cron/metrics/route.ts(opens in a new tab)apps/web/src/app/api/telegram/webhook/route.ts(opens in a new tab)apps/web/src/app/api/alerts/confirm/route.ts(opens in a new tab)apps/web/src/app/api/alerts/stop/route.ts(opens in a new tab)apps/web/src/lib/http.ts(opens in a new tab)apps/web/src/lib/ratelimit.ts(opens in a new tab)apps/web/src/lib/auth/guard.ts(opens in a new tab)apps/web/src/lib/auth/session.ts(opens in a new tab)apps/web/src/lib/auth/challenge.ts(opens in a new tab)apps/web/src/lib/auth/verify.ts(opens in a new tab)apps/web/src/lib/auth/unavailable.ts(opens in a new tab)apps/web/src/lib/ext/auth.ts(opens in a new tab)apps/web/src/lib/ext/token.ts(opens in a new tab)apps/web/src/lib/ext/inbox.ts(opens in a new tab)apps/web/src/lib/ext/store.ts(opens in a new tab)apps/web/src/lib/topup/gate.ts(opens in a new tab)apps/web/src/lib/topup/faucet.ts(opens in a new tab)apps/web/src/lib/topup/oneclick.ts(opens in a new tab)apps/web/src/lib/near/tx.ts(opens in a new tab)apps/web/src/lib/cron.ts(opens in a new tab)apps/web/src/lib/health.ts(opens in a new tab)apps/web/src/lib/health-server.ts(opens in a new tab)apps/web/src/lib/network.ts(opens in a new tab)apps/web/src/lib/site/network.ts(opens in a new tab)apps/web/src/lib/env.ts(opens in a new tab)apps/web/src/lib/db/tenants.ts(opens in a new tab)apps/web/src/lib/canary/limits.ts(opens in a new tab)apps/web/src/lib/canary/service.ts(opens in a new tab)apps/web/src/lib/payables/pipeline.ts(opens in a new tab)apps/web/src/lib/payables/limits.ts(opens in a new tab)apps/web/src/lib/payables/extract.ts(opens in a new tab)apps/web/src/lib/payables/upload.ts(opens in a new tab)apps/web/src/lib/payables/queue.ts(opens in a new tab)apps/web/src/lib/payables/queue-rules.ts(opens in a new tab)apps/web/src/lib/payables/store.ts(opens in a new tab)apps/web/src/lib/payables/confirm.ts(opens in a new tab)apps/web/src/lib/sdk/factory.ts(opens in a new tab)apps/web/src/lib/alerts/links.ts(opens in a new tab)apps/web/src/lib/alerts/stop.ts(opens in a new tab)apps/web/src/lib/alerts/telegram.ts(opens in a new tab)apps/web/src/lib/watcher/watch.ts(opens in a new tab)apps/web/src/lib/docs/errors.ts(opens in a new tab)apps/web/supabase/migrations/20261108000006_invoices.sql(opens in a new tab)apps/web/supabase/migrations/20261108000012_networks.sql(opens in a new tab)apps/web/supabase/migrations/20261108000013_extension_tokens.sql(opens in a new tab)apps/web/supabase/migrations/20261108000015_extension_queue.sql(opens in a new tab)apps/web/src/components/console/InvoiceUploadForm.tsx(opens in a new tab)apps/web/src/components/console/TopUpPanel.tsx(opens in a new tab)apps/web/src/components/console/TestUsdcFaucet.tsx(opens in a new tab)apps/web/src/components/wallet/WalletProvider.tsx(opens in a new tab)apps/web/src/app/alerts/confirm/page.tsx(opens in a new tab)apps/web/src/app/alerts/stop/page.tsx(opens in a new tab)apps/web/src/app/extension/page.tsx(opens in a new tab)apps/web/src/app/extension/actions.ts(opens in a new tab)apps/web/src/app/console/[vault]/payables/actions.ts(opens in a new tab)apps/web/src/app/console/[vault]/topup/actions.ts(opens in a new tab)apps/web/vercel.json(opens in a new tab)apps/web/src/app/docs-md/[[...slug]]/route.ts(opens in a new tab)apps/web/src/app/llms.txt/route.ts(opens in a new tab)apps/web/src/app/llms-full.txt/route.ts(opens in a new tab)apps/web/src/app/docs/search-index.json/route.ts(opens in a new tab)apps/web/src/app/proof/[vault]/snapshot.json/route.ts(opens in a new tab)apps/web/src/app/proof/[vault]/ledger.jsonl/route.ts(opens in a new tab)apps/web/src/app/proof/[vault]/payments.csv/route.ts(opens in a new tab)apps/web/src/lib/proof/service.ts(opens in a new tab)apps/web/src/lib/proof/snapshot-file.ts(opens in a new tab)apps/web/src/lib/proof/ledger-file.ts(opens in a new tab)apps/web/src/lib/proof/payments-csv.ts(opens in a new tab)apps/web/src/lib/proof/decisions.ts(opens in a new tab)docs/evidence/demo-vault/payments-2026-11-09.csv(opens in a new tab)apps/web/src/app/console/[vault]/alerts/actions.ts(opens in a new tab)apps/web/supabase/migrations/20261108000011_alerts.sql(opens in a new tab)docs/THREAT_MODEL.md(opens in a new tab)README.md(opens in a new tab)docs/evidence/trial-vault/README.md(opens in a new tab)docs/evidence/demo-vault/README.md(opens in a new tab)