API reference

The 0xCPU API

Everything the site does, you can do with curl. JSON in, JSON out — except /api/work, which streams binary frames.

Overview

Base URL https://0xcpu.fun. Reading is open. Spending — custom renders, grinds, reservations — needs a session: a signed, HttpOnly cookie you get by signing a Sign-In with Ethereum message. Requests that change state must come from the same origin or carry no Origin header (e.g. curl).

Money is ETH. Credit is kept in wei, quotes convert at the Chainlink ETH/USD feed at quote time, and every job bills the core time its workers measured — never more than the quote, with the remainder refunded when the job completes.

Sign-In with Ethereum

GET /api/auth/nonce
Returns { nonce } and sets a signed nonce cookie (10 min).
POST /api/auth/verify
Body { message, signature } — an EIP-4361 message for this domain with that nonce, signed with personal_sign. Smart-contract wallets (ERC-1271 / ERC-6492) are verified on-chain. Sets a 7-day session cookie.
GET /api/auth/me
{ address } or { address: null }.
POST /api/auth/logout
Clears the session.
message
0xcpu.fun wants you to sign in with your Ethereum account:
0xYourAddress

Sign in to 0xCPU. This signature costs no gas and approves nothing.

URI: https://0xcpu.fun
Version: 1
Chain ID: 1
Nonce: <nonce>
Issued At: 2026-09-12T12:00:00.000Z
Expiration Time: 2026-09-12T12:10:00.000Z

Pulse & health

GET /api/pulse
ETH/USD (Chainlink, Coinbase fallback), the latest Ethereum block, the public ledger, the fleet’s CPU models, the latest hero render, today’s budget and the price list. CDN-cached for 20 s.
GET /api/health
The function’s own CPU and region, a ~150 ms micro-benchmark run on that core (keccak/s, iterations/s), Blobs latency and the head block of each chain’s RPC.
GET /api/hero/:id
The latest hero render’s final tiles as a frame stream (immutable).
shell
curl https://0xcpu.fun/api/pulse | jq '{eth, block, stats: {renders: .stats.renders, coreMs: .stats.coreMs}}'

Jobs

A job is a signed promise of work: the server validates the request, reserves the quote, and returns a token that authorizes exactly that job’s units. Workers record what they did; completion bills from those records only.

POST /api/jobs
{ "kind": "render" } — the public hero render (16 tiles, 768², no sign-in).
{ "kind": "render", "params": { "view": View, "location"? } } — a custom render (session).
{ "kind": "grind", "params": { "prefix", "deployer", "initCodeHash", "protect"? } } — CREATE2 salt search (session).
GET /api/jobs/:id
The job record, including its result once complete.
POST /api/jobs/:id/complete
Idempotent. Folds the workers’ records into a result and refunds the unused quote.
View
{
  "cx": -0.7453, "cy": 0.1127,        // centre in the complex plane
  "scale": 3.4e-6,                    // complex units per pixel
  "cos": 1, "sin": 0,                 // rotation (sent as numbers so both sides agree bit-for-bit)
  "maxIter": 800, "width": 1920, "height": 1080,
  "ss": 1                             // 1, or 2 for 2×2 supersampling
}
shell
curl -X POST https://0xcpu.fun/api/jobs -H 'content-type: application/json' -d '{"kind":"render"}'
# → { "job": { "id": "…", "units": 16, "params": { "view": {…}, "tile": 192 }, "tiles": [...] }, "token": "…" }

Streaming work

POST /api/work
Body { token, i } — run unit i of the job. Renders stream four progressively sharper passes of tile i; grinds stream progress while searching salts for slot i % 16, round ⌊i / 16⌋. Every call ends with a trailer carrying the worker’s CPU, region, timing and a keccak256 receipt.
shell
curl -X POST https://0xcpu.fun/api/work -H 'content-type: application/json' \
  -d '{"token":"<token>","i":0}' --output tile-0.bin

Frame format

Each frame is u32 LE length (of type + body), u8 type, then the body.

TypeBody
1 PASSu8 pass · u8 stride · u16 gw · u16 gh then deflate-raw of Δmu u16[gw·gh] ‖ inside u8[gw·gh]. mu is the mean smooth iteration ×16 of the escaped samples; Δ is a row-wise delta (mod 2¹⁶); inside counts samples that never escaped.
2 TRAILER{ n, ms, hash, iterations | attempts, receipt, worker: { cpu, cores, memMb, region, node, container, cold } }
3 PROGRESS{ n, attempts, ms, rate, best: { nibbles, address } } (grinds)
4 ERROR{ n, error } — e.g. tile_timeout; the unit is not billed.

hash is keccak256 of the final pass’s mu bytes (LE) followed by the inside bytes.

Verifying a tile

Integer iteration counts use only +, −, × and comparisons on IEEE-754 doubles, so every JavaScript engine reproduces them exactly. Re-render the tile with the same kernel and compare: inside must match exactly and mu within 1 (the smooth term uses log2, whose last bit may differ; any integer mismatch moves mu by at least 4).

typescript
import { renderTile, compareTiles } from "./lib/compute/mandelbrot";

const local = renderTile(view, tile);              // same View + Tile the server used
const { match, total } = compareTiles(received, local);
console.log(match === total ? "identical" : `${total - match} pixels differ`);

Credit & top-ups

GET /api/balances?address=0x…
ETH and USDC for any address on Ethereum, Base, Arbitrum, Optimism and Robinhood Chain.
GET /api/account
Session: credit, lifetime deposits and spend, free core-time left today, recent jobs and top-ups.
POST /api/deposits
Body { chainId, txHash }. Credits the sender of an ETH transfer to the treasury once it has enough confirmations (Ethereum 2, others 1); returns 202 pending until then. Idempotent — resubmitting never double-credits.

Reservations

GET /api/reservations
Your reservations.
POST /api/reservations
{ tier, region, term: "hourly"|"monthly"|"yearly", qty, note? } — a request, nothing is charged.
DELETE /api/reservations/:id
Cancel one.

Limits

LimitValue
Hero renders8 per network per 10 min · 150 per day for everyone
Free core-time10 core-minutes per wallet per UTC day
Custom renders≤ 3840×2160, ≤ 3000 iterations, ≤ 96 tiles, ss 1 or 2
Grindsprefix ≤ 7 hex (≤ 6 on the free allowance)
Per unit8 s of compute; job tokens live 5 min (renders) / 10 min (grinds)
Top-ups≥ 0.001 ETH, ETH only

Errors

Errors are JSON { error, detail? } with a meaningful status: 400 bad input, 401 sign_in_required / bad_token, 402 insufficient_credits / free_allowance_exhausted, 409 job_closed / fresh, 429 rate_limited / budget_exhausted, 503 market_unavailable / busy.