OSTRAKA
Ostraka API

Docs

Ostraka is an OpenAI-compatible API for hundreds of models. Any client that speaks Chat Completions works with a base URL and a key.

  1. Open the app and sign in with an Ethereum wallet. Signing in is a free message signature.
  2. Add funds: deposit USDG on Robinhood Chain into the vault.
  3. Under API keys, create a key with a spending limit. The key is shown once.
  4. Call https://ostraka.xyz/api/v1 with Authorization: Bearer osk_….
curl https://ostraka.xyz/api/v1/chat/completions \
  -H "Authorization: Bearer $OSTRAKA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "deepseek/deepseek-chat",
    "stream": true,
    "messages": [{"role": "user", "content": "Write a haiku about receipts."}]
  }'

No wallet yet? POST /api/demo/keys returns a demo key: 24 hours, a small limit, a few low-cost models, up to 1,024 output tokens per request, three per visitor per day.

Keys and limits

  • Every key has a lifetime spending limit, set when you create it. When a request would go past it, the API answers 402 key_limit_reached.
  • A key can be limited to a list of models. Other models answer 403 model_not_allowed.
  • Keys can expire after 1 to 90 days, and you can revoke one at any time. Revoking takes effect within seconds.
  • Each key handles up to 120 requests a minute.
  • We store only a hash of each key. If you lose one, revoke it and create another.

GET /api/v1/key returns the calling key's limit, what it has spent, what's left, and the account's available balance. Agents can use it to check their budget.

Models and prices

GET /api/v1/models is public and uses the OpenAI list format. Each model's pricing shows both prices in USD per million tokens:

{
  "id": "anthropic/claude-sonnet-4.5",
  "context_length": 200000,
  "capabilities": { "tools": true, "reasoning": true, "vision": true },
  "pricing": {
    "unit": "usd_per_million_tokens",
    "prompt": 3.15, "completion": 15.75,
    "upstream_prompt": 3, "upstream_completion": 15,
    "markup_bps": 500
  }
}

When the provider reports the exact cost of a request, we charge that cost plus the markup. Otherwise we charge the tokens used times the listed price. Either way, the receipt records both our charge and the provider's cost.

Chat completions

POST /api/v1/chat/completions follows the OpenAI Chat Completions format: messages, stream, tools, tool_choice, response_format, temperature, max_tokens or max_completion_tokens, and the model's other parameters pass through.

  • Before a request runs, we hold its largest possible cost against your balance and key. If your balance covers less output than you asked for, max_tokens is lowered to what it covers, and the x-ostraka-max-tokens header says what was used.
  • If the provider fails, nothing is charged.
  • If you stop reading a stream, the request is cancelled and you pay only for what was delivered.
  • n above 1 isn't supported.

Non-streaming answers include an ostraka object:

"ostraka": {
  "receipt": "0x3f1c…",
  "cost_usd": 0.004213,
  "upstream_cost_usd": 0.004012,
  "verify": "https://ostraka.xyz/verify#0x3f1c…"
}

Receipts

Every paid request gets a receipt. Its id is in the x-ostraka-receipt response header, streaming or not. GET /api/v1/receipts/{id} returns:

{
  "receipt": {
    "v": 1, "id": "0x…", "account": "0x…", "model": "deepseek/deepseek-chat",
    "in": 412, "out": 188, "cost": 251, "upstream": 239, "ts": 1793102400,
    "leaf": "0x…", "signature": "0x…", "signer": "0x…"
  },
  "status": "settled",
  "epoch": { "n": 41, "root": "0x…", "tx": "0x…" },
  "proof": ["0x…", "0x…"],
  "vault": "0x…"
}

cost and upstream are in USDG base units: millionths of a dollar. A receipt never contains your prompt or the answer.

Verifying a receipt

The verify page does all of this in your browser. To do it yourself:

  1. Leaf. keccak256(keccak256(abi.encode(bytes32 id, address account, bytes32 keccak256(model), uint64 in, uint64 out, uint128 cost, uint128 upstream, uint64 ts))) must equal leaf.
  2. Signature. Recover the signer of signature as an EIP-191 personal signature over the 32 leaf bytes. It must be the receipt signer published on the ledger and in /llms.txt.
  3. Inclusion. Hash the leaf up the proof, sorting each pair (OpenZeppelin MerkleProof rules). The result is the epoch root.
  4. On chain. vault.roots(epoch) on Robinhood Chain must return that root.
// with viem
const ok = await recoverMessageAddress({ message: { raw: r.leaf }, signature: r.signature }) === SIGNER;
const root = await client.readContract({ address: vault, abi, functionName: "roots", args: [BigInt(epoch.n)] });

The settlement charges each account the sum of its receipts in the epoch, so your receipts add up to your charge.

Balance and settlement

  • Your balance is held by the vault contract, in USDG on Robinhood Chain.
  • Requests are metered as they happen. At most once per the vault's minimum gap (an hour at launch), the gateway closes an epoch and settles it: one transaction with each account's total and the epoch's Merkle root.
  • The vault never charges an account more than it holds, or more than its per-settlement cap.
  • Withdrawing: request an amount, wait the withdrawal delay (24 hours at launch), then collect. You get the amount, or everything still withdrawable if you spent some in the meantime. Nothing can pause withdrawals.
  • Any change that would raise what the operator can charge (a new operator, a higher cap, a shorter gap, a longer delay) and any change to the credits share waits 8 days on chain.

Holder credits

A fixed share of settled revenue (shown on the ledger) goes to the credits pool when anyone calls sweep(). Each round:

  1. We take a snapshot of token balances at a block.
  2. Holders above the minimum get a share of the round in proportion to their balance, capped per wallet.
  3. The round's Merkle root is posted on chain. Holders claim in the app until the deadline. Unclaimed amounts return to the pool for later rounds.

Claimed credits are added to your vault balance and are spent before your cash. Credits are for inference only and can't be withdrawn. The pool contract has no other way to pay out.

Coding agents

Any tool that accepts an OpenAI-compatible provider works. Use base URL https://ostraka.xyz/api/v1, your key, and a model id from the catalog.

Cline, Roo Code, Kilo Code

API Provider: OpenAI Compatible. Base URL as above, API key, Model ID from the catalog.

Continue

models:
  - name: Ostraka
    provider: openai
    model: deepseek/deepseek-chat
    apiBase: https://ostraka.xyz/api/v1
    apiKey: osk_…

Aider

export OPENAI_API_BASE=https://ostraka.xyz/api/v1
export OPENAI_API_KEY=osk_…
aider --model openai/deepseek/deepseek-chat

OpenAI SDK (Node)

import OpenAI from "openai";
const client = new OpenAI({ baseURL: "https://ostraka.xyz/api/v1", apiKey: process.env.OSTRAKA_API_KEY });

Errors

Errors use the OpenAI envelope, { "error": { "message", "type", "param", "code" } }.

StatusCodeMeaning
400invalid_value, invalid_json, unsupported_parameterThe request is malformed or asks for something unsupported.
401invalid_api_key, key_expiredThe key is missing, unknown, revoked or expired.
402insufficient_balanceYour balance doesn't cover the request. Add funds.
402key_limit_reachedThe key reached its spending limit.
403model_not_allowedThe key isn't allowed to use that model.
404model_not_foundNo model with that id. See GET /api/v1/models.
429rate_limit_exceeded, upstream_rate_limitedToo many requests on this key, or the provider is busy.
502, 503upstream_unreachable, upstream_unavailableThe provider failed. Nothing was charged.

Privacy

  • Prompts and answers pass through Ostraka and are never stored or logged.
  • The provider that runs the model reads your prompt in order to answer. We ask the router to use only providers that don't store or train on prompts. The provider's own policy still applies.
  • We store your wallet address, your keys (hashed), and receipts: model, token counts, charge, provider cost, time.
  • Receipts go on chain only as part of a Merkle root, which reveals nothing about any single request.

Network and contracts

Robinhood Chain mainnet (chain ID 4663). Balances are in USDG. Contract addresses and the receipt signer are on the ledger, and the source is verified on the explorer.

  • OstrakaVault holds balances, takes settlements and roots, pays withdrawals, and sweeps revenue.
  • CreditsPool holds the credits share and pays it out as credits, round by round.