API Reference

Random Generator API

Cryptographically verifiable random numbers over HTTP. Base URL: https://api.provable.io

Updated

Authentication

Every endpoint below is callable anonymously — anonymous calls are free and rate-limited by IP. Authenticated calls are attributed to your account for usage tracking, daily quotas, and webhook delivery.

Send your API key with either of these headers:

  • x-api-key: YOUR_API_KEY
  • Authorization: Bearer YOUR_API_KEY

Create or rotate your key on the dashboard.

Live vs. test keys

Every key is either a live key (prefix pk_live_) or a test key (prefix pk_test_). Pick the mode when you create the key on the dashboard.

  • Live keys — real cryptographic randomness backed by a server seed you can verify. Calls count toward your daily quota, appear in dashboard usage stats, and trigger any configured webhooks.
  • Test keys — outcomes are deterministic: the server derives the seed from (your account, clientSeed), so the same parameters always return the same numbers (any test key on your account will produce the same outcome for a given clientSeed). Responses are flagged "mode": "test". Test calls are excluded from live usage stats and quotas and do not fire webhooks, which makes them safe for fixtures, snapshot tests, and CI. They are still subject to the anonymous per-minute rate limit (120 req/min), so they can't be used to bypass abuse protection.

Restricting where a key can be used

Each key (live or test) can carry two optional allowlists, editable from the dashboard:

  • Allowed IP ranges — one CIDR per line (IPv4 or IPv6), e.g. 203.0.113.0/24 or 2001:db8::/32.
  • Allowed Referer origins — one scheme://host[:port] per line. A leading *. matches one or more subdomain labels, e.g. https://*.example.com.

Empty list = no restriction. When both lists are set, the request must satisfy both. Requests that fail an active restriction are rejected with HTTP 403 and a JSON body:

{
  "error": "Request IP is not in this API key's allowed IP ranges.",
  "code": "ip_not_allowed"
}

Possible error codes: ip_not_allowed, referer_not_allowed. Denied requests are logged to the key's activity feed in the dashboard so you can spot leaked or misused keys.

Prefer machines? Download the OpenAPI 3.0 spec (/openapi.json) to generate client SDKs, import into Postman/Insomnia, or run contract tests.

Official SDK

The typed TypeScript client is generated directly from the OpenAPI spec above, so request params and response bodies always stay in sync with the live API.

npm install @provableio/sdk
import { ProvableClient } from "@provableio/sdk";

const client = new ProvableClient({ apiKey: process.env.PROVABLE_KEY });
const { data, error } = await client.getInts({ clientSeed: "order-42", count: 5, min: 1, max: 100 });
if (error) throw new Error(error.error);
console.log(data.outcome);

Zero runtime dependencies; works in Node 18+, browsers, Deno, Bun, and Cloudflare Workers. Source and full docs: provableio/provable-core sdk/typescript · @provableio/sdk on npm.

Try it inline

Every endpoint below has a Run button on its example snippet. Runs are anonymous by default; paste an API key here (or sign in) to attribute them to your account.

Idempotency

Network retries on random-generating endpoints would otherwise re-run the RNG, return a different outcome, and double-count usage. To make retries safe, send an Idempotency-Key header on any call to /api/floats, /api/ints, /api/shuffle, /api/pick, /api/bytes, /api/dice, or /api/gaussian:

curl "https://api.provable.io/api/ints?clientSeed=order-42&count=5" \
  -H "x-api-key: $PROVABLE_KEY" \
  -H "Idempotency-Key: a3e1c7b9-3a8c-4f1e-9b2d-d8c0e7f4b1aa"

For 24 hours after the first successful call, any retry with the same key returns the original status, body, and Content-Type verbatim. Replays carry an extra response header so callers can tell them apart from fresh draws:

Idempotent-Replayed: true
  • Quota-safe. Replays do not increment daily quota, monthly usage, or key activity counters.
  • Scope. Keys are scoped to your API key (or to your IP for anonymous calls), so two accounts using the same opaque string never collide.
  • Conflicts. Reusing the same key with different query parameters returns 409 Conflict with body { "error": "...", "code": "idempotency_key_conflict" }. Pick a new key whenever the request body or params change.
  • Format. Any opaque ASCII string up to 255 characters — UUIDs (v4) are the typical choice.
  • Caching window. Only successful (2xx) responses are stored, for 24 hours. After that the key is forgotten and a retry generates a fresh outcome.
  • Where it doesn't apply. /api/verifyServerHash, /api/listOutcomes, and /api/health are already side-effect-free, so the header is accepted but ignored on those routes.
Hash chains #hash-chains

Every generation endpoint draws from a hash chain — a server-side row carrying a serverSeed, its public serverHash, and the current cursor/nonce. Each draw bumps the nonce and is recorded into /api/listOutcomes. Chains are addressed by a (api key, clientSeed) pair.

How chains are scoped

  • Per-key chains. Every live api key (pk_live_*) has its own private chain for each clientSeed it uses. Draws made with key A never advance key B's chain, even when both use clientSeed=blackjack-2026. The two are independent hash streams from the moment the chain is created.
  • Anonymous chain. Calls without an api key share a single anonymous chain per clientSeed. It's public by design — any anonymous caller can draw from it and any caller can rotate it. Don't use the anon chain for application state you don't want anyone else to influence; create a key.
  • Test keys. pk_test_* keys bypass the live chain entirely on every random-generating endpoint (floats, ints, shuffle, pick, bytes, dice, gaussian): outcomes are deterministic per (account, clientSeed), stored as test rows in a per-user fixture space, never advance live seed_state, and never count toward live usage stats or fire webhooks. Responses are tagged mode: "test".

What spans all chains

Verification and lookup are intentionally global — anyone with a serverHash, a shortId, or a clientSeed can audit any outcome regardless of which chain produced it:

  • /api/verifyServerHash scans every chain (anon + every key) for the given clientSeed and returns a match if any chain holds that serverHash.
  • /api/listOutcomes, /api/outcome, and /o/:id permalinks return outcomes from any chain — the chain identity is internal and not part of the public outcome payload.

Practical guidance

  • Each api key + clientSeed pair is a fresh chain. The first draw for a new pair lazily creates the chain; you don't manage it.
  • Rotating one chain (see /api/rotate) reveals only that chain's prior serverSeed — other chains keep theirs secret.
  • If you need parallel uncorrelated chains under one account (e.g. one per game table), use distinct clientSeed values, the same api key, and rotate them independently.
GET /api/floats #floats

Generate cryptographically verifiable random floating-point numbers in the range [0, 1).

Query Parameters

NameTypeRequiredDescription
clientSeedstringRequiredYour seed for this generation. Combined with our server seed for provably fair output.
countinteger (1–100)OptionalNumber of floats to generate. Defaults to 1.

Example Request

cURL
curl "https://api.provable.io/api/floats?clientSeed=my-app&count=5"

Example Response

{
  "outcome": [0.4823, 0.1175, 0.9034, 0.5567, 0.2218],
  "clientSeed": "my-app",
  "serverHash": "9f86d081884c7d65...",
  "nonce": 0,
  "cursor": 5,
  "count": 5,
  "created": 1716566400000,
  "shortId": "k7Qm2A9bXz",
  "permalink": "https://provable.io/o/k7Qm2A9bXz"
}

The shortId is a stable public ID. Hand anyone the permalink to let them view and re-verify this outcome on a no-auth page. See /o/:id below.

GET /api/ints #ints

Generate cryptographically verifiable random integers within a custom range.

Query Parameters

NameTypeRequiredDescription
clientSeedstringRequiredYour seed for this generation.
countinteger (1–100)OptionalNumber of integers to generate. Defaults to 1.
mininteger (≥ 0)OptionalMinimum value (inclusive).
maxinteger (≥ 1)OptionalMaximum value (inclusive).

Example Request

cURL
curl "https://api.provable.io/api/ints?clientSeed=my-app&count=5&min=1&max=100"

Example Response

{
  "outcome": [42, 87, 13, 65, 29],
  "clientSeed": "my-app",
  "serverHash": "9f86d081884c7d65...",
  "nonce": 0,
  "cursor": 5,
  "count": 5,
  "min": 1,
  "max": 100,
  "created": 1716566400000,
  "shortId": "k7Qm2A9bXz",
  "permalink": "https://provable.io/o/k7Qm2A9bXz"
}
GET /api/shuffle #shuffle

Return a uniformly fair shuffle of a caller-supplied array. The same clientSeed/cursor/nonce always yields the same permutation, so the result is fully verifiable.

Query Parameters

NameTypeRequiredDescription
clientSeedstringRequiredYour seed for this generation.
itemsJSON array, comma list, or repeated paramRequired1–1000 entries to shuffle. Accepts items=["a","b"], items=a,b, or items=a&items=b.

Example Request

cURL
curl "https://api.provable.io/api/shuffle?clientSeed=my-app&items=%5B%22a%22%2C%22b%22%2C%22c%22%2C%22d%22%5D"

Example Response

{
  "outcome": ["c", "a", "d", "b"],
  "clientSeed": "my-app",
  "serverHash": "9f86d081884c7d65...",
  "nonce": 0,
  "cursor": 0,
  "count": 4,
  "endpoint": "shuffle",
  "created": 1716566400000
}
GET /api/pick #pick

Pick one item from a list, optionally with per-item weights. Returns the chosen item and its zero-based index.

Query Parameters

NameTypeRequiredDescription
clientSeedstringRequiredYour seed for this generation.
itemsJSON array, comma list, or repeated paramRequired1–1000 entries to choose from.
weightsJSON array or comma list of numbersOptionalNon-negative weights, one per item. Defaults to uniform. Must sum to a positive number.

Example Request

cURL
curl "https://api.provable.io/api/pick?clientSeed=loot-roll&items=common,rare,legendary&weights=70,25,5"

Example Response

{
  "outcome": "legendary",
  "index": 2,
  "clientSeed": "loot-roll",
  "serverHash": "9f86d081884c7d65...",
  "nonce": 0,
  "cursor": 0,
  "count": 1,
  "weights": [70, 25, 5],
  "endpoint": "pick",
  "created": 1716566400000
}
GET /api/bytes #bytes

Return count random bytes encoded as hex (default) or base64. Useful for tokens, salts, and key material.

Query Parameters

NameTypeRequiredDescription
clientSeedstringRequiredYour seed for this generation.
countinteger (1–1024)OptionalNumber of bytes to generate. Defaults to 32.
encodinghex | base64OptionalOutput encoding. Defaults to hex.

Example Request

cURL
curl "https://api.provable.io/api/bytes?clientSeed=my-app&count=16&encoding=hex"

Example Response

{
  "outcome": "3f0a8b7d1c4e6f209a8c5b4d2e1f0a8b",
  "clientSeed": "my-app",
  "serverHash": "9f86d081884c7d65...",
  "nonce": 0,
  "cursor": 0,
  "count": 16,
  "encoding": "hex",
  "endpoint": "bytes",
  "created": 1716566400000
}
GET /api/dice #dice

Roll dice using standard tabletop notation. Returns the individual rolls, the modifier, and the total.

Query Parameters

NameTypeRequiredDescription
clientSeedstringRequiredYour seed for this generation.
notationstringRequiredDice notation NdM, NdM+K, or NdM-K. N: 1–100 dice. M: 2–1,000,000 sides. Examples: 3d6, 2d20+5, 1d100-10.

Example Request

cURL
curl "https://api.provable.io/api/dice?clientSeed=tabletop&notation=3d6%2B2"

Example Response

{
  "outcome": { "notation": "3d6+2", "rolls": [4, 6, 3], "modifier": 2, "total": 15 },
  "clientSeed": "tabletop",
  "serverHash": "9f86d081884c7d65...",
  "nonce": 0,
  "cursor": 0,
  "count": 3,
  "sides": 6,
  "modifier": 2,
  "endpoint": "dice",
  "created": 1716566400000
}
GET /api/gaussian #gaussian

Draw samples from a normal, exponential, or poisson distribution with caller-supplied parameters.

Query Parameters

NameTypeRequiredDescription
clientSeedstringRequiredYour seed for this generation.
countinteger (1–100)OptionalNumber of samples to draw. Defaults to 1.
distributionnormal | exponential | poissonOptionalDefaults to normal.
meannumberOptionalμ for normal. Defaults to 0.
stddevnumber (> 0)Optionalσ for normal. Defaults to 1.
lambdanumber (> 0)Optionalλ for exponential (> 0) or poisson (> 0, ≤ 100). Defaults to 1.

Example Request

cURL
curl "https://api.provable.io/api/gaussian?clientSeed=stats&distribution=normal&count=5&mean=0&stddev=1"

Example Response

{
  "outcome": [-0.42, 1.05, 0.18, -1.21, 0.66],
  "clientSeed": "stats",
  "serverHash": "9f86d081884c7d65...",
  "nonce": 0,
  "cursor": 0,
  "count": 5,
  "distribution": "normal",
  "mean": 0,
  "stddev": 1,
  "endpoint": "gaussian",
  "created": 1716566400000
}
GET /api/stream #stream

Open a long-lived Server-Sent Events connection that emits one verifiable outcome per interval, indefinitely (up to a 10 minute cap). Use it for live games, simulators, and dashboards that need a steady drip of randomness without paying the per-request HTTP overhead.

Response type differs from the other endpoints. Instead of a single JSON body the server holds the connection open and writes a stream of text/event-stream frames. Each frame is a self-contained event: outcome message whose data: field is a full JSON outcome (identical shape to the matching non-streaming endpoint, plus an outcomeId field of clientSeed:cursor:nonce). Every emitted outcome verifies independently via /api/verifyServerHash.

Query Parameters

NameTypeRequiredDescription
endpointstringRequiredWhich generator to stream: floats, ints, shuffle, pick, bytes, dice, or gaussian.
clientSeedstringRequiredYour seed for this stream. The same client seed is reused for every emitted outcome.
intervalMsinteger (100–60000)OptionalMilliseconds between outcomes. Defaults to 1000.
any endpoint paramsvariousOptionalPass the same query parameters you would to the underlying endpoint (e.g. count, min, max, items, notation, distribution). Validated once on connect.
lastEventIdstringOptionalResume after a disconnect. Pass the most recent outcomeId you received (clientSeed:cursor:nonce); the server will only emit outcomes strictly after that point on the same clientSeed. Equivalent to the Last-Event-ID request header that EventSource sends automatically on auto-reconnect.

Lifecycle

  • The first outcome is emitted immediately after the connection is accepted (it also doubles as parameter validation — bad inputs come back as a normal 400, no stream is opened).
  • Heartbeat comments (: heartbeat …) are sent every ~15 seconds so proxies don't time the connection out.
  • Each emitted outcome counts as one billed call against your account (live keys only). Anonymous and test-mode streams are not metered but still subject to the per-IP anonymous rate limit on the initial connection.
  • The server caps any single connection at 10 minutes. When the cap (or quota) is reached the server emits a final event: done frame and closes — reconnect to keep streaming.
  • Every event: outcome frame includes an id: line. On a transient drop, vanilla EventSource auto-reconnects and replays it as Last-Event-ID; raw fetch clients can pass the same value as a ?lastEventId= query parameter. Either way the server resumes strictly after the last id you saw on that clientSeed, so reconnects don't double-count or skip — and skipped duplicates aren't billed.
  • Closing the connection (e.g. EventSource.close()) tears down the timers cleanly on the server, so no further outcomes are generated or billed.

Example Request

cURL
curl -N "https://api.provable.io/api/stream?endpoint=floats&clientSeed=my-app&intervalMs=1000"

Example Stream

id: my-app:0:0
event: outcome
data: {"outcome":[0.4823],"clientSeed":"my-app","serverHash":"9f86d0...","nonce":0,"cursor":0,"count":1,"endpoint":"floats","created":1716566400000,"outcomeId":"my-app:0:0"}

: heartbeat 1716566415000

id: my-app:0:1
event: outcome
data: {"outcome":[0.1175],"clientSeed":"my-app","serverHash":"9f86d0...","nonce":1,"cursor":0,"count":1,"endpoint":"floats","created":1716566401000,"outcomeId":"my-app:0:1"}

event: done
data: {"reason":"max_duration","count":600,"durationMs":600000}

For a full worked example see Streaming outcomes in real time.

POST /api/batch #batch

Run up to 50 independent draws across any of the random-generating endpoints in a single HTTP round trip. Each draw uses its own clientSeed/cursor/nonce, so every result is independently verifiable via /api/verifyServerHash and has its own shortId/permalink.

Request Body

NameTypeRequiredDescription
drawsarray (1–50)RequiredArray of { endpoint, params } objects. endpoint is one of floats, ints, shuffle, pick, bytes, dice, or gaussian. params takes the same fields you'd pass as query string to the single-draw endpoint (each draw must supply its own clientSeed).

Behavior

  • Per-draw error isolation. A failing draw produces { "ok": false, "error": "…" } in its slot — it does not abort the rest of the batch. Successful draws still persist their outcomes.
  • Quota. Authenticated calls bump usage counters once per successful draw (failures don't count). If the daily quota is hit mid-batch, remaining draws short-circuit with { "ok": false, "error": "…", "code": "quota_exceeded" }.
  • Order. results is in the same order as draws.
  • Idempotency. A single Idempotency-Key header covers the whole batch — replays return the identical results array and do not re-run any draw.

Example Request

cURL
curl -X POST "https://api.provable.io/api/batch" \
  -H "Content-Type: application/json" \
  -d '{
    "draws": [
      { "endpoint": "ints", "params": { "clientSeed": "chest-1", "min": 1, "max": 100 } },
      { "endpoint": "ints", "params": { "clientSeed": "chest-2", "min": 1, "max": 100 } },
      { "endpoint": "pick", "params": { "clientSeed": "loot-3", "items": ["common","rare","legendary"], "weights": [70,25,5] } }
    ]
  }'

Example Response

{
  "results": [
    { "ok": true, "outcome": { "outcome": [42], "clientSeed": "chest-1", "serverHash": "9f86d081...", "nonce": 0, "cursor": 0, "count": 1, "min": 1, "max": 100, "endpoint": "ints", "created": 1716566400000, "shortId": "k7Qm2A9bXz", "permalink": "https://provable.io/o/k7Qm2A9bXz" } },
    { "ok": true, "outcome": { "outcome": [73], "clientSeed": "chest-2", "serverHash": "5e3b...", "nonce": 0, "cursor": 0, "count": 1, "min": 1, "max": 100, "endpoint": "ints", "created": 1716566400000, "shortId": "9aB2c3D4eF", "permalink": "https://provable.io/o/9aB2c3D4eF" } },
    { "ok": true, "outcome": { "outcome": "legendary", "index": 2, "clientSeed": "loot-3", "serverHash": "0a1b...", "nonce": 0, "cursor": 0, "count": 1, "weights": [70, 25, 5], "endpoint": "pick", "created": 1716566400000, "shortId": "P9q8R7s6T5", "permalink": "https://provable.io/o/P9q8R7s6T5" } }
  ]
}
POST /api/rotate #rotate

Reveal the current serverSeed for a chain and rotate to a fresh one. This is the only endpoint that exposes the secret serverSeed — every other endpoint surfaces only the public serverHash. After rotation, cursor and nonce reset to 0 on the new serverHash.

Which chain gets rotated

  • Authenticated call (pk_live_*) — rotates only that key's chain for the given clientSeed. Other keys' chains and the anonymous chain are untouched.
  • Anonymous call (no x-api-key) — rotates the shared anonymous chain. There's no owner, so any caller can rotate it; treat the revealed serverSeed as public.
  • Test keys (pk_test_*) — rejected 403 (code: "test_mode_forbidden"). Test mode never reveals secrets, and forbidding rotate prevents a test key from exposing the public anon chain's serverSeed.

Request Body

NameTypeRequiredDescription
clientSeedstringRequiredThe client seed identifying which chain to rotate (combined with the caller's api key, or anon).

Example Request

cURL
curl -X POST https://api.provable.io/api/rotate \
  -H "x-api-key: pk_live_..." \
  -H "content-type: application/json" \
  -d '{"clientSeed":"blackjack-2026"}'

Example Response

{
  "clientSeed": "blackjack-2026",
  "revealed": {
    "serverSeed": "b1946ac92492d2347c6235b4d2611184a3a6f3c1e5c1c4d8e6b7f1a2c3d4e5f6",
    "serverHash": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
    "cursor": 7,
    "nonce": 3
  },
  "next": {
    "serverHash": "a3b1c5d7e9f1a3b5c7d9e1f3a5b7c9d1e3f5a7b9c1d3e5f7a9b1c3d5e7f9a1b3",
    "cursor": 0,
    "nonce": 0,
    "rotatedAt": 1716566400000
  }
}

Verifying the reveal

To audit fairness, recompute sha256(revealed.serverSeed) and check it matches revealed.serverHash. That hash is what was published with every outcome on this chain since the previous rotate (or since the chain was created), so any /o/:id permalink referencing it can now be re-derived from the revealed seed.

Errors

StatusCodeWhen
400clientSeed missing or empty.
403test_mode_forbiddenCalled with a pk_test_* key.
404Addressed chain has no recorded outcome yet. For authed calls that's this key's chain for this clientSeed; for anonymous calls it's the anon chain. Generate at least one outcome first.
GET /api/verifyServerHash #verifyServerHash

Verify that a server hash matches our records for a given client seed. Returns true if the hash is authentic.

Query Parameters

NameTypeRequiredDescription
clientSeedstringRequiredThe client seed used to generate the outcome.
serverHashstringRequiredThe server hash returned with the original outcome.

Example Request

cURL
curl "https://api.provable.io/api/verifyServerHash?clientSeed=my-app&serverHash=9f86d081884c7d65..."

Example Response

{
  "verified": true,
  "clientSeed": "my-app",
  "serverHash": "9f86d081884c7d65...",
  "outcomes": [
    {
      "shortId": "k7Qm2A9bXz",
      "permalink": "https://provable.io/o/k7Qm2A9bXz",
      "endpoint": "ints",
      "cursor": 5,
      "nonce": 0,
      "created": 1716566400000
    }
  ]
}

When verified, the response also lists permalinks for any recorded outcomes under that (clientSeed, serverHash) pair — convenient for handing a third party a single link.

GET /api/listOutcomes #listOutcomes

List all recorded outcomes for a given client seed, in chronological order.

Query Parameters

NameTypeRequiredDescription
clientSeedstringRequiredThe client seed to look up.

Example Request

cURL
curl "https://api.provable.io/api/listOutcomes?clientSeed=my-app"

Example Response

[
  {
    "outcome": [42, 87, 13, 65, 29],
    "clientSeed": "my-app",
    "serverHash": "9f86d081884c7d65...",
    "nonce": 0,
    "cursor": 5,
    "created": 1716566400000,
    "shortId": "k7Qm2A9bXz",
    "permalink": "https://provable.io/o/k7Qm2A9bXz"
  }
]
POST /api/commit #commit

Stronger fairness guarantee than the one-shot /api/floats and /api/ints. We generate a fresh server seed, publish its serverHash and a commitId, and hold the seed secret. You then submit your client seed to /api/reveal — because the hash was published before you committed your seed, we can't have picked our seed in response to yours. Default TTL is 10 minutes.

Example Request

cURL
curl -X POST "https://api.provable.io/api/commit"

Example Response

{
  "commitId": "8c3d7b9e-1f2a-4c5d-9e8f-7a6b5c4d3e2f",
  "serverHash": "9f86d081884c7d65...",
  "expiresAt": 1716567000000
}
POST /api/reveal #reveal

Submit your clientSeed and the generation parameters against a commitId from /api/commit. We mark the commit revealed (single-use), run the generation against the committed server seed, persist the outcome, and return the revealed serverSeed for independent verification.

Request Body

NameTypeRequiredDescription
commitIdstringRequiredThe commitId returned by /api/commit.
clientSeedstringRequiredYour seed for this generation.
endpointstringRequiredEither "floats" or "ints".
paramsobjectOptionalGenerator parameters: { count } for floats; { count, min, max } for ints.

Errors

StatusWhen
404The commitId does not exist or was already swept after expiring.
409The commit has already been revealed (single-use).
410The commit expired before being revealed.

Example Request

cURL
curl -X POST "https://api.provable.io/api/reveal" \
  -H "Content-Type: application/json" \
  -d '{"commitId":"8c3d7b9e-...","clientSeed":"my-app","endpoint":"ints","params":{"count":5,"min":1,"max":100}}'

Example Response

{
  "outcome": [42, 87, 13, 65, 29],
  "clientSeed": "my-app",
  "serverHash": "9f86d081884c7d65...",
  "serverSeed": "b1946ac92492d2347c6235b4d2611184...",
  "nonce": 0,
  "cursor": 0,
  "count": 5,
  "min": 1,
  "max": 100,
  "endpoint": "ints",
  "commitId": "8c3d7b9e-1f2a-4c5d-9e8f-7a6b5c4d3e2f",
  "created": 1716566400000
}
GET /api/health #health

Service health, version, and uptime. Returns 503 when the database is unreachable.

Example Request

cURL
curl "https://api.provable.io/api/health"

Example Response

{
  "status": "ok",
  "version": "1.0.0",
  "uptime": 12345
}

Daily Merkle roots (transparency log)

Every UTC night we publish a SHA-256 Merkle root over every outcome generated that day. Anyone can fetch the root, request an inclusion proof for any outcome, and verify both locally without trusting our database.

The published list lives at /transparency. The scheme is fully deterministic:

  • Outcome id: clientSeed:cursor:nonce
  • Canonical leaf string: outcomeId|serverHash|clientSeed|timestampMs
  • Leaf hash: SHA256(0x00 || utf8(canonical))
  • Internal node: SHA256(0x01 || left(32) || right(32))
  • Odd level: last node duplicated (Bitcoin-style)
  • Order: outcomes sorted by (clientSeed, cursor, nonce) ascending
  • Empty day: root is "" and leafCount = 0

To verify a proof: start with leaf.hash, then for each sibling combine as SHA256(0x01 || sibling || acc) when position = "left", otherwise SHA256(0x01 || acc || sibling). The final accumulator must equal root.

GET /api/merkle/:date #merkle-root

Get the published Merkle root for a UTC day.

Path Parameters

NameTypeRequiredDescription
datestringRequiredUTC date in YYYY-MM-DD form.

Example Request

curl "https://api.provable.io/api/merkle/2026-05-23"

Example Response

{
  "date": "2026-05-23",
  "root": "a3f1c2...e7",
  "leafCount": 14238,
  "treeHeight": 14,
  "publishedAt": 1716595500000
}
GET /api/merkle/:date/proof/:outcomeId #merkle-proof

Get an inclusion proof for a single outcome inside a published daily tree.

Path Parameters

NameTypeRequiredDescription
datestringRequiredUTC date in YYYY-MM-DD form.
outcomeIdstringRequiredThe outcome's short id, clientSeed:cursor:nonce.

Example Request

curl "https://api.provable.io/api/merkle/2026-05-23/proof/my-app:0:0"

Example Response

{
  "date": "2026-05-23",
  "outcomeId": "my-app:0:0",
  "leaf": {
    "outcomeId": "my-app:0:0",
    "serverHash": "9f86d081884c7d65...",
    "clientSeed": "my-app",
    "timestamp": 1716566400000,
    "canonical": "my-app:0:0|9f86d081884c7d65...|my-app|1716566400000",
    "hash": "5f4e3d..."
  },
  "index": 0,
  "leafCount": 14238,
  "treeHeight": 14,
  "root": "a3f1c2...e7",
  "publishedAt": 1716595500000,
  "siblings": [
    { "position": "right", "hash": "2bcd..." },
    { "position": "left",  "hash": "9876..." }
  ]
}

Paste the response into the verifier widget on /transparency to confirm inclusion client-side.