DepΓ³sito recibido: pΙ± DepΓ³sito acreditado: pΙ± DepΓ³sito liquidado: pΙ±
API
Los formularios, OpenAPI y MCP2 son interfaces pares sobre un solo plano de control. Los enlaces siguientes exponen sus contratos de mΓ‘quina.
Overview
Custodial-Monero control plane for plurnk's optional hosted LLM inference.
The REST/JSON contract for plurnk.web β a Tor-first, no-JS-first commerce and account control plane for Plurnk's hosted inference service. HTML forms, this OpenAPI surface, and the MCP 2026-07-28 server at /mcp are peer adapters over one shared capability layer; none proxies through another. /v1 remains the separately purchased, OpenAI-compatible inference usage API, and metered search remains under /api/v1/search/*.
Getting started
A REST client, end to end:
- For an agentic purchase, create or sign into an account in the browser.
Password, TOTP, recovery, and authorization consent never enter MCP elicitation or tool arguments. The existing JSON auth endpoints remain available to deliberate REST consumers.
- Discover OAuth metadata, register a public client, and complete an
authorization-code flow with S256 PKCE for resource https://plurnk.ai/api/v1 and scope plurnk:control.
- Read
GET /api/v1/account, create a deposit intent, and manage usage keys
with the returned pct_ control token.
- A newly created plaintext
pk_<64-hex>usage key is returned exactly
once. Store it on the host that will call /v1 or metered /search.
- Fund: see "The money flow". Deposits are prepaid, spend-only credits.
- Inference: the hosted service meters
/v1usage against thatpk_key and
debits the account balance.
Auth
Three credentials have deliberately disjoint authority:
Cookie: pk.sidβ same-origin browser session. It drives forms and is the
only credential accepted by password, TOTP, recovery-code, session, security-activity, account-deletion, and operator surfaces.
Authorization: Bearer pct_<opaque>β one-hour OAuth 2.1 control token,
bound to either the /api/v1 or /mcp resource and scope plurnk:control. It can read account commerce state and manage pk_ usage keys. A token for one resource is rejected by the other.
Authorization: Bearer pk_<64-hex>β usage key accepted only by/v1
inference and metered /api/v1/search/*. It cannot read or mutate the account, mint/revoke keys, authorize MCP, or subscribe to account events.
Key lifecycle
A pk_ key has no account-control scopes. It carries usage authority only: inference plus the independently toggleable Search capability. There is no way to promote it into a session or OAuth principal.
The controls are lifecycle, not privilege:
- **Expiry** β optional
expires_in_daysmakes the key stop authenticating
after that window.
- **Disable** β a key can be disabled (a kill-switch short of deletion) and
re-enabled; a disabled key doesn't authenticate at all.
- **Revoke** β
DELETE /api/v1/auth/keys/{id}retires it for good.
Sensitive account mutations require a browser session and their documented step-up proof. OAuth control tokens and usage keys are categorically refused.
The money flow
Custodial piconero, double-entry ledger. Amounts are strings of piconero (1 XMR = 1e12 piconero) to stay exact past 2^53.
- Deposit:
POST /api/v1/account/deposit-intentleases a Monero subaddress
and can include an exact optional amount_xmr; its response carries the canonical monero: payment URI. The service cannot spend the caller's external wallet. The scanner credits an observed payment after confirmations (pending β credited β settled).
- Spend: the hosted service meters inference per key and debits your
balance (priced in piconero) β there is no public spend call; usage is reported by the plurnk endpoint and lands in the ledger.
- No withdrawals: deposits are final, prepaid, spend-only credits (Terms
Β§6). The hot wallet is watch-only β no spend key exists on this host, so no call can move funds out.
Event stream
Connect a WebSocket to /ws with either a same-origin browser session or an /api/v1-bound pct_ control token in the Authorization header. Query credentials and pk_ usage keys never authorize private events. Every connection gets a { "type": "hello" } greeting; an authenticated one then receives its OWN account's events as { type, at, data } JSON messages (amounts are decimal piconero strings):
deposit_creditedβ a chain deposit landed (data: amount_piconero,
target_kind/target_id, source_ref, block_height).
deposit_tierβ a deposit crossed a confirmation tier (data adds
tier: credited = spendable, settled = fully confirmed).
inference_debitβ metered inference spend debited the balance (data:
amount_piconero, source_ref β the usage report's id).
The type vocabulary is append-only, like error_key β tolerate unknown types. Delivery is pull-only and best-effort notification (a missed event changes nothing in the ledger; reconcile with the REST reads). There are no outbound webhooks β the platform makes no outbound product requests.
CORS
/api/*, OAuth discovery/token/registration, protected-resource metadata, /mcp, and this spec's own URLs answer any origin where the protocol needs it. Never credentials mode: session cookies stay same-origin; cross-origin control uses OAuth bearer tokens. HTML pages remain outside the embedding surface.
Versioning
Within /api/v1, changes are additive only β new endpoints, new optional fields, new values on the append-only vocabularies (error_key, the event types). Build clients that tolerate unknown fields and unknown enum values. Anything removed or reshaped ships as /api/v2 served alongside /v1 for an announced overlap window; retired identifiers are never reused.
Moderation
Operator bans come in tiers (ban_type): timed (locked out until expiry, content stays), permanent (locked out + content hidden from public reads), erasure (irreversible removal, == self-deletion). A ban revokes the user's sessions + keys immediately.
Content negotiation
/signup, /signin, /signin/recover, /signout are content-negotiated:
Content-Type: application/jsonβ JSON response- urlencoded form post β 303 redirect (success β destination; failure β back
to the form with ?e=ERROR_KEY)
Error shape
Every JSON error is { error_key: string, fields?: string[] }. The error_key is a stable machine-readable identifier (the full vocabulary is enumerated on the ErrorResponse schema below); the locale files provide human text. fields names the inputs at fault on validation errors.
Rate limiting
Report filing is per-IP-bucket rate-limited; when tripped, 429 with { error_key: "error_rate_limited" } and a Retry-After header. IPs are hashed with a daily-rotating salt (SPEC Β§2) β no raw IP is persisted.
Endpoints
auth
POST /api/v1/auth/signup
Create a new account
Creates the user, issues 8 single-use recovery codes (returned in the JSON path; flashed to session and retrieved separately in the form path), signs the new user in immediately.
Request body (required)
application/jsonSignupRequestapplication/x-www-form-urlencodedSignupRequest
Responses
200β Account created. JSON path returns the 8 single-use recovery codes inline so a scripted signup is a single round-trip; the same flash is consumed by the form path via the redirect β /signup/codes β /api/v1/auth/codes chain.application/jsonβSignupSuccessResponse303β Account created (form path) β redirect to /signup/codes400β Missing fields, mismatched passwords, or invalid usernameapplication/jsonβErrorResponse409β Username taken (or reserved)application/jsonβErrorResponse
POST /api/v1/auth/signin
Sign in to an existing account
Request body (required)
application/jsonSigninRequestapplication/x-www-form-urlencodedSigninRequest
Responses
200β Signed in (JSON path)application/jsonβOkResponse303β Signed in (form path) β redirect to /account400β Missing fieldsapplication/jsonβErrorResponse401β Wrong username or passwordapplication/jsonβErrorResponse403β Account bannedapplication/jsonβErrorResponse
POST /api/v1/auth/recover
Recover account using a single-use recovery code
Consumes one of the 8 recovery codes issued at signup; sets a new password and signs the user in. Code is single-use; remaining codes stay valid.
Request body (required)
application/jsonRecoverRequestapplication/x-www-form-urlencodedRecoverRequest
Responses
200β Recovered + signed inapplication/jsonβOkResponse303β Form-path success β redirect to /account400β Missing fields or mismatched passwordsapplication/jsonβErrorResponse401β Wrong username or codeapplication/jsonβErrorResponse403β Account bannedapplication/jsonβErrorResponse
POST /api/v1/auth/signin/totp
Complete signin with a TOTP code
Second step of a two-factor signin. POST /signin sets a partial session (pendingTotpUserId) when the user has TOTP enrolled; this endpoint takes the 6-digit code from the authenticator and promotes the partial session to a fully signed-in one. Recovery codes bypass this β /signin/recover clears totp_secret on success, so a user who lost their authenticator escapes via the recovery flow rather than this one.
Request body (required)
application/jsonSigninTotpRequestapplication/x-www-form-urlencodedSigninTotpRequest
Responses
200β Signed inapplication/jsonβOkResponse303β Signed in (form path) β 303 to /account400β Code missingapplication/jsonβErrorResponse401β No pending TOTP signin, or code invalidapplication/jsonβErrorResponse403β Account bannedapplication/jsonβErrorResponse
POST /api/v1/auth/signout
Destroy the current session
Security: cookieAuth
Responses
200β Signed out (JSON path)application/jsonβOkResponse303β Signed out (form path) β redirect to /
GET /api/v1/auth/codes
Retrieve the post-signup recovery codes (one-shot flash)
Returns the 8 recovery codes generated at signup, then clears them from the session. Subsequent calls return 404. Only useful in the narrow window between a successful /signup and the user closing the tab.
Security: cookieAuth
Responses
200β Codes availableapplication/jsonβCodesResponse404β No codes in the session flash (already retrieved, or never signed up in this session)application/jsonβErrorResponse
GET /api/v1/auth/keys
List the authenticated user's active keys
Lists active (un-revoked) keys with metadata only β the plaintext key is never re-derivable. Use DELETE to revoke. Paginated, newest first, 25 per page.
Security: cookieAuth or controlTokenAuth
Parameters
pageβ query, stringstring, pattern^[1-9][0-9]*$
Responses
200β A page of keysapplication/jsonβTokenListResponse401β Not signed inapplication/jsonβErrorResponse
POST /api/v1/auth/keys
Issue an inference/search usage key
Generates a pk_<64-hex> usage key bound to the authenticated user. The plaintext key is returned exactly once β in this response (JSON path), or in the one-shot flash on the keys page (form path). Only its sha256 hash is persisted. Pass the key only to /v1 inference or metered /api/v1/search/*; it is categorically rejected as account-control or MCP authentication.
Security: cookieAuth or controlTokenAuth
Request body
application/jsonKeyCreateRequestapplication/x-www-form-urlencodedKeyCreateFormRequest
Responses
201β Token issued (JSON path)application/jsonβKeyCreateResponse303β Form path β "/account/keys" β success renders the plaintext exactly once via a session flash; failure carries `?e=error_key_label_required` / `?e=error_key_expiry_invalid`.400β Missing label or invalid expiry (error_key_label_required / error_key_expiry_invalid)application/jsonβErrorResponse401β Not signed inapplication/jsonβErrorResponse
DELETE /api/v1/auth/keys/{id}
Revoke a usage key
Security: cookieAuth or controlTokenAuth
Parameters
idβ path, required, integerinteger
Responses
200β Token revokedapplication/jsonβOkResponse401β Not signed inapplication/jsonβErrorResponse404β No active key with that id belongs to the callerapplication/jsonβErrorResponse
POST /api/v1/auth/keys/{id}/delete
Revoke a usage key (no-JS form alias of DELETE)
Same effect as DELETE /api/v1/auth/keys/{id} β exposed as a POST so the no-JS keys page can revoke from a native form.
Security: cookieAuth or controlTokenAuth
Parameters
idβ path, required, integerinteger
Request body
application/jsonobjectapplication/x-www-form-urlencodedobject
Responses
200β Token revoked (JSON path)application/jsonβOkResponse303β Form path β "/account/keys" (failure carries ?e=error_not_found)401β Not signed inapplication/jsonβErrorResponse404β No active key with that id belongs to the callerapplication/jsonβErrorResponse
POST /api/v1/auth/keys/{id}/disable
Disable your own usage key (user axis)
Sets the USER disable axis on the caller's own key. A key is live only when all three axes (user/system/operator) are enabled, so this disables but cannot override a system/operator disable. Reversible via /enable.
Security: cookieAuth or controlTokenAuth
Parameters
idβ path, required, integerinteger
Request body
application/jsonobjectapplication/x-www-form-urlencodedobject
Responses
200β Token disabled (JSON path)application/jsonβOkResponse303β Form path β "/account/keys"401β Not signed inapplication/jsonβErrorResponse404β No active key with that id belongs to the callerapplication/jsonβErrorResponse
POST /api/v1/auth/keys/{id}/enable
Re-enable your own usage key (user axis)
Clears the USER disable axis on the caller's own key. The key becomes live only if the system and operator axes are also enabled.
Security: cookieAuth or controlTokenAuth
Parameters
idβ path, required, integerinteger
Request body
application/jsonobjectapplication/x-www-form-urlencodedobject
Responses
200β Token enabled (JSON path)application/jsonβOkResponse303β Form path β "/account/keys"401β Not signed inapplication/jsonβErrorResponse404β No active key with that id belongs to the callerapplication/jsonβErrorResponse
POST /api/v1/auth/keys/{id}/search/enable
Allow web search for your own key (Search capability)
Sets the Search capability flag on the caller's own key, permitting it to spend on the metered web-search path. Orthogonal to the disable axes and the money path - it never affects authentication or inference spend.
Security: cookieAuth or controlTokenAuth
Parameters
idβ path, required, integerinteger
Request body
application/jsonobjectapplication/x-www-form-urlencodedobject
Responses
200β Search allowed (JSON path)application/jsonβOkResponse303β Form path -> "/account/keys"401β Not signed inapplication/jsonβErrorResponse404β No active key with that id belongs to the callerapplication/jsonβErrorResponse
POST /api/v1/auth/keys/{id}/search/disable
Disallow web search for your own key (Search capability)
Clears the Search capability flag on the caller's own key. The key keeps inference usage authority; it just can't reach the metered search path. Reversible via /search/enable.
Security: cookieAuth or controlTokenAuth
Parameters
idβ path, required, integerinteger
Request body
application/jsonobjectapplication/x-www-form-urlencodedobject
Responses
200β Search disallowed (JSON path)application/jsonβOkResponse303β Form path -> "/account/keys"401β Not signed inapplication/jsonβErrorResponse404β No active key with that id belongs to the callerapplication/jsonβErrorResponse
POST /api/v1/search/authorize
Authorize-and-spend for one web search (metered)
A search-side filter pings this once per search with the user's usage key. Verifies the key is live and Search-enabled, then debits the endpoint-published search price from spendable balance (atomic, idempotent on request_id). Off (503) when the search rate lane is offline.
Security: usageKeyAuth
Request body (required)
application/jsonobjectapplication/x-www-form-urlencodedobject
Responses
200β Authorized and charged (or an idempotent replay)application/jsonβobject400β Missing request_idapplication/jsonβErrorResponse401β Invalid or inactive keyapplication/jsonβErrorResponse402β Insufficient spendable balanceapplication/jsonβErrorResponse403β Key lacks the Search capabilityapplication/jsonβErrorResponse503β Search rate lane offlineapplication/jsonβErrorResponse
POST /api/v1/search/void
Reverse a search charge the endpoint couldn't fulfil
Called when a charged search fails downstream (results undeliverable). Idempotently reverses the charge β credits the customer, debits the operator β scoped to the charge's owner. A re-void is a no-op (replayed).
Security: usageKeyAuth
Request body (required)
application/jsonobjectapplication/x-www-form-urlencodedobject
Responses
200β Reversed (or an idempotent replay)application/jsonβobject400β Missing request_idapplication/jsonβErrorResponse401β Invalid or inactive keyapplication/jsonβErrorResponse404β No matching charge for this accountapplication/jsonβErrorResponse
operator
POST /api/v1/account/operator/keys/{id}/disable
Operator-disable any user's usage key (operator axis)
Sets the OPERATOR disable axis on any user's key (abuse intervention). Gated by operator session; non-operators get 404.
Security: cookieAuth
Parameters
idβ path, required, integerinteger
Request body
application/jsonobjectapplication/x-www-form-urlencodedobject
Responses
200β Token operator-disabledapplication/jsonβOkResponse404β Not an operator, or no active key with that idapplication/jsonβErrorResponse
POST /api/v1/account/operator/keys/{id}/enable
Operator-enable any user's usage key (operator axis)
Clears the OPERATOR disable axis on any user's key. Gated by operator session; non-operators get 404.
Security: cookieAuth
Parameters
idβ path, required, integerinteger
Request body
application/jsonobjectapplication/x-www-form-urlencodedobject
Responses
200β Token operator-enabledapplication/jsonβOkResponse404β Not an operator, or no active key with that idapplication/jsonβErrorResponse
GET /api/v1/account/operator/reports
The content-report triage queue (operator)
Open reports (SPEC Β§7), priority-ordered β subjects with more open reports float to the top, ties break newest-first. Each row carries the repeat-count signal. Paginated.
Security: cookieAuth
Parameters
pageβ query, stringstring
Responses
200β Open report queueapplication/jsonβobject404β Not an operatorapplication/jsonβErrorResponse
GET /api/v1/account/operator/reports/{id}
A single report with evidence + pattern stats (operator)
Security: cookieAuth
Parameters
idβ path, required, stringstring
Responses
200β The report, its evidence snapshot, and subject-pattern statsapplication/jsonβobject404β Not found / not operatorapplication/jsonβErrorResponse
POST /api/v1/account/operator/reports/{id}/resolve
Resolve a report β record the decision, enforce the side-effect (operator)
Records the operator's triage decision on the report and applies its side-effect: dismiss/warn record only; ban bans the reported user. Enforcement runs before the close, so a self-ban or a decision that can't apply to the subject aborts without resolving. Idempotent β a second resolve 409s.
Security: cookieAuth
Parameters
idβ path, required, stringstring
Request body (required)
application/jsonResolveReportRequestapplication/x-www-form-urlencodedResolveReportRequest
Responses
200β Resolvedapplication/jsonβOkResponse303β Form path: 303 to /account/operator/reports400β Invalid decision / reason missing or too longapplication/jsonβErrorResponse404β Not found / not operatorapplication/jsonβErrorResponse409β Already resolved / cannot ban selfapplication/jsonβErrorResponse
GET /api/v1/account/operator/support/tickets
List support tickets (operator)
Security: cookieAuth
Parameters
statusβ query, stringstringcategoryβ query, stringstringpageβ query, stringstring
Responses
200β Ticket listapplication/jsonβobject404β Not an operatorapplication/jsonβErrorResponse
GET /api/v1/account/operator/support/tickets/{id}
A single support ticket (operator)
Security: cookieAuth
Parameters
idβ path, required, stringstring
Responses
200β The ticketapplication/jsonβobject404β Not found / not operatorapplication/jsonβErrorResponse
POST /api/v1/account/operator/support/tickets/{id}/resolve
Resolve a support ticket (operator)
Security: cookieAuth
Parameters
idβ path, required, stringstring
Request body
application/jsonResolveTicketRequestapplication/x-www-form-urlencodedResolveTicketRequest
Responses
200β Resolvedapplication/jsonβOkResponse303β Form path: 303 to /account/operator/tickets400β Notes missing/too longapplication/jsonβErrorResponse404β Not found / not operatorapplication/jsonβErrorResponse409β Already resolvedapplication/jsonβErrorResponse
GET /api/v1/account/operator/moderation/actions
The moderation audit log (operator)
Security: cookieAuth
Parameters
target_kindβ query, stringstringtarget_idβ query, stringstringpageβ query, stringstring
Responses
200β Recent moderation actionsapplication/jsonβobject404β Not an operatorapplication/jsonβErrorResponse
POST /api/v1/account/operator/moderate
No-JS moderation console dispatch (ban/unban by body)
One body-keyed endpoint behind the operator moderation console's no-JS forms. The REST routes put the target user in the path, which a native form can't fill from a typed input; this dispatches on action (ban/unban by target_username) to the same lookup, mutation, and audit the REST routes use. JSON clients should prefer the REST routes; this exists for the no-JS console.
Security: cookieAuth
Request body (required)
application/jsonModerateRequestapplication/x-www-form-urlencodedModerateRequest
Responses
200β Action applied (JSON path)application/jsonβOkResponse303β Form path: 303 to /account/operator/moderation400β Invalid action/kind or reason missingapplication/jsonβErrorResponse404β Target not found / not operatorapplication/jsonβErrorResponse409β Already in the requested state, or self-banapplication/jsonβErrorResponse
POST /api/v1/account/operator/users/{user}/ban
Ban a user account (moderation)
Security: cookieAuth
Parameters
userβ path, required, stringstring
Request body
application/jsonModerationActionRequestapplication/x-www-form-urlencodedModerationActionRequest
Responses
200β Bannedapplication/jsonβOkResponse303β Form path: 303 redirect400β Reason requiredapplication/jsonβErrorResponse404β Not found / not operatorapplication/jsonβErrorResponse409β Already banned, or banning yourselfapplication/jsonβErrorResponse
POST /api/v1/account/operator/users/{user}/unban
Unban a user account (moderation)
Security: cookieAuth
Parameters
userβ path, required, stringstring
Request body
application/jsonModerationActionRequestapplication/x-www-form-urlencodedModerationActionRequest
Responses
200β Unbannedapplication/jsonβOkResponse303β Form path: 303 redirect400β Reason requiredapplication/jsonβErrorResponse404β Not found / not operatorapplication/jsonβErrorResponse409β Not bannedapplication/jsonβErrorResponse
POST /api/v1/account/operator/janitor/sweep
Run the 30-day account janitor sweep (Β§spec/account-lifecycle)
Sweeps accounts older than the age window whose lifetime balance high-water never reached the reserve minimum (and that aren't the operator): each account's dust is forfeited to the house as an adjustment (conservation preserved) and its username is freed. This is the invocable surface β plurnk-build owns the schedule and calls this on its own timer. Idempotent in effect (swept accounts are tombstoned and drop out of the candidate set). No body.
Security: cookieAuth
Responses
200β Sweep complete β returns the swept accounts and forfeited amountsapplication/jsonβOkResponse303β Form path: 303 to /account/operator/janitor404β Not operatorapplication/jsonβErrorResponse
POST /api/v1/account/operator/deposits/{id}/credit
Credit an unattributed deposit to a user (Β§spec/account-lifecycle)
Attributes a deposit that arrived on our address outside any lease window to a username. Uses the scanner's own txid:addressIndex source_ref, so the chain-idempotency index makes a double-credit impossible even against a racing scan. Single-shot: a replay (or a row already resolved) yields 404. username must resolve to an existing account.
Security: cookieAuth
Parameters
idβ path, required, stringstring
Request body
application/jsonDepositCreditRequestapplication/x-www-form-urlencodedDepositCreditRequest
Responses
200β Creditedapplication/jsonβOkResponse303β Form path: 303 to /account/operator/deposits404β Not found / already resolved / unknown user / not operatorapplication/jsonβErrorResponse
POST /api/v1/account/operator/deposits/{id}/dismiss
Dismiss an unattributed deposit with a mandatory note (Β§spec/account-lifecycle)
Resolves an unattributed deposit without crediting anyone (e.g. returned out-of-band). The note is recorded on the resolution. Single-shot: a replay (or a row already resolved) yields 404.
Security: cookieAuth
Parameters
idβ path, required, stringstring
Request body
application/jsonModerationActionRequestapplication/x-www-form-urlencodedModerationActionRequest
Responses
200β Dismissedapplication/jsonβOkResponse303β Form path: 303 to /account/operator/deposits400β Note requiredapplication/jsonβErrorResponse404β Not found / already resolved / not operatorapplication/jsonβErrorResponse
account
GET /api/v1/account
The signed-in user's identity
Security: cookieAuth or controlTokenAuth
Responses
200β The user's public handleapplication/jsonβAccountResponse401β Not signed inapplication/jsonβErrorResponse
GET /api/v1/account/deposit
The current deposit surface β the ACTIVE lease (a pure read)
Reads the active deposit lease (or a legacy permanent address on pre-lease accounts). Never allocates β POST /api/v1/account/deposit-intent to lease an address. 404 when nothing is live.
Security: cookieAuth or controlTokenAuth
Parameters
amount_xmrβ queryOptional exact amount to encode in the returned Monero payment URI.
XmrAmount
Responses
200β The active lease (expires_at/reserved_until null on a legacy address)application/jsonβDepositAddressResponse401β Not signed inapplication/jsonβErrorResponse404β No active lease β POST /api/v1/account/deposit-intent firstapplication/jsonβErrorResponse
GET /api/v1/account/ledger
The signed-in user's ledger entries (account statement)
Paginated, newest first. Each entry's amount_piconero is signed β debits (inference spend) negative, credits (deposits) positive. tier is the confirmation status computed on read against the chain height (CRYPTO Β§5): chain deposits climb pending β credited β settled; internal moves are final; null when the daemon is unreachable. Operator fee rows are not the user's and never appear.
Security: cookieAuth or controlTokenAuth
Parameters
pageβ query, stringstring, pattern^[1-9][0-9]*$typeβ query, stringstringkeyβ query, stringstringfromβ query, stringstringtoβ query, stringstringsortβ query, stringstringdirβ query, stringstring
Responses
200β A page of ledger entriesapplication/jsonβLedgerResponse401β Not signed inapplication/jsonβErrorResponse
POST /api/v1/account/deposit-intent
Lease a deposit address (active ~1 hour, then retired)
Deposit addresses are LEASES, not permanent identities. The response's address is yours until expires_at (~1 hour) β pay within the window. A deposit broadcast inside the window is credited to you even if it mines later (a quiet reserve outlasts the mempool's lifetime). Do NOT save or reuse the address: after the reserve it recycles, and a payment to a stale address lands in an operator queue instead of your balance. Calling again inside the active window returns the SAME lease. An optional exact XMR amount changes only the returned payment URI; this call never spends an external wallet.
Security: cookieAuth or controlTokenAuth
Request body
application/jsonDepositIntentRequestapplication/x-www-form-urlencodedDepositIntentFormRequest
Responses
200β The active leaseapplication/jsonβDepositAddressResponse303β Form path redirects to `/account`, preserving a normalized non-secret amount in the query when present.400β amount_xmr is not positive exact XMR with at most 12 decimal placesapplication/jsonβErrorResponse401β Not signed inapplication/jsonβErrorResponse503β No address can be issued right now (pool empty + wallet unreachable) β try again shortlyapplication/jsonβErrorResponse
GET /api/v1/account/ledger/summary
Exact signed pΙ± nets grouped by key, day, or entry type
The reporting engine's summary: signed piconero nets (credits positive, debits negative β an inference settle refund nets against its reserve) grouped by group = key | day | type (default type), over the same filter set as the statement (type, key, from, to). entries counts collapsed statement lines β turns, not raw double-entry rows. "How much did Key X spend last week?" is ?group=key&key=X&from=...&to=... β one call, exact to the piconero.
Security: cookieAuth or controlTokenAuth
Parameters
groupβ query, stringstringtypeβ query, stringstringkeyβ query, stringstringfromβ query, stringstringtoβ query, stringstring
Responses
200β The grouped summaryapplication/jsonβLedgerSummaryResponse401β Not signed inapplication/jsonβErrorResponse
GET /api/v1/account/turns/{holdId}
Analyze one metered turn β the charge record + the turn content (Β§spec/inference-link)
The Analyze view behind every inference line on the statement (its hold_id). hold is web's OWN ledger record for the turn β the authoritative money facts; the endpoint's copy of the charge is never served. turn is the content (messages in, emission out, usage), fetched on view from the endpoint over the private bank link; it is null when that link is down or the turn has aged out of the endpoint's ~30-day hot content window β the charge facts above it are permanent. Ownership is an existence-leak 404: another user's hold id reads as not-found.
Security: cookieAuth or controlTokenAuth
Parameters
holdIdβ path, required, stringstring, pattern^[1-9][0-9]*$
Responses
200β The turn viewapplication/jsonβTurnViewResponse401β Not signed inapplication/jsonβErrorResponse404β Unknown hold / not yoursapplication/jsonβErrorResponse
POST /api/v1/account/password
Change the signed-in user's password
Requires current-password reauth + new password + confirmation. On success, every other session for this user is terminated; the caller's own session is preserved so they aren't signed out from the device they're rotating from. Usage keys are NOT automatically revoked β they're managed independently via /api/v1/auth/keys.
Security: cookieAuth
Request body (required)
application/jsonChangePasswordRequestapplication/x-www-form-urlencodedChangePasswordRequest
Responses
200β Password changed (JSON path)application/jsonβOkResponse303β Password changed (form path) β redirect to /account/security400β Missing or mismatched fieldsapplication/jsonβErrorResponse401β Not signed in, or current-password reauth failedapplication/jsonβErrorResponse429β Reauth throttled β too many password attempts for this account (Retry-After set; SECURITY Β§6)application/jsonβErrorResponse
GET /api/v1/account/sessions
List active sessions for the signed-in user
Returns one entry per non-expired session bound to this user. Each carries a short fingerprint (sha256 of the sid, truncated) for display and as the revoke target. The full sid is never exposed to the client. current: true marks the session this request rode in on.
Security: cookieAuth
Responses
200β Session listapplication/jsonβSessionListResponse401β Not signed inapplication/jsonβErrorResponse
POST /api/v1/account/sessions/revoke-others
Sign out everywhere else (revoke all sessions except the caller's)
Terminates every session for this user EXCEPT the caller's own β they aren't signed out from the device they're managing their security on.
Security: cookieAuth
Request body
application/jsonobjectapplication/x-www-form-urlencodedobject
Responses
200β Others revoked (JSON path)application/jsonβOkResponse303β Others revoked (form path) β 303 to "/account/security/sessions"401β Not signed inapplication/jsonβErrorResponse
POST /api/v1/account/sessions/{fingerprint}/delete
Revoke a session by fingerprint (no-JS form alias of DELETE)
Same effect as DELETE /api/v1/account/sessions/{fingerprint} β exposed as a POST so the no-JS sessions page can revoke from a native form. Revoking the caller's own current session destroys the cookie and (form path) redirects to "/".
Security: cookieAuth
Parameters
fingerprintβ path, required, stringstring
Request body
application/jsonobjectapplication/x-www-form-urlencodedobject
Responses
200β Revoked (JSON path)application/jsonβSessionRevokeResponse303β Revoked (form path) β 303 to "/account/security/sessions" (or "/" on self-revoke)401β Not signed inapplication/jsonβErrorResponse404β No matching session for the callerapplication/jsonβErrorResponse
DELETE /api/v1/account/sessions/{fingerprint}
Revoke a specific session by fingerprint
Deletes the session identified by fingerprint. The user_id guard on the server-side query prevents cross-user revocation. If the user revokes their own current session, the response carries self: true and the session cookie is destroyed in the same response β the page redirects to /signin.
Security: cookieAuth
Parameters
fingerprintβ path, required, stringstring
Responses
200β Revokedapplication/jsonβSessionRevokeResponse401β Not signed inapplication/jsonβErrorResponse404β No matching session for the callerapplication/jsonβErrorResponse
GET /api/v1/account/activity
User-facing security activity log (paginated, newest first)
Lists auth-lifecycle events on this account so the user can notice unauthorized changes. Each event has a stable event_type enum, a UTC timestamp, a transport ("clearnet" | "onion"), and an optional detail blob with event-specific JSON (e.g. key name on key_issued). Per SPEC Β§2 no raw IP or User-Agent is ever logged. Paginated, newest first, 25 per page.
Security: cookieAuth
Parameters
pageβ query, stringstring, pattern^[1-9][0-9]*$
Responses
200β A page of the activity logapplication/jsonβAuthActivityResponse401β Not signed inapplication/jsonβErrorResponse
POST /api/v1/account/delete
Delete the signed-in account
Deletes the signed-in account after password reauth. Any remaining balance is FORFEITED to the house per the Terms (an adjustment pair β the response reports the exact forfeited_piconero; CRYPTO Β§9.5). Deletion is a tombstone (SPEC Β§3): the public identity is severed and the user's content is removed from the live site, while the rows stay intact internally. Sessions, OAuth control credentials, usage keys, and recovery codes are dropped and the live session is destroyed in the same response.
Security: cookieAuth
Request body (required)
application/jsonDeleteAccountRequestapplication/x-www-form-urlencodedDeleteAccountRequest
Responses
200β Account deleted (JSON path)application/jsonβOkResponse303β Account deleted (form path) β 303 to "/"400β Password missingapplication/jsonβErrorResponse401β Not signed in, or password reauth failedapplication/jsonβErrorResponse429β Reauth throttled β too many password attempts for this account (Retry-After set; SECURITY Β§6)application/jsonβErrorResponse
POST /api/v1/account/totp/enroll
Begin TOTP enrollment (step 1 of 2)
Generates a fresh base32 secret and returns it along with the otpauth:// URI an authenticator app reads. The secret is stashed in the session as pendingTotpSecret β it is NOT written to the user row until the follow-up confirm call succeeds, so an abandoned enrollment never strands the user.
Security: cookieAuth
Request body (required)
application/jsonTotpEnrollRequestapplication/x-www-form-urlencodedTotpEnrollRequest
Responses
200β Enrollment started (JSON path)application/jsonβTotpEnrollResponse303β Enrollment started (form path) β 303 to "/account/security" (pending state renders the secret)400β Password missingapplication/jsonβErrorResponse401β Not signed in, or password reauth failedapplication/jsonβErrorResponse409β Already enrolledapplication/jsonβErrorResponse429β Reauth throttled β too many password attempts for this account (Retry-After set; SECURITY Β§6)application/jsonβErrorResponse
POST /api/v1/account/totp/confirm
Confirm TOTP enrollment (step 2 of 2)
Verifies the code generated by the user's authenticator against the pending session secret. On success, promotes the pending secret to users.totp_secret and clears the pending slot.
Security: cookieAuth
Request body (required)
application/jsonTotpCodeRequestapplication/x-www-form-urlencodedTotpCodeRequest
Responses
200β Enrollment confirmed (JSON path)application/jsonβOkResponse303β Enrollment confirmed (form path) β 303 to "/account/security" (active state)400β Code missingapplication/jsonβErrorResponse401β Not signed in, or code invalidapplication/jsonβErrorResponse409β No pending enrollment (run /enroll first)application/jsonβErrorResponse
DELETE /api/v1/account/totp
Disable TOTP for the signed-in account
Requires BOTH password AND a current TOTP code. Disabling 2FA from an active session is the attacker's preferred next step; gating on the live authenticator ensures the attacker would need both the password and the device.
Security: cookieAuth
Request body (required)
application/jsonTotpDisableRequest
Responses
200β Disabledapplication/jsonβOkResponse400β Password or code missingapplication/jsonβErrorResponse401β Not signed in, password reauth failed, or code invalidapplication/jsonβErrorResponse409β Not currently enrolledapplication/jsonβErrorResponse429β Reauth throttled β too many password attempts for this account (Retry-After set; SECURITY Β§6)application/jsonβErrorResponse
POST /api/v1/account/totp/disable
Disable TOTP (no-JS form alias of DELETE /api/v1/account/totp)
Same effect and same dual-credential gate (password AND a current TOTP code) as DELETE /api/v1/account/totp β exposed as a POST so the no-JS security page can reach it from a native form. JSON clients should prefer the DELETE verb.
Security: cookieAuth
Request body (required)
application/jsonTotpDisableRequestapplication/x-www-form-urlencodedTotpDisableRequest
Responses
200β Disabled (JSON path)application/jsonβOkResponse303β Disabled (form path) β 303 to "/account/security" (initial state)400β Password or code missingapplication/jsonβErrorResponse401β Not signed in, password reauth failed, or code invalidapplication/jsonβErrorResponse409β Not currently enrolledapplication/jsonβErrorResponse429β Reauth throttled β too many password attempts for this account (Retry-After set; SECURITY Β§6)application/jsonβErrorResponse
health
GET /api/v1/rates
The live pricing table as a plain GET (Β§spec/event-stream)
The same snapshot the /ws rates frame pushes β one entry per SERVICE (endpoint configuration) with the four token-category prices in exact piconero, plus marketplace metrics (context_tokens, tq, conformance_pct, latency_p50/p95_ms) when the endpoint has measured them. Agents poll this; browsers get the push twin. Fail-closed: 503 when no live snapshot exists β never a stale or fabricated price.
Responses
200β The live snapshotapplication/jsonβobject503β No live snapshot (endpoint link down or no valid rates yet)application/jsonβErrorResponse
GET /api/v1/health
Public liveness probe β server + db only
Responses
200β Liveness. Operational detail (wallet/daemon/pool/read-only state) is operator-only β see /api/v1/account/operator/health.application/jsonβHealthResponse
GET /api/v1/account/operator/health
Operator-only operational detail (wallet, daemon, pool depth, read-only state)
Behind operator auth. Carries internal state deliberately kept off the public probe (e.g. pool depth an attacker might try to drain). Non-operators get 404 (existence-leak posture).
Security: cookieAuth
Responses
200β Full subsystem detail.application/jsonβOperatorHealthResponse404β Not an operator (existence-leak posture).application/jsonβErrorResponse
reports
POST /api/v1/reports
Report a user account (anonymous-capable; rate-limited)
Files a conduct report against a user account (SPEC Β§7). The server resolves the subject, captures an immutable evidence snapshot, and records the reporter context (salted-IP bucket β never the raw IP β transport, and the account id when signed in). Per-bucket rate-limited. Reporter identity is never exposed to the reported party.
Security: cookieAuth
Request body (required)
application/jsonReportRequestapplication/x-www-form-urlencodedReportRequest
Responses
201β Report filed (JSON path) β returns the report idapplication/jsonβobject303β Form path: 303 back to the subject with ?reported=1400β Invalid subject or category, or notes too longapplication/jsonβErrorResponse404β The reported subject does not existapplication/jsonβErrorResponse
support
POST /api/v1/support/tickets
Submit a support ticket (anonymous-capable; rate-limited)
Request body
application/jsonSupportTicketRequestapplication/x-www-form-urlencodedSupportTicketRequest
Responses
200β Submittedapplication/jsonβOkResponse303β Form path: 303 to /contact?submitted=1400β Validation failedapplication/jsonβErrorResponse
Authentication
cookieAuth
- Type
apiKey- In
cookie- Name
pk.sid
HttpOnly session cookie issued by /signup / /signin / /signin/recover. Browser path.
controlTokenAuth
- Type
oauth2
OAuth 2.1 authorization-code token with S256 PKCE, scope plurnk:control, and exact resource audience /api/v1. Control tokens begin pct_, expire after one hour, and are not usage credentials.
usageKeyAuth
- Type
http- Scheme
bearer- Bearer format
pk_<hex>
Usage key issued by POST /api/v1/auth/keys. Pass as Authorization: Bearer pk_<64-hex> only to /v1 inference or /api/v1/search/*. It has no account-control, MCP, session, or event authority. It may have an optional expiry and can be disabled or revoked.
Schemas
DepositCreditRequest
Credit an unattributed deposit to a user. username is NOT marked required here β the handler owns the check so an unknown/blank user returns the existence-leak 404, not a generic validation error.
usernameβ string
ModerationActionRequest
Operator moderation action. reason is mandatory for the audit log but is NOT marked required here β the handler owns that check so it returns the specific error_moderation_reason_required, not a generic validation error. related_ticket_id optionally links the action to a support ticket.
reasonβ stringrelated_ticket_idβ string, pattern^[1-9][0-9]*$ban_typeβ stringOn the ban route: timed | permanent | erasure (SECURITY Β§6.5). The handler validates β error_ban_type_invalid.
ban_durationβ stringOn a timed ban: a duration modifier from PLURNK_WEB_BAN_DURATIONS.
ModerateRequest
No-JS console dispatch body. action is ban | unban (with target_username). The business-required fields are NOT marked required β the handler owns validation so it returns specific error_keys. Numeric patterns tolerate an empty value so a blank optional form field doesn't trip the validator.
actionβ stringOne of:
ban,unbantarget_usernameβ stringban_typeβ stringOn action=ban: timed (lockout, auto-expires) | permanent (lockout) | erasure (tombstone, irreversible). SECURITY Β§6.5. The handler validates β error_ban_type_invalid.
ban_durationβ stringOn a timed ban: a SQLite duration modifier from PLURNK_WEB_BAN_DURATIONS (e.g. '7 days'). Ignored for permanent/erasure.
reasonβ stringrelated_ticket_idβ string, pattern^([1-9][0-9]*)?$
SupportTicketRequest
A support ticket submission. The handler validates category + body.
categoryβ stringbodyβ stringreply_tokenβ string
ReportRequest
A content report (SPEC Β§7). The handler owns validation so it returns the specific error_keys (invalid subject/category, notes too long). The evidence snapshot, content hash, URL, and reporter context are captured server-side β never supplied by the client.
subject_kindβ stringuser (the only supported subject; handler-validated).
subject_idβ stringThe subject's id (form values are strings; handler-validated).
categoryβ stringEnum: out_of_scope | spam | harassment | impersonation | copyright | payment_fraud | other (handler-validated).
notesβ stringFree-form reporter note (handler caps at 4000 chars).
ResolveTicketRequest
Operator ticket resolution. The handler owns the resolution-notes checks.
resolution_notesβ string
ResolveReportRequest
Operator report resolution (SPEC Β§7). The handler owns validation so it returns the specific error_keys; resolution is left loose here (not an enum) so an unsupported value yields error_report_resolution_invalid rather than the generic validator error.
resolutionβ stringOne of: dismiss | warn | ban (handler-validated).
reasonβ stringFree-form reason
OkResponse
okβ required, boolean
SignupSuccessResponse
JSON-path signup response with the 8 single-use recovery codes inline. The codes flash is consumed in this response; subsequent calls to /api/v1/auth/codes return 404.
okβ required, booleancodesβ required, arrayEight 16-character hex single-use recovery codes.
Items:
string, pattern^[0-9a-f]{16}$keyβ objectPresent only when the request carried issue_key true β the first usage key, shown exactly once.
ErrorResponse
The shape of every JSON error: { error_key, fields? }. error_key is a stable identifier you can branch on; fields (validation errors) names the offending inputs. The enum below is the exhaustive list; the families a consumer hits most:
Auth & session
error_signin_requiredβ not authenticated (401).error_signin_bad_creds/error_password_incorrectβ wrong credentials.error_totp_required/error_totp_code_required/error_totp_invalidβ 2FA step.error_account_bannedβ the account is banned (permanent, or timed not yet expired).error_rate_limitedβ per-IP bucket limit tripped (429;Retry-After).
Keys
error_key_label_required/error_key_expiry_invalidβ bad key-creation input.
Validation
error_validation_failedβ schema validation failed (fieldsnames them).error_not_foundβ the target doesn't exist (or you can't see it β no leak).
Operator
error_already_banned/error_not_banned/error_cannot_ban_self,
error_ban_type_invalid / error_ban_duration_invalid β ban actions.
error_keyβ required, stringStable machine-readable identifier. The complete vocabulary (and English text) lives in
src/locales/en.json.One of:
error_unknown,error_not_found,error_validation_failed,error_signin_required,error_key_label_required,error_key_expiry_invalid,error_signin_missing_fields,error_signin_bad_creds,error_signup_missing_fields,error_signup_username_invalid,error_signup_username_taken,error_terms_required,error_american_required,error_password_mismatch,error_recover_missing_fields,error_recover_bad_code,error_account_banned,error_codes_unavailable,error_password_required,error_password_incorrect,error_deposit_address_unavailable,error_deposit_amount_invalid,error_rates_unavailable,error_totp_required,error_totp_code_required,error_totp_invalid,error_totp_already_enrolled,error_totp_not_enrolled,error_totp_no_pending,error_rate_limited,error_csrf,error_support_category_invalid,error_support_body_required,error_support_body_too_long,error_support_reply_token_too_long,error_support_resolution_notes_required,error_support_resolution_notes_too_long,error_report_subject_invalid,error_report_category_invalid,error_report_notes_too_long,error_report_resolution_invalid,error_report_resolution_reason_required,error_report_resolution_reason_too_long,error_report_already_resolved,error_support_already_resolved,error_moderation_reason_required,error_moderation_reason_too_long,error_already_banned,error_not_banned,error_cannot_ban_self,error_ban_type_invalid,error_ban_duration_invalid,error_payment_required,error_search_unavailable,error_search_not_enabled,error_search_charge_not_found,error_inference_cap_exceeded,error_invalid_key,error_amount_requiredfieldsβ arrayFor validation errors, the input names at fault.
Items:
string
SlugResponse
slugβ required, string
SignupRequest
usernameβ required, string, pattern^[A-Za-z0-9_-]{1,32}$issue_keyβ booleanJSON path only β also mint a first usage key inline (returned once as
key), so a deliberate REST client can go zeroβusage in one call. The HTML form path never sends this.accept_termsAffirmative Terms-of-Service + Privacy-Policy acceptance β REQUIRED to create an account (enforced by the handler with error_terms_required so it owns the error_key; not schema-required). JSON clients send true; the HTML form sends "on".
americanAffirmative jurisdiction-eligibility representation (the service is only available to adult American citizens subject to American jurisdiction) β REQUIRED to create an account (handler- enforced with error_american_required). JSON clients send true; the HTML form sends "on".
passwordβ required, string, formatpasswordpassword_confirmβ required, string, formatpassword
SigninRequest
usernameβ required, stringpasswordβ required, string, formatpassword
RecoverRequest
usernameβ required, stringcodeβ required, stringOne of the 8 single-use recovery codes issued at signup.
passwordβ required, string, formatpasswordpassword_confirmβ required, string, formatpassword
CodesResponse
codesβ required, arrayEight 16-character hex single-use recovery codes.
Items:
string, pattern^[0-9a-f]{16}$
AccountResponse
usernameβ required, stringhas_totpβ required, booleanTrue if the user has an active TOTP enrollment.
balanceβ object, nullableBalance in piconero (exact decimal strings, Β§13). null when the daemon is unreachable. spendable = internal funds + chain deposits at β₯ credited confs.
XmrAmount
Positive exact XMR amount with no more than 12 decimal places. The shared capability layer owns this semantic validation so every adapter returns error_deposit_amount_invalid rather than an adapter-specific schema error.
string
DepositIntentRequest
amount_xmr
DepositIntentFormRequest
amount_xmr_csrfβ required, stringSame-origin double-submit token rendered into the native form.
DepositAddressResponse
addressβ required, stringThe leased Monero subaddress for self-deposit.
expires_atβ required, string, nullableLease expiry (ISO-8601); null on a legacy permanent address.
reserved_untilβ required, string, nullableUntil when a slow-to-mine payment still credits you; null on legacy.
amount_xmrβ requiredNormalized requested amount, or null when the intent is amount-free.
payment_uriβ required, string, pattern^monero:Canonical wallet handoff URI; includes tx_amount exactly when amount_xmr is present.
TurnViewResponse
holdβ required, objectWeb's authoritative charge record for the turn (the ledger's hold).
LedgerResponse
entriesβ required, arrayItems:
objectpageβ required, integerpage_sizeβ required, integertotalβ required, integerpage_countβ required, integerfilterβ objectThe normalized filter/sort echoed back (server-whitelisted).
LedgerSummaryResponse
groupβ required, stringOne of:
key,day,typerowsβ required, arrayItems:
objectfilterβ required, object
ChangePasswordRequest
current_passwordβ required, string, formatpasswordpasswordβ required, string, formatpasswordpassword_confirmβ required, string, formatpassword
SessionSummary
fingerprintβ required, stringShort base64url-encoded sha256 of the sid (12 chars). Stable per session, non-reversible.
created_atβ required, stringlast_seenβ required, stringcurrentβ required, booleanTrue if this is the session the request rode in on.
SessionListResponse
sessionsβ required, arrayItems:
SessionSummary
AuthEvent
idβ required, integerevent_typeβ required, stringOne of:
signup,signin_password,signin_totp,signout,recover_used,password_changed,totp_enrolled,totp_disabled,key_issued,key_revoked,session_revoked,sessions_revoked_others,control_authorizedoccurred_atβ required, stringtransportβ required, stringOne of:
clearnet,onion,detailβ object, nullableEvent-specific JSON; shape varies per event_type.
AuthActivityResponse
eventsβ required, arrayItems:
AuthEventpageβ required, integerpage_sizeβ required, integertotalβ required, integerpage_countβ required, integer
SessionRevokeResponse
okβ required, booleanselfβ required, booleanTrue if the caller revoked their own current session. Page should redirect to /signin.
DeleteAccountRequest
passwordβ required, string, formatpasswordThe current account password. Required as a reauth even on an active session β destructive actions don't ride a stolen cookie.
SigninTotpRequest
codeβ required, string, pattern^[0-9]{6}$
TotpEnrollRequest
passwordβ required, string, formatpassword
TotpEnrollResponse
secretβ required, string, pattern^[A-Z2-7]+$Base32-encoded TOTP shared secret. Shown to the user once, here, for entry into an authenticator app. The server has already stashed this in the session as
pendingTotpSecretβ it is NOT written to the user row until /confirm succeeds.otpauth_urlβ required, stringotpauth://URI containing the secret + issuer + algorithm, for the authenticator app to enroll directly.
TotpCodeRequest
codeβ required, string, pattern^[0-9]{6}$
TotpDisableRequest
passwordβ required, string, formatpasswordcodeβ required, string, pattern^[0-9]{6}$
KeyCreateRequest
nameβ stringRequired human-readable label for the key.
expires_in_daysβ integerOptional hard expiry in days from now (positive integer). Omit for a key that never expires. The handler validates β error_key_expiry_invalid.
searchβ booleanWhether this usage key may also authorize metered web search.
KeyCreateFormRequest
nameβ stringRequired human-readable label for the key.
expires_in_daysβ stringOptional expiry in days (form values are strings). The handler validates β error_key_expiry_invalid.
searchβ stringOptional no-JS form representation of the Search capability.
One of:
on,off
KeyCreateResponse
idβ required, integerkeyβ required, string, pattern^pk_[0-9a-f]{64}$The plaintext key, returned exactly once. Pass it only to
/v1inference or metered/api/v1/search/*.nameβ required, stringcreated_atβ required, string
TokenSummary
idβ required, integerprefixβ required, stringnameβ required, stringexpires_atβ required, string, nullablecreated_atβ required, stringlast_used_atβ required, string, nullableuser_disabled_atβ required, string, nullablesystem_disabled_atβ required, string, nullableoperator_disabled_atβ required, string, nullablesearch_enabledβ required, integerOne of:
0,1
TokenListResponse
keysβ required, arrayItems:
TokenSummarypageβ required, integerpage_sizeβ required, integertotalβ required, integerpage_countβ required, integer
HealthResponse
serverβ required, stringStatus string for the express process
dbβ required, stringStatus string for sqlite
timestampβ stringISO-8601 probe time
OperatorHealthResponse
serverβ required, stringStatus string for the express process
dbβ required, stringStatus string for sqlite
daemonβ required, stringStatus string for monerod
walletβ required, stringStatus string for monero-wallet-rpc
writableβ required, booleanFalse = the platform is in maintenance read-only (PLURNK_WEB_FREEZE); every authenticated mutation returns 503.
read_only_reasonβ required, string,nullnull when writable; otherwise frozen | wallet_unreachable | daemon_unreachable | wallet_sync_behind (the latter three are reported for visibility but no longer gate globally).
pool_depthβ required, integerUnassigned subaddresses remaining in the pool (operator runway gauge).