Account API
Wallet sign-in, sessions, holder plans and payments. The account API lives at https://api.example.com/account; the wallet routes it relies on live at https://auth.example.com/auth.
Conventions
- Request bodies are JSON objects with
Content-Type: application/json. Unknown fields and trailing data answer400 invalid_request; bodies over 8 KiB answer413 request_too_large. - Routes marked session need
Authorization: Bearer sk-sv-user-…. A missing, unknown or expired token answers401 invalid_session. - Money amounts are exact decimal strings in USD. Times are RFC 3339 in UTC.
- Rate limits answer
429 rate_limitedwithRetry-Afterin seconds. They are counted per account or for the whole service, never per IP address. - Any route can answer
503 unavailablewhile a dependency is down. Retry later.
The user view
GET /account/me returns it, and so do sign-in responses (under user) and the routes that change an account.
{
"wallet": {
"holdingBps": 0,
"checkedAt": "2026-10-09T12:00:00Z"
},
"plan": null,
"balance": {
"creditUsd": "5",
"usedUsd": "0.0125",
"reservedUsd": "0",
"availableUsd": "4.9875"
},
"freeTokens": {
"resetsAt": "2026-10-10T00:00:00Z",
"models": []
},
"trial": {
"eligible": false,
"minSupplyBps": …,
"models": [
{
"model": "sevren-lite-27b",
"input": {"total": …, "used": 0, "remaining": 0},
"output": {"total": …, "used": 0, "remaining": 0}
},
…
]
},
"createdAt": "2026-10-09T00:00:00Z"
}| Field | Meaning |
|---|---|
wallet | Always present: {holdingBps, checkedAt} for the wallet the account signs in with. holdingBps is the wallet's share of the token's total supply in basis points (50 = 0.5%), and checkedAt is when it was last read: at the last wallet sign-in or plan refresh, in the background or with POST /account/plan/refresh. The address is never returned. |
plan | null, or {id, label, active}. A plan is active only while its last check is at most one hour old; an inactive plan gives no free tokens and no discount. Its free tokens per model are in freeTokens in GET /v1/catalog, and its discount, if it has one, is the tier's discountBps in GET /auth/wallet/config. |
balance | creditUsd added in total, usedUsd spent, reservedUsd held by requests that are running, and availableUsd = credit − used − reserved. |
freeTokens | Always present: the active plan's free tokens of the day. models lists each model the plan covers (empty without an active plan) with input (prompt) and output, each {total, used, remaining}: total per day, used what the wallet has used today, and remaining what is left now, which can be lower than total − used while requests hold part of it. resetsAt is the next 00:00 UTC. See Holder benefits. |
trial | Always present: the holder free try. minSupplyBps is the share of total supply it needs (the same as trial in GET /v1/catalog; 0 means none is offered). models lists every model with free-try tokens, with input and output as {total, used, remaining}: total is the model's trialTokens, used what the wallet has used ever, and remaining is 0 unless eligible. eligible is true when the wallet's last check, at most one hour old, reaches minSupplyBps. See Holder benefits. |
createdAt | When the account was created. |
Sign-in and sessions
Wallet sign-in returns a session. token is your API key; see Authentication.
{
"token": "sk-sv-user-…",
"expiresAt": "2026-10-23T12:00:00Z",
"user": { …the user view… }
}POST/account/login/wallet
AUTH: none
Signs in with a wallet and creates a new session; the first sign-in with a wallet creates its account. Body: {"assertion"}, an assertion from POST https://auth.example.com/auth/wallet/verify. The plan in the assertion replaces the stored one.
Returns 200 with a session, or 201 with "created": true when the wallet had no account yet. Errors: 401 invalid_assertion (invalid, expired or already used), 429 rate_limited (a service-wide budget), 503 wallet_unavailable (wallet sign-in is not enabled).
POST/account/logout
AUTH: session
Ends the session whose token is sent. No body. Returns 204.
GET/account/me
AUTH: session
Returns the user view.
curl https://api.example.com/account/me -H "Authorization: Bearer $SEVREN_API_KEY"Holder plans
POST/account/plan/refresh
AUTH: session
Reads the wallet's holding again and updates the plan and free-try eligibility. Plans are also refreshed in the background every few minutes. No body. Returns the user view.
Errors: 409 wallet_expired (the auth service no longer has the wallet: sign in with it again), 503 wallet_unavailable (plans are not enabled), 429 rate_limited (6 per hour per account).
Payments
GET/account/payments
AUTH: session
Everything the web app needs to add credit: the payment contract address, the account's payment reference and the accepted assets, plus the recent payments the backend has observed. See Payments for the full flow.
Returns {"enabled", "chainId", "contract", "ref", "blockTag", "confirmations", "explorerUrl", "minUsd", "assets": [...], "payments": [...]}. When payments are off: {"enabled": false, "payments": [...]} only. Each payment has id, asset, amount, creditedUsd, status ("credited" | "unmatched"), txHash, createdAt.
Credit is added by the payments watcher at latest minus confirmations blocks (currently 900, usually 1 to 2 minutes). Safe/finalized configurations omit confirmations and wait for that block tag instead. This endpoint never moves money.
Wallet routes on https://auth.example.com
These routes run in the auth service, which never receives prompts. They produce the assertions the account routes accept. The full flow is in Authentication.
GET/auth/wallet/config
AUTH: none
What a wallet message must contain and which holder plans exist. Each tier has the minSupplyBps share of total supply it needs and, when it has one, the discountBps taken off the input and output prices of every paid model for what an account on that tier pays from credit while its plan is active (omitted when 0). When plans.enabled is false, tiers is empty and there is no token.
{
"chainId": 4663,
"statement": "…",
"resourcePrefix": "urn:sevren:wallet:",
"maxMessageAgeSeconds": 300,
"plans": {
"enabled": true,
"token": "0x…",
"tiers": [{"id": "…", "label": "…", "minSupplyBps": …, "discountBps": …}]
}
}POST/auth/siwe/nonce
AUTH: none
No body. Returns {"nonce", "expiresAt"}. The nonce works once and expires after a few minutes. Errors: 429 rate_limited (a service-wide budget).
POST/auth/wallet/verify
AUTH: none
Body: {"message", "signature"}: the EIP-4361 message and its personal_sign signature. Returns {"assertion", "expiresAt", "purpose", "plan", "holdingBps"}; the assertion is valid for 5 minutes and works once.
Errors: 400 invalid_resource (not exactly one wallet resource, or not urn:sevren:wallet:login), 400 unsupported_signature (for example a wallet that is not deployed yet), 401 unauthorized (bad signature, domain, chain, statement or age, or a used nonce), 429 rate_limited, 503 unavailable (the chain could not be read).
