ASAnalog Seed

DocsDevelopers

The reference lives on this site.

Base URL https://api.analogseed.dev. Keep as_live_… on a machine you control. Need numbers, colors, chords, or a giveaway without writing code? Openthe studio.

01Quickstart

GET /v1/status and GET /v1/pubkey need no key. Every response that carries entropy sends Cache-Control: no-store. Integer draws use bitmask rejection. bytes on GET /v1/random is an integer from 1 to 65536. format=raw cannot be combined withsign=true.

Batch creative draws. On 429 or 503, keep the last batch or pause. Do not substitute a language PRNG if the work claims physical entropy. Never put an API key in client-side HTML or a published sketch.

1curl -fsS https://api.analogseed.dev/v1/status
2
3curl -fsS \
4 -H "Authorization: Bearer $ANALOGSEED_API_KEY" \
5 -H "Content-Type: application/json" \
6 -d '{"kind":"integers","min":0,"max":99,"count":3,"sign":true,"nonce":"batch-7"}' \
7 https://api.analogseed.dev/v1/draw
8
9curl -fsS \
10 -H "Authorization: Bearer $ANALOGSEED_API_KEY" \
11 "https://api.analogseed.dev/v1/random?bytes=32&format=hex"

429Quota headers

Managed keys with a daily byte cap receive these headers on 200 and on 429 quota_exceeded. Unlimited keys omit them. They describe only the caller's quota.

HeaderMeaning
X-AS-Quota-LimitThat key's daily byte cap.
X-AS-Quota-RemainingBytes remaining in the current UTC day for that key.
X-AS-Quota-ResetNext UTC midnight, YYYY-MM-DDT00:00:00Z.

503Retry-After

Body statusTypical Retry-AfterMeaning
empty60Not enough conditioned bytes. Nothing was taken.
sandbox_paused300The public reserve is held so keyed callers keep priority.
signing_unavailablenonesign=true on an instance without an Ed25519 key.

There is never a partial response. Request a key on the access page. The endpoint returns 201 received. The secret is issued out of band.

02Integration patterns

Direct serve-once

The API output is the result: a draw, a shuffle, a weighted choice, a UUID, or raw bytes. Ask forsign: true when someone who was not in the request needs to check it. Bind a nonce you already published. Do not retry a successful call to get a better sample. Do not cache the body. A 503 with status empty means the operation did not happen.

Entropy-in-depth

When a process already has a CSPRNG and must not stall, mix conditioned bytes with fresh operating-system entropy through HKDF-SHA-256. Salt AS-HKDF-v1, infoanalogseed-csprng-reseed, 32-byte output. The IKM is the physical sample concatenated with 32 local bytes. If the API returns 429 or 503, keep the operating-system generator and label it. That fallback is not avalanche.

1import hashlib
2import hmac
3import json
4import os
5import urllib.error
6import urllib.request
7
8SALT = b"AS-HKDF-v1"
9INFO = b"analogseed-csprng-reseed"
10
11
12def hkdf_sha256(ikm: bytes, salt: bytes, info: bytes, length: int = 32) -> bytes:
13 if not salt:
14 salt = b"\x00" * 32
15 prk = hmac.new(salt, ikm, hashlib.sha256).digest()
16 okm = b""
17 previous = b""
18 counter = 1
19 while len(okm) < length:
20 previous = hmac.new(prk, previous + info + bytes([counter]), hashlib.sha256).digest()
21 okm += previous
22 counter += 1
23 return okm[:length]
24
25
26def reseed(physical: bytes) -> bytes:
27 local = os.urandom(32)
28 return hkdf_sha256(physical + local, SALT, INFO, 32)
29
30
31def random_bytes_or_local(base: str, key: str, n: int = 32) -> tuple[bytes, str]:
32 """Return (material, source). source is 'mixed' or 'os-csprng'."""
33 url = f"{base}/v1/random?bytes={n}&format=hex"
34 req = urllib.request.Request(url, headers={"Authorization": f"Bearer {key}"})
35 try:
36 with urllib.request.urlopen(req, timeout=10) as res:
37 body = json.load(res)
38 physical = bytes.fromhex(body["bytes"])
39 return reseed(physical), "mixed"
40 except urllib.error.HTTPError as exc:
41 if exc.code not in (429, 503):
42 raise
43 return os.urandom(32), "os-csprng"

03AS-RECEIPT-V1

A receipt is an Ed25519 signature over one serve-once response. It is an origin attestation. It is not a VRF: the bytes are a physical measurement, not a function of the signing key. Asking twice is two draws.GET /v1/pubkey publishes the 32-byte public key. key_idis the first 16 hex characters of SHA-256 of that key.

The signed message is the ASCII prefix AS-RECEIPT-V1, a newline, and the UTF-8 canonical JSON of payload. Canonical JSON sorts object keys, uses no insignificant whitespace, and escapes non-ASCII as \uXXXX.

FieldValue
bytes_consumedPool bytes actually removed for this response
endpoint/v1/random, /v1/draw, or /v1/sandbox/draw
issued_atUTC timestamp YYYY-MM-DDTHH:MM:SSZ
key_id16 hex characters, SHA-256(public key)[:16]
nonceThe caller's nonce, or JSON null
output_sha256Hex SHA-256 of the raw bytes, or of canonical JSON of data
policyCP2, CP1, or CP1,CP2 in pool order, without duplicates
receipt_idRandom UUID for this statement, not an input to the entropy
request_sha256Hex SHA-256 of the canonical JSON of the normalized request, credentials excluded
sourceavalanche
  1. version is AS-RECEIPT-V1.
  2. The Ed25519 signature verifies over that message under the published public key.
  3. payload.key_id equals SHA-256(public key)[:16].
  4. output_sha256 matches the served bytes or the canonical JSON of data.
  5. If you pass an expected nonce, payload.nonce equals it exactly.

Check a pasted response on /verify, or download the standalone verifiers below. Changing one bit of the output fails the check.

04analogseed-mcp

The official MCP server speaks stdio. Cursor, Claude Desktop, and other clients draw the same public primitives. With ANALOGSEED_API_KEY set, draws usePOST /v1/draw and raw bytes use GET /v1/random. Without a key, draws use the public sandbox. A 429 or 503 includes Retry-After when the API sent it. The key-request call does not return a secret.

VariableDefaultRole
ANALOGSEED_API_URLhttps://api.analogseed.devAPI origin, no trailing path.
ANALOGSEED_API_KEYunsetBearer token. Omit it to stay on the sandbox.

Cursor reads .cursor/mcp.json in the project, or the user-level MCP config. Claude Desktop reads claude_desktop_config.json(~/Library/Application Support/Claude/ on macOS,%APPDATA%\Claude\ on Windows). Restart the client after saving.

1{
2 "mcpServers": {
3 "analogseed": {
4 "command": "npx",
5 "args": ["-y", "analogseed-mcp"],
6 "env": {
7 "ANALOGSEED_API_URL": "https://api.analogseed.dev",
8 "ANALOGSEED_API_KEY": "as_live_..."
9 }
10 }
11 }
12}

Sandbox ceilings: raw at most 64 bytes, uuids at most 4, integers, floats, and weighted draws at most 16, shuffle at most 50 items. sign=true asks for an AS-RECEIPT-V1 receipt. That signature is not a VRF.

ToolWhat it calls
draw_integerskind=integers. Bitmask rejection. replacement=false is partial Fisher–Yates.
draw_floatskind=floats. Uniform in [min, max).
shuffle_itemskind=shuffle.
weighted_choicekind=weighted. Positive integer weights.
draw_uuidskind=uuids.
random_bytesWith a key: GET /v1/random. Sandbox: kind=raw. Encoding is hex or base64.
service_statusGET /v1/status. Coarse readiness only.

05OpenAPI 3.1

Every public operation and component schema. A download of the same document is in the next section.

OpenAPI 3.1.0 · Analog Seed 1.3.0. Conditioned avalanche entropy from a physical noise source. Served bytes are one-time. An optional Ed25519 signature is an AS-RECEIPT-V1 entropy receipt, not a verifiable random function.

Draw an unbiased primitive

POST /v1/draw

Turns serve-once avalanche bytes into raw, integers, floats, a shuffle, a weighted choice, or UUID version 4. Integer ranges use bitmask rejection, not modulo. Leftover rejection bytes are discarded. sign=true attaches an Ed25519 AS-RECEIPT-V1 entropy receipt.

Auth: BearerAuth · Entropy

BodyRequired

application/json

kind = raw

FieldTypeNotes
bytes *integer1…65536
encoding"hex" | "base64"default "hex"
kind *const "raw"
noncestring | nulllength 1…128, default null
signbooleanAttach an Ed25519 `AS-RECEIPT-V1` origin receipt. · default false

kind = integers

FieldTypeNotes
count *integer1…1024
kind *const "integers"
max *integer
min *integer
noncestring | nulllength 1…128, default null
replacementbooleandefault true
signbooleanAttach an Ed25519 `AS-RECEIPT-V1` origin receipt. · default false
stepinteger1……, default 1

kind = floats

FieldTypeNotes
count *integer1…1024
kind *const "floats"
maxnumberdefault 1
minnumberdefault 0
noncestring | nulllength 1…128, default null
signbooleanAttach an Ed25519 `AS-RECEIPT-V1` origin receipt. · default false

kind = shuffle

FieldTypeNotes
items *—[]
kind *const "shuffle"
noncestring | nulllength 1…128, default null
pickinteger1…512
signbooleanAttach an Ed25519 `AS-RECEIPT-V1` origin receipt. · default false

kind = weighted

FieldTypeNotes
choices *object[]
countinteger1…256, default 1
kind *const "weighted"
noncestring | nulllength 1…128, default null
replacementbooleandefault true
signbooleanAttach an Ed25519 `AS-RECEIPT-V1` origin receipt. · default false

kind = uuids

FieldTypeNotes
countinteger1…256, default 1
kind *const "uuids"
noncestring | nulllength 1…128, default null
signbooleanAttach an Ed25519 `AS-RECEIPT-V1` origin receipt. · default false

OutResponses

StatusMeaning
200Unbiased primitive derived from serve-once avalanche bytes. Headers: X-AS-Policy, X-AS-Quota-Limit, X-AS-Quota-Remaining, X-AS-Quota-Reset, X-AS-Source.
400Invalid request body, parameter out of range, or body larger than 64 KiB.
401Missing, malformed, or invalid Bearer API key.
429Daily byte quota exceeded (`quota_exceeded`) or per-minute rate limit reached (`rate_limited`).
503Entropy pool temporarily exhausted (`empty`) or signing key unavailable.

Schemas19 components

FieldTypeNotes
bytes_consumed *integer1……
policy *stringexample "CP2"
source *"avalanche"

06Verifiers

The Python tool prints valid and exits 0, or printsinvalid: … on stderr and exits 1.--pubkey-hex takes 64 hex characters.--expect-nonce is optional. The JavaScript file is the same checks on WebCrypto.

python verify_receipt.py receipt.json --pubkey-file pubkey.json --expect-nonce ticket-1
node verify_receipt.mjs receipt.json --pubkey-file pubkey.json --expect-nonce ticket-1

verify_receipt.py

Python. Needs the cryptography package.

verify_receipt.mjs

Node 20+ and this browser. No npm packages.

openapi.json

OpenAPI 3.1 snapshot of the public API.