Docs / API
API reference
An OpenAI and OpenRouter compatible API: point any client at the base URL below with your Pontica key. Keys are issued by wallet signature, and the balance behind them is what the chain credited to your wallet.
Checked against this release on 22 Sep 2026
Base URL and auth#
- Base URL
https://pontica.gg/api/v1- Auth
Authorization: Bearer sk-or-v1-…, your Pontica key- Format
- JSON in, JSON out; server-sent events with
"stream": true - Money
- USD at the model's list price. 1 OBOL activated is $1 of balance.
- Browsers
- CORS is open to every origin; no cookies are read.
A Pontica key is an OpenRouter key that the gateway issued into Pontica's own OpenRouter workspace. The gateway accepts only the keys it issued: any other key gets 401 not_a_pontica_key.
- POST/api/v1/chat/completionsChat completions, streamed or not.
- GET/api/v1/modelsThe live OpenRouter catalogue, plus a
ponticafield. - GET/api/v1/models/countHow many models the catalogue lists.
- GET/api/v1/keyYour key's limit and usage, plus your Pontica balance.
- POST/api/v1/keysIssue the key of a wallet by signature. Retires its old key.
- POST/api/v1/keys/syncMove the key's limit to what the chain credited.
- GET/api/v1/keys/activityYour key's usage per day and model, last 30 days.
- GET/api/statusGateway, chain and catalogue state.
- GET/api/statsProtocol totals and the last 24 hours of events.
Quick start#
Put your key in the environment variable PONTICA_API_KEY (issue it on your account page), then call any model id from the catalogue. The model in these snippets comes from Pontica's picks.
curl https://pontica.gg/api/v1/chat/completions \
-H "Authorization: Bearer $PONTICA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "anthropic/claude-sonnet-5",
"messages": [{ "role": "user", "content": "Say hello in Greek." }]
}'import OpenAI from 'openai';
const client = new OpenAI({
baseURL: 'https://pontica.gg/api/v1',
apiKey: process.env.PONTICA_API_KEY,
});
const stream = await client.chat.completions.create({
model: 'anthropic/claude-sonnet-5',
messages: [{ role: 'user', content: 'Say hello in Greek.' }],
stream: true,
});
for await (const chunk of stream) {
process.stdout.write(chunk.choices[0]?.delta?.content ?? '');
}import os
from openai import OpenAI
client = OpenAI(
base_url="https://pontica.gg/api/v1",
api_key=os.environ["PONTICA_API_KEY"],
)
reply = client.chat.completions.create(
model="anthropic/claude-sonnet-5",
messages=[{"role": "user", "content": "Say hello in Greek."}],
)
print(reply.choices[0].message.content)Tools that speak the OpenAI API work the same way: set the base URL to https://pontica.gg/api/v1 and the key to your Pontica key.
Issue a key#
A wallet gets its key by signing a Sign-In with Ethereum (EIP-4361) message and posting it. The account page does this for you; here is the exact protocol for your own client.
1. Build the message
Byte for byte, with the lines joined by \n and no newline at the end:
pontica.gg wants you to sign in with your Ethereum account:
{address, EIP-55 checksum}
Issue a Pontica API key for this wallet. This retires any key it already has.
URI: https://pontica.gg
Version: 1
Chain ID: 4663
Nonce: {16 lowercase hex from crypto.getRandomValues}
Issued At: {UTC ISO 8601 with seconds and Z, e.g. 2026-09-22T09:30:00Z}
Expiration Time: {Issued At + 10 minutes, same format}
- The address line must be the EIP-55 checksum form of the address you post.
- The nonce is 16 lowercase hex characters, fresh for every message.
- Issued At may be at most 60 s ahead of the gateway's clock; Expiration Time at most 10 minutes after Issued At.
- The statement line decides the action. Only the two statements on this page are accepted.
2. Sign it
Sign with personal_sign (EIP-191) over the UTF-8 bytes of the message, sent to the wallet as hex. The prefix counts bytes, not characters. Signatures of ordinary accounts are recovered (v may be 27, 28, 0 or 1; high s values are refused). When recovery does not match and the address has code, the gateway asks the contract with EIP-1271 isValidSignature. EIP-7702 accounts pass by recovery. Smart wallets that are not deployed yet (ERC-6492) are not supported: deploy the wallet first.
A signed issue message works like a password until it is used or expires, 10 minutes at most. Never paste it anywhere.
3. Post it
POST https://pontica.gg/api/v1/keys
Content-Type: application/json
{ "address": "0x…", "message": "pontica.gg wants you to sign in…", "signature": "0x…" }
The gateway checks the message and the signature, reads what the chain credited to the wallet (at least 1 OBOL is needed), creates the new key, retires every other key of the wallet and answers 201 with the key, once, with cache-control: no-store:
keystring- The secret,
sk-or-v1-…. In this answer only: Pontica cannot show it again. hashstring- SHA-256 of the key, 64 hex. Safe to keep; it syncs the key.
base_urlstringhttps://pontica.gg/api/v1addressstring- The wallet, EIP-55.
activated_usdnumber- Everything the chain has credited to the wallet.
used_usdnumber- What the wallet's earlier keys spent (the carry).
limit_usd,remaining_usdnumber- The new key's limit: credited minus carry.
replacednumber- How many keys this retired.
notestring- "Shown once. Pontica cannot show it again."
- Replay guard. Each key remembers the Issued At of its message. A message not newer than the wallet's last one is refused with
409 signature_used: sign a new one. - Racing requests. If two issues for one wallet cross, the earliest key wins and the other answers
409 issue_conflict. Sign again. - Rate. At most 5 keys per wallet in 24 hours:
429 too_many_keyswithRetry-After. - Retired keys. The old key stops at once. Requests already running on it finish, and what they cost is carried into the new key's accounting.
Sync a key#
After a new activation, POST /api/v1/keys/sync moves the key's limit to what the chain credited. It never creates a key, never returns a secret and never lifts a limit above what the chain proves. Three ways in:
- The key:
Authorization: Bearer sk-or-v1-…and no body. Best for code. - The hash: body
{ "hash": "…" }, the SHA-256 of the key. What the account page keeps. - A signature: body
{ address, message, signature }with the statementSync the limit of this wallet's Pontica API key.in the same message format. For a lost hash.
It answers 200 with { address, hash, activated_usd, used_usd, limit_usd, remaining_usd, synced }, where synced is true when this call moved the limit. The new limit is the larger of the key's usage and credited minus carry. A hash sync repeated within 10 seconds returns the same numbers unless an activation landed.
Errors: 401 key_retired (another, older key of the wallet is the live one), 401 not_a_pontica_key, 404 no_key, 409 issue_conflict (a key for this wallet is being issued right now; try again in a minute), 400 missing_field for a malformed hash.
GET /key#
GET /api/v1/key with your key answers OpenRouter's own key answer (limit, limit_remaining, usage and the rest, in USD) plus a pontica field for keys Pontica issued:
address- The wallet the key belongs to.
activated_usd- Everything the chain has credited to that wallet.
used_usd- Everything the wallet's keys have spent.
remaining_usd- What is left to spend.
retired- True when a newer key replaced this one.
needs_sync- True when an activation is not in the key's limit yet: call sync.
For a key Pontica did not issue, pontica is null.
Chat completions#
POST /api/v1/chat/completions is OpenAI and OpenRouter compatible. The body goes to OpenRouter byte for byte, and the answer comes back untouched.
- Streaming. With
"stream": truethe answer is server-sent events:data:lines, keep-alive comments (: OPENROUTER PROCESSING), thendata: [DONE]. The last chunk before[DONE]carriesusagewithcost, the USD charged at list price, which is what your balance pays. - Cancelling. Close the connection. The gateway passes the cancel on to OpenRouter; some providers stop billing there, others finish the reply and bill it.
- Limits. Bodies over 4.5 MB answer
413 body_too_large, and a request may run 300 s including its stream; past that the platform cuts it without[DONE]. For larger or longer requests callhttps://openrouter.ai/api/v1directly with the same key: it is an OpenRouter key, with the same balance. - Headers. The gateway adds
HTTP-Referer: https://pontica.ggandX-OpenRouter-Title: Ponticaand forwards nothing else from your client: no IP address, cookies or user agent. It returnscontent-type,x-generation-id,x-provider-nameandretry-after. - Errors.
401 missing_key,not_a_pontica_keyorkey_retiredfrom the gateway; everything else, including402when your limit is used up, comes from OpenRouter unchanged. While the gateway cannot check keys (its OpenRouter key expired or OpenRouter is unreachable), chat keeps working for the keys it cannot check.
Models#
GET /api/v1/models answers OpenRouter's catalogue JSON unchanged (data, total_count, links) plus pontica: { book, fetched_at, source: "openrouter" }. book is null until the book has asks; then it is { obol_price_usd, discount_bps, depth_obol, fee_bps, quote_usdg: "10", block }, where obol_price_usd is what a 10 USDG book buy pays per OBOL, book fee included.
- By default it lists text output models, like OpenRouter. Allowed query parameters:
output_modalities,input_modalities,supported_parameters,category. Anything else answers400 unsupported_parameter. - Prices are USD per token as decimal strings. Router models answer
"-1": their price varies per request. - Cached 60 s in the browser and 5 minutes at the edge. When OpenRouter fails, the last good copy is served with
x-pontica-stale: 1; with no copy the answer is502, never a made up list. GET /api/v1/models/countanswers{ count, fetched_at }and accepts onlyoutput_modalities.
Key activity#
GET /api/v1/keys/activity with your key answers { address, hash, retired, data }, where data is OpenRouter's activity report for that key: per UTC day and model, the requests, prompt, completion and reasoning tokens and the USD spent, for the last 30 completed days. No prompt data exists there. Retired keys can still read their history.
Status and stats#
GET /api/status always answers 200 and is cached 15 s:
gateway.connected,reason- Whether the gateway can issue keys, and why not:
not_configured,key_expired,key_invalid,not_management_key,upstream_unreachable. gateway.workspace- The Pontica workspace's logging switches at OpenRouter:
io_logging,data_discount_logging,broadcast. gateway.budget_usd- The workspace's lifetime budget, kept equal to all OBOL ever credited.
credits_remaining_usd,outstanding_usd,covered- The operator's OpenRouter balance, the credited balance not spent yet, and whether the first covers the second.
chain{ id, block, rpc, deployed, paused },rpcone ofok,degraded,down.models{ count, fetched_at }of the live catalogue.
See the live answer
Loading
GET /api/stats answers protocol totals from the contracts' own counters, the events of the last 24 hours (at most 50, newest first, with real block times) and the gateway's usage over 30 days; cached 60 s. GET /api/stats/daily?days=7 (or 30, 90) answers one row per UTC day.
Errors#
The gateway's own errors use the OpenAI shape, { "error": { "message", "type", "code", "param" } }, with a plain sentence in message. Errors from OpenRouter pass through unchanged, in OpenRouter's shape.
400invalid_request_error
bad_json missing_field bad_address bad_message wrong_chain wrong_domain address_mismatch signature_expired signature_from_future unsupported_parameter
The request or the signed message was not accepted; the message says which part. Sign again.
401authentication_error
missing_key bad_signature not_a_pontica_key key_retired
No usable key or signature. Use your current key or issue a new one.
402insufficient_quota
nothing_activated
Activate OBOL to this wallet first.
404not_found_error
no_key not_found
This wallet has no key yet, or no key has that hash.
405invalid_request_error
method_not_allowed
Wrong method; the Allow header lists the right ones.
409conflict_error
signature_used issue_conflict
Another key request crossed this one, or the message was used. Sign again.
413invalid_request_error
body_too_large
Over 4.5 MB. Send it to openrouter.ai/api/v1 with the same key.
429rate_limit_error
too_many_keys
This wallet issued 5 keys in the last 24 hours. Retry-After says when to try again.
500api_error
internal_error
An unexpected error. Try again.
502api_error
upstream_unavailable chain_unavailable
The gateway could not reach OpenRouter or Robinhood Chain. Try again.
503service_unavailable
gateway_not_connected gateway_key_expired
The inference gateway is not connected yet, or its OpenRouter key expired.
Limits#
Free models and rate limits
- Paid models: OpenRouter sets no request limit per key; its capacity is shared globally.
- Free models (ids ending in
:free) have per minute and per day caps per OpenRouter account. Every Pontica key shares the operator's account, so heavy use by one key can use up the free quota for everyone that day. - Your balance is the real limit. When your key's limit is used up, OpenRouter answers
402. Activate more OBOL and sync. - Overshoot. OpenRouter checks a key's limit when a request starts and charges it when it ends, so many expensive requests at once can end above the limit. The gateway counts the overshoot against the wallet: its next limit stays at its usage until a new activation covers it.
Known limits of the gateway
- About 10,000 keys. The gateway keeps no database: each key's bookkeeping lives in its name at OpenRouter, and OpenRouter's key list cannot be filtered, so issuing a key and a signed sync read the whole list. That holds up to about 10,000 keys. Past it the ledger needs splitting across workspaces or a small store. A known limit, accepted for launch.
- Hosting limits (Vercel). Request bodies up to 4.5 MB; a chat request may run 300 s including its stream; key routes 30 s; everything else 20 s.
- A hard ceiling. The Pontica workspace at OpenRouter has a lifetime budget kept equal to all OBOL ever credited, so all keys together can never spend more than the chain credited.
- The operator's key expires. OpenRouter fixes the expiry of the management key at creation. When it passes, key routes answer
503 gateway_key_expireduntil the operator rotates it;/api/statusshows the date.
Contract addresses#
The Pontica contracts on Robinhood Chain (chain id 4663), non upgradeable, with their source verified on Sourcify. ABIs: obol, mint, exchange, staking, funds. The full agent guide is Pontica for agents.