Nucleora API

Nucleora API documentation

https://api.nucleora.org — authentication, keys, scopes, limits, and the things this API does not do.

Read the FAQ before you plan work against this API. Four of its answers will change your design: there is no enforced per-key rate limit, the API is not end-to-end encrypted and Nucleora can read your payloads, there is no SLA, and a lost key is unrecoverable. Each is quoted from the source that decides it.
Questions answered on this page
  1. How do I get a key, and what scopes exist?
  2. What happens if I lose my key?
  3. Is there a rate limit?
  4. Is my data encrypted, and can Nucleora read it?
  5. Is the API versioned, and what happens on a breaking change?
  6. What is the uptime target or SLA?
  7. Can I self-host?
  8. What does the API not do?
  9. How much of the API is actually documented here?
  10. What do errors look like, and which ones mean it is your fault?
  11. Anything else I should know before I build against this?

What this page covers, honestly

The backend registers 1,609 routes, of which 1,589 are distinct /api/ endpoint paths across 154 families. This page documents the authentication and key-management surface in full, names every family with its size and the scope that reaches it, and gives one worked example per request shape. It does not document 1,589 request and response bodies, and no part of it should be read as implying otherwise.

Every number on this page carries the command that re-derives it, in the table at the end. If a number here disagrees with that command's output, the number is wrong — report it.

Authenticating

Two credential types, and they are not interchangeable:

Unauthenticated requests get 401 with code AUTH_REQUIRED. That includes unknown paths, so the API cannot be probed for endpoints without a credential.

Worked examples

Every endpoint named below was checked against the live route table at build time. backend means the Flask application answers it; edge means the Cloudflare Worker does and the request never reaches the backend.

Check the service is up (no credential required)

curl -s https://api.nucleora.org/health

Answered by the edge worker, which probes the backend's /api/health and reports both halves. Returns 200 when both are up and 503 when the backend is unreachable; it never returns 200 while the backend is down.

answered by: /healthedge

Issue a key (bearer token, not an API key)

curl -s -X POST https://api.nucleora.org/api/v1/keys \
  -H "Authorization: Bearer $NUCLEORA_JWT" \
  -H "Content-Type: application/json" \
  -d '{"name":"ci-pipeline","scopes":["read","design"],"rate_limit":1000}'

scopes is required and an omitted list is refused rather than treated as full access. The response body contains the plaintext key exactly once.

answered by: /api/v1/keysedge

List your keys (prefixes only)

curl -s https://api.nucleora.org/api/v1/keys \
  -H "Authorization: Bearer $NUCLEORA_JWT" 

Returns key_prefix, never the key. Scoped to the subject of the bearer token.

answered by: /api/v1/keysedge

Revoke a key

curl -s -X DELETE https://api.nucleora.org/api/v1/keys/$KEY_ID \
  -H "Authorization: Bearer $NUCLEORA_JWT" 

The row is marked revoked, not deleted, so an audit of what the key did survives. A key id that is not yours returns 404 rather than 403, so key ids are not enumerable across accounts.

answered by: /api/v1/keysedge

A read call with a key (needs the read scope)

curl -s https://api.nucleora.org/api/catalog \
  -H "X-API-Key: $NUCLEORA_KEY" 

GET on a known family demands read. A key without it gets 403 with code AUTH_SCOPE_INSUFFICIENT -- not 401, because the key is valid and reissuing it would fail identically.

answered by: /api/catalogbackend

A compute call with a key (needs the family scope)

curl -s -X POST https://api.nucleora.org/api/rna/analyze \
  -H "X-API-Key: $NUCLEORA_KEY" \
  -H "Content-Type: application/json" \
  -d '{"sequence":"GGGAAACCC"}'

A POST under /api/rna/ demands the rna scope. The request body shape is the backend's and is not documented here -- see “what this site does not document”.

answered by: /api/rna/analyzebackend

A family outside the known list needs an all-scopes key

curl -s -X POST https://api.nucleora.org/api/vaccine/antigen/prioritize \
  -H "X-API-Key: $NUCLEORA_STAR_KEY" \
  -H "Content-Type: application/json" \
  -d '{}'

vaccine is not in the scope map's known list, so this path demands the literal * scope. This applies to 1,062 of 1,589 distinct endpoints.

answered by: /api/vaccine/antigen/prioritizebackend

FAQ

How do I get a key, and what scopes exist?

Sign in to obtain a bearer token, then POST /api/v1/keys with a name and an explicit scope list. Key management is answered by the edge worker and is never proxied to the backend, because the plaintext key must not reach the backend or its logs.

Key management accepts a bearer token only, deliberately not an API key. A key that can mint keys cannot be meaningfully revoked: the holder mints a replacement before you finish revoking the first.

A scope is a plain string. The scopes the gate can ask for are derived from the second path segment, and there are 11 families in the known list: analysis, cad, catalog, codon, crispr, design, dna, health, organisms, protein, rna. Read verbs ask for read; other verbs ask for the family name. Any path whose family is not in that list asks for the literal *.

There is no scope hierarchy and no wildcard pattern matching. * is matched as an exact string, so design* or re* grant nothing. An omitted or empty scope list is refused at issue time, and an empty scopes column is read as no scopes, not all scopes.

Evidence, read out of the live repository at build time:

Which scope a path demands

cloudflare/api-worker/worker.js lines 924–934

The second path segment selects the scope. GET and HEAD need read; anything else needs the scope named after the family. A family not in KNOWN demands the literal *, so a route added tomorrow is not open by default -- and so most of the API today is reachable only with a * key. The counts are below.

924function requiredScopeFor(path, method) {
925 const p = String(path || '');
926 if (!p.startsWith('/api/')) return '*';
927 const seg = p.split('/')[2] || '';
928 const KNOWN = new Set(['catalog', 'health', 'organisms', 'codon', 'dna', 'rna',
929 'protein', 'design', 'analysis', 'crispr', 'cad']);
930 if (!KNOWN.has(seg)) return '*';
This is the line that makes 1,062 of 1,589 distinct endpoints require an all-scopes key.
931 return (method === 'GET' || method === 'HEAD') ? 'read' : seg;
932}
933 
934 

How a scope is matched

cloudflare/api-worker/worker.js lines 916–919

Exact string membership, plus a written *. There is no hierarchy: a design key does not imply read, so a key that both reads and writes the design family must carry both scopes.

916function apiKeyHasScope(scopes, required) {
917 if (!required) return true;
918 return scopes.includes('*') || scopes.includes(required);
919}

What happens if I lose my key?

You issue a new one and revoke the old one. A lost key cannot be recovered, and Nucleora cannot recover it for you. Support cannot either -- there is nothing to look up.

The plaintext exists in exactly one place: the body of the 201 response to the request that created it. What is written to the database is sha256(key) and a 16-character prefix for identification. There is no column that holds the key and no code path that returns it twice, so a lost key cannot be displayed again by any means, including database access.

Practical consequence: capture the key field from the issue response into your secret store in the same step that creates it. Do not build a user interface that offers to show a key later.

Evidence, read out of the live repository at build time:

The key is returned once

cloudflare/api-worker/worker.js lines 1048–1049

Only sha256(key) is written to the database. The response says so in the body, so a client author does not build a “show key again” screen that cannot work.

1048 notice: 'This key is shown once and is not recoverable. Store it now. ' +
1049 'Only its hash is kept, so it cannot be displayed again.',

The table a key row lives in

cloudflare/migrations/0001_initial.sql lines 90–104

key_hash is the only representation of the key. There is no column that could hold the plaintext. Note the 4 incompatible definitions of this table across the repository's SQL files, covered in the FAQ.

90CREATE TABLE IF NOT EXISTS api_keys (
91 id TEXT PRIMARY KEY,
92 user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
93 name TEXT NOT NULL,
94 key_hash TEXT UNIQUE NOT NULL,
95 key_prefix TEXT NOT NULL,
96 scopes TEXT,
97 rate_limit INTEGER DEFAULT 1000,
98 created_at INTEGER DEFAULT (strftime('%s', 'now') * 1000),
99 expires_at INTEGER,
100 last_used INTEGER,
101 usage_count INTEGER DEFAULT 0,
102 revoked INTEGER DEFAULT 0,
103 revoked_at INTEGER
104);

Is there a rate limit?

Per-key rate limiting is not enforced. Your key carries a rate_limit value, that value is read on every authenticated request, and it is then passed along without anything counting against it. Nothing in the request path decrements it, and no request is ever rejected for exceeding it.

The reason is in the code and is not an oversight. Both obvious implementations -- incrementing a usage counter in D1, or a KV counter -- are a write on every request, and this project has already exhausted its KV write quota exactly that way. The fix at the time made the IP-level limiter read-heavy. Adding a per-key write back onto the hot path would reintroduce a known outage, so the counter is deferred until it can be sized against a quota audit.

What is enforced is a per-IP limiter at the edge: 100 requests per 60 seconds, with a 300-second block when exceeded. It is approximate by design -- the counter is persisted roughly every 10 requests to conserve KV writes, and it keys on client IP, so several clients behind one NAT share a budget and one client spread across several edge locations can exceed it. Treat it as a floor you should stay well under, not a contract.

Design your client as though no limit protects the service: back off on 429, and do not rely on a limit to shape your own traffic. Sustained abuse is handled by an IP ban list, not by throttling.

Evidence, read out of the live repository at build time:

Per-key rate limiting is not enforced

cloudflare/api-worker/worker.js lines 1293–1304

Quoted in full rather than summarised, because the summary an integrator would expect from a documentation site (“rate limits apply”) is false.

1293 /* Per-key rate limiting is NOT enforced here yet, deliberately. The
1294 obvious implementations -- incrementing usage_count in D1, or a KV
1295 counter -- are a WRITE ON EVERY REQUEST, and this project has
1296 already exhausted the KV free tier exactly that way (the fix made
1297 checkRateLimit read-only). Adding one back on the hot path would
1298 reintroduce a known outage. The key's rate_limit is read and
1299 carried below so the backend can see it; the counter belongs in a
1300 Durable Object or an aggregated write, sized against the quota
1301 audit rather than guessed at here. Stated rather than silently
1302 skipped: a caller reading this code should not believe a limit is
1303 being applied when it is not. */
1304 apiKeyContext = keyCheck;

The limit that IS applied, per IP

cloudflare/api-worker/worker.js lines 108–113

Per CLIENT IP, not per key, and enforced with a deliberately read-heavy KV strategy: the counter is persisted roughly every writeThreshold requests, so the effective limit is approximate and a burst arriving across several edge locations can exceed it. Do not design a client around this number.

108const RATE_LIMIT = {
109 requests: 100, // Max requests per window
110 windowMs: 60000, // Window size (1 minute)
111 blockDurationS: 300, // Block duration in seconds (5 minutes)
112 writeThreshold: 10, // Only write to KV every N requests (reduces KV writes)
113};

Is my data encrypted, and can Nucleora read it?

Encrypted in transit. Nucleora can read your payloads. The API is not end-to-end encrypted, and no plan on the record makes it so.

The decision is written down. The recommendation is server-side encryption at rest with key custody separated from ciphertext custody -- Model 2 in the document quoted above -- and the document explicitly declines to ship end-to-end encryption as the default. It also names the cost in its own words: Nucleora can read every sequence its users send each other, and that remains true after the change.

Status matters here and the document is candid about it: it is marked decision proposed, awaiting owner sign-off. So what is deployed today is the weaker Model 1 -- transport-only, with application-layer plaintext in storage. Anyone who obtains a database export reads it.

Concretely, for an integrator: anything you send through this API should be treated as readable by Nucleora staff and by anyone who obtains Nucleora's database. If you hold sequences that must be unreadable by your vendor, do the sensitive step locally and send only what you can afford to disclose. This site is forbidden from using the words “end-to-end” or “zero-knowledge” as claims, by a rule written into the decision document itself.

Evidence, read out of the live repository at build time:

The encryption decision, as written

docs/security/ENCRYPTION_DECISION.md lines 357–359

Model 2 is server-side encryption at rest. The same document rejects Model 3 (end-to-end) as the default.

357**Adopt Model 2 -- server-side encryption at rest with separated key custody
358-- as the uniform model for all account-to-account sends, on web, desktop and
359API alike. Do not ship Model 3 as the default.**

What that costs you, in the document's own words

docs/security/ENCRYPTION_DECISION.md lines 374–377

If your threat model requires that Nucleora cannot read an unpublished construct, this API does not satisfy it. That is the answer, not a roadmap item.

374What is being traded away, named plainly: **Nucleora can read every sequence
375its users send each other, and this remains true after the change.** A user
376who needs their collaborator's unpublished construct to be unreadable by
377Nucleora does not get that from Model 2 and must not be told otherwise.

Words this site is forbidden to use

docs/security/ENCRYPTION_DECISION.md lines 392–396

This page is product copy. The rule binds it: you will not find “end-to-end” or “zero-knowledge” used as a claim anywhere on this site.

392- **The product copy must not overstate it.** The words "end-to-end",
393 "zero-knowledge" and "only you can read this" are false under Model 2. The
394 accurate sentence is: "Encrypted in transit and encrypted at rest with keys
395 Nucleora holds. Nucleora staff can access message contents; we log access."
396 

Is the API versioned, and what happens on a breaking change?

Partly, and the honest description is awkward. Key management lives at /api/v1/keys, with /api/keys accepted as an unversioned alias so an early integrator is not stranded. Both resolve to the same handler.

The rest of the API is not versioned. The backend registers 0 routes under /api/v1/: every functional endpoint lives directly under /api/<family>/ with no version segment. The only /api/v1/ paths that answer anything are the key-management routes, and those are answered at the edge, not by the backend.

So there is no version negotiation, no Accept version header, no deprecation header, and no sunset policy. A breaking change to a functional endpoint will reach you as a changed response shape on the same URL. Pin nothing; validate the responses you parse.

If you need a stable contract, the two things worth building on are the error envelope (error, code, timestamp, request_id) and the key-management surface, which is versioned precisely because third parties integrate against it.

What is the uptime target or SLA?

There is no SLA. There is no published uptime target, no credit for downtime, no support response commitment, and no status page.

What exists is one honest health endpoint. GET /health reports the edge worker and the backend separately, returns 503 when the backend is unreachable, and does not return 200 while the service cannot serve requests. It is not cached when degraded, because a cached 503 outlives an outage and a cached 200 hides one. Poll it yourself; it is the only availability signal on offer.

The backend is a single Cloud Run service behind one Cloudflare Worker. A backend request that exceeds 30 seconds returns 504, and an unreachable backend returns 502. Build retries and a circuit breaker into your client, because nothing here does that for you.

Can I self-host?

There is no supported self-hosting path and no published container image, and nobody has done it. What follows is a description of the shape of the system, not an offer.

The compute is a Flask application: 422 Python modules under engine/, 1,609 registered routes, served behind a Cloudflare Worker that does authentication, CORS, path sanitisation and proxying. The Worker is Cloudflare-specific: it depends on D1 for the key table and on two KV namespaces for the IP limiter and the ban list. Running the Flask half without the Worker means running with no authentication at all, because the key check lives at the edge.

The licensing position is not stated in a form an integrator can rely on, so treat self-hosting as unavailable rather than merely undocumented. If you need it, ask before building against the hosted API.

What does the API not do?

It does not enforce a per-key rate limit, it does not version its functional endpoints, it does not offer an SLA, and it cannot keep a payload from Nucleora. Those four are answered above. Beyond them:

Evidence, read out of the live repository at build time:

What is reachable without any credential

cloudflare/api-worker/worker.js lines 116–117

Two health endpoints, plus two path prefixes. The backend currently registers 0 routes under /api/v1/public/, so in practice the unauthenticated surface is the two health endpoints and nothing else.

116const PUBLIC_ROUTES_EXACT = ['/health', '/worker-health'];
117const PUBLIC_ROUTES_PREFIX = ['/api/v1/public/', '/.well-known/'];

How much of the API is actually documented here?

A small fraction, and the rest is not hidden. The backend registers 1,609 routes, of which 1,589 are distinct /api/ endpoint paths across 154 families. This site documents the authentication and key-management surface properly, names every family with its size, and gives one worked example per request shape. It does not document 1,589 request and response bodies and does not claim to.

Re-derive the counts yourself:

PYTHONPATH=. /Users/leocecrojas/Desktop/genelab/.venv/bin/python3 _internal/qa/dump_route_table.py >/dev/null 2>&1; /Users/leocecrojas/Desktop/genelab/.venv/bin/python3 -c 'import json; d=json.load(open("_internal/qa/_route_table.json")); print(len(d["rules"]))'

What this means for planning: you can rely on this page for how to authenticate, what a key can reach, and what the error envelope looks like. For the body shape of a specific endpoint you will need to ask, or read the response.

What do errors look like, and which ones mean it is your fault?

Every error from the edge is JSON with error, code, timestamp and request_id. Quote the request_id in a bug report; it appears in the worker's logs.

The distinction worth knowing is between 401, 403 and 503, because it is deliberate and it tells you who has to act:

A 503 with that code is the only authentication failure that is Nucleora's to fix. Do not loop on it.

Anything else I should know before I build against this?

Three things found while writing this page, kept here rather than quietly fixed in the prose.

Evidence, read out of the live repository at build time:

The table a key row lives in

cloudflare/migrations/0001_initial.sql lines 90–104

key_hash is the only representation of the key. There is no column that could hold the plaintext. Note the 4 incompatible definitions of this table across the repository's SQL files, covered in the FAQ.

90CREATE TABLE IF NOT EXISTS api_keys (
91 id TEXT PRIMARY KEY,
92 user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
93 name TEXT NOT NULL,
94 key_hash TEXT UNIQUE NOT NULL,
95 key_prefix TEXT NOT NULL,
96 scopes TEXT,
97 rate_limit INTEGER DEFAULT 1000,
98 created_at INTEGER DEFAULT (strftime('%s', 'now') * 1000),
99 expires_at INTEGER,
100 last_used INTEGER,
101 usage_count INTEGER DEFAULT 0,
102 revoked INTEGER DEFAULT 0,
103 revoked_at INTEGER
104);

What a Nucleora API key looks like

cloudflare/api-worker/worker.js lines 848–849

The accepted prefix is nk_. Keys this worker issues carry the longer nk_live_ prefix, so the stored prefix column and the accepted prefix are deliberately not the same string -- see the issue path below.

848const API_KEY_PREFIX = 'nk_';
849const API_KEY_HASH_RE = /^[a-f0-9]{64}$/;

Every endpoint family, and the scope that reaches it

Derived from the live route table. A family in the scope map's known list is reachable with read (for GET and HEAD) or with the family's own name; every other family requires a key carrying the literal * scope.

familydistinct endpointsscope that reaches it
/api/platform/181* only
/api/crispr/156read, crispr
/api/design/139read, design
/api/cad/106read, cad
/api/vaccine/103* only
/api/analysis/91read, analysis
/api/sequence/87* only
/api/vlab/82* only
/api/home/74* only
/api/databases/71* only
/api/subapp/63* only
/api/compliance/53* only
/api/emerging/42* only
/api/established/32* only
/api/settings/26* only
/api/lab/24* only
/api/photopharm/20* only
/api/rna/19read, rna
/api/dna/14read, dna
/api/explain/11* only
/api/viewport/10* only
/api/longevity/9* only
/api/subapp_protein_struct/9* only
/api/pathways/7* only
/api/virtual_lab/7* only
/api/planner/6* only
/api/mut/5* only
/api/enzyme_lab/4* only
/api/format/4* only
/api/branding/2* only
/api/codon_validate_construct/2* only
/api/design_complete_vaccine/2* only
/api/design_mrna/2* only
/api/ensure_table/2* only
/api/gc_quality_tradeoff/2* only
/api/market/2* only
/api/provenance/2* only
/api/selftest/2* only
/api/align_to_reference/1* only
/api/alphafold/1* only
/api/alphafold_structure/1* only
/api/analyze/1* only
/api/ancient_consensus/1* only
/api/annotate/1* only
/api/antigen_evidence/1* only
/api/apply_abortive_fix/1* only
/api/apply_cap_fix/1* only
/api/apply_cold_chain_fix/1* only
/api/apply_dsrna_fix/1* only
/api/apply_plus1_fix/1* only
/api/apply_runoff_fix/1* only
/api/autoconfig/1* only
/api/backbones/1* only
/api/batch_import/1* only
/api/cap_compatibility/1* only
/api/catalog/1read, catalog
/api/chembl/1* only
/api/clone/1* only
/api/codon_back_translation/1* only
/api/codon_concordance/1* only
/api/codon_dinucleotide_bias/1* only
/api/codon_full_validation/1* only
/api/codon_mfe_optimize/1* only
/api/codon_pair_bias/1* only
/api/codon_quality_score/1* only
/api/codon_structure_delta/1* only
/api/codon_tester_install/1* only
/api/codon_tester_run/1* only
/api/codon_testers/1* only
/api/cold_chain_stability/1* only
/api/colony_pcr_primer_design/1* only
/api/compare_organisms/1* only
/api/cpu_info/1* only
/api/deep_fetch/1* only
/api/design_multi/1* only
/api/diagnose_construct/1* only
/api/digest/1* only
/api/dose_anchors/1* only
/api/dose_calculator/1* only
/api/environmental_release_export/1* only
/api/enzyme_sites/1* only
/api/export_gff3/1* only
/api/fetch_protein/1* only
/api/fetch_sequence/1* only
/api/fix_all_chemistry/1* only
/api/fold/1* only
/api/gc_profile/1* only
/api/harmonize/1* only
/api/health/1read, health
/api/homology_search/1* only
/api/import/1* only
/api/in_silico_pcr/1* only
/api/in_silico_pcr_multiplex/1* only
/api/interpro/1* only
/api/ivt_advanced/1* only
/api/ivt_plasmid/1* only
/api/ivt_primer_preset/1* only
/api/job/1* only
/api/kegg/1* only
/api/kozak_score/1* only
/api/license/1* only
/api/lims_push/1* only
/api/lnp_encapsulation/1* only
/api/lnp_formulation/1* only
/api/lnp_reference_formulations/1* only
/api/lookup/1* only
/api/manufacturability/1* only
/api/manufacture/1* only
/api/map_png/1* only
/api/multi_align/1* only
/api/multiplex_pool/1* only
/api/ncbi_fetch/1* only
/api/ncbi_search/1* only
/api/ngs_fidelity/1* only
/api/optimize/1* only
/api/optimize_multi/1* only
/api/organism_info/1* only
/api/pairwise_align/1* only
/api/parts/1* only
/api/peptide_properties/1* only
/api/plan_linearization/1* only
/api/plasmid_genbank/1* only
/api/polya_design/1* only
/api/prebuilt_sequences/1* only
/api/primers/1* only
/api/project_budget/1* only
/api/protein_suite/1* only
/api/reoptimize_for_issue/1* only
/api/reoptimize_issue_kinds/1* only
/api/resolve_all_fixable/1* only
/api/restriction/1* only
/api/rfam/1* only
/api/rna_gel/1* only
/api/runoff_overhang/1* only
/api/sanger_qc/1* only
/api/scan_motifs/1* only
/api/scrub_motifs/1* only
/api/search_sequence/1* only
/api/signal_swaps/1* only
/api/simulate_ivt/1* only
/api/spec_sheet/1* only
/api/string_db/1* only
/api/structure_payload/1* only
/api/synthesis_order/1* only
/api/synthesis_products/1* only
/api/translation_ribbon/1* only
/api/uniprot/1* only
/api/uorf_scan/1* only
/api/utr5_unwinding/1* only
/api/vaccination_research/1* only
/api/vaccination_studio/1* only
/api/validate/1* only
/api/validate_protein/1* only
/api/virtual_gel/1* only

Every figure on this page, and how to re-derive it

Run from the repository root. These commands are the authority; the numbers above are their output as of this build.

figurevaluewhat it counts, and the command that re-derives it
api_endpoints1,589distinct /api/* endpoint paths
Unique rule STRINGS. A path registered for GET and POST is one endpoint; counting rule objects double-counts it.
PYTHONPATH=. /Users/leocecrojas/Desktop/genelab/.venv/bin/python3 _internal/qa/dump_route_table.py >/dev/null 2>&1; /Users/leocecrojas/Desktop/genelab/.venv/bin/python3 -c 'import json; d=json.load(open("_internal/qa/_route_table.json")); print(len({r["rule"] for r in d["rules"] if r["rule"].startswith("/api")}))'
api_families154distinct /api/<family> path families
Split on '/', matching how the scope gate parses a path. A substring match would merge rna with rna_structure.
PYTHONPATH=. /Users/leocecrojas/Desktop/genelab/.venv/bin/python3 _internal/qa/dump_route_table.py >/dev/null 2>&1; /Users/leocecrojas/Desktop/genelab/.venv/bin/python3 -c 'import json; d=json.load(open("_internal/qa/_route_table.json")); print(len({r["rule"].split("/")[2] for r in d["rules"] if r["rule"].startswith("/api/")}))'
api_family_analysis91distinct endpoints under /api/analysis/
Derived, not taken from the brief that commissioned this site: that brief's family sizes were close but not all correct.
PYTHONPATH=. /Users/leocecrojas/Desktop/genelab/.venv/bin/python3 _internal/qa/dump_route_table.py >/dev/null 2>&1; /Users/leocecrojas/Desktop/genelab/.venv/bin/python3 -c 'import json; d=json.load(open("_internal/qa/_route_table.json")); print(len({r["rule"] for r in d["rules"] if r["rule"].split("/")[1:3]==["api","analysis"]}))'
api_family_cad106distinct endpoints under /api/cad/
Derived, not taken from the brief that commissioned this site: that brief's family sizes were close but not all correct.
PYTHONPATH=. /Users/leocecrojas/Desktop/genelab/.venv/bin/python3 _internal/qa/dump_route_table.py >/dev/null 2>&1; /Users/leocecrojas/Desktop/genelab/.venv/bin/python3 -c 'import json; d=json.load(open("_internal/qa/_route_table.json")); print(len({r["rule"] for r in d["rules"] if r["rule"].split("/")[1:3]==["api","cad"]}))'
api_family_crispr156distinct endpoints under /api/crispr/
Derived, not taken from the brief that commissioned this site: that brief's family sizes were close but not all correct.
PYTHONPATH=. /Users/leocecrojas/Desktop/genelab/.venv/bin/python3 _internal/qa/dump_route_table.py >/dev/null 2>&1; /Users/leocecrojas/Desktop/genelab/.venv/bin/python3 -c 'import json; d=json.load(open("_internal/qa/_route_table.json")); print(len({r["rule"] for r in d["rules"] if r["rule"].split("/")[1:3]==["api","crispr"]}))'
api_family_design139distinct endpoints under /api/design/
Derived, not taken from the brief that commissioned this site: that brief's family sizes were close but not all correct.
PYTHONPATH=. /Users/leocecrojas/Desktop/genelab/.venv/bin/python3 _internal/qa/dump_route_table.py >/dev/null 2>&1; /Users/leocecrojas/Desktop/genelab/.venv/bin/python3 -c 'import json; d=json.load(open("_internal/qa/_route_table.json")); print(len({r["rule"] for r in d["rules"] if r["rule"].split("/")[1:3]==["api","design"]}))'
api_family_platform181distinct endpoints under /api/platform/
Derived, not taken from the brief that commissioned this site: that brief's family sizes were close but not all correct.
PYTHONPATH=. /Users/leocecrojas/Desktop/genelab/.venv/bin/python3 _internal/qa/dump_route_table.py >/dev/null 2>&1; /Users/leocecrojas/Desktop/genelab/.venv/bin/python3 -c 'import json; d=json.load(open("_internal/qa/_route_table.json")); print(len({r["rule"] for r in d["rules"] if r["rule"].split("/")[1:3]==["api","platform"]}))'
api_family_vaccine103distinct endpoints under /api/vaccine/
Derived, not taken from the brief that commissioned this site: that brief's family sizes were close but not all correct.
PYTHONPATH=. /Users/leocecrojas/Desktop/genelab/.venv/bin/python3 _internal/qa/dump_route_table.py >/dev/null 2>&1; /Users/leocecrojas/Desktop/genelab/.venv/bin/python3 -c 'import json; d=json.load(open("_internal/qa/_route_table.json")); print(len({r["rule"] for r in d["rules"] if r["rule"].split("/")[1:3]==["api","vaccine"]}))'
api_ip_block_s300block duration after exceeding the per-IP limit, seconds
Implemented as a KV ban entry, so it survives across edge locations in a way the counter itself does not.
grep -A4 'const RATE_LIMIT = {' cloudflare/api-worker/worker.js
api_ip_limit100per-IP requests allowed per window
The limit that IS enforced, as distinct from the per-key limit that is not.
grep -A4 'const RATE_LIMIT = {' cloudflare/api-worker/worker.js
api_ip_window_s60rate-limit window, seconds
windowMs in the worker, divided by 1000 here so the page reads in seconds.
grep -A4 'const RATE_LIMIT = {' cloudflare/api-worker/worker.js
api_ip_write_threshold10requests between KV persists of the IP counter
The reason the per-IP limit is approximate. Published because a client author who treats the limit as exact will be surprised in both directions.
grep -A4 'const RATE_LIMIT = {' cloudflare/api-worker/worker.js
api_key_bytes32bytes of entropy in an issued key
From crypto.getRandomValues. The figure the SHA-256 storage decision rests on.
grep -A6 'function randomKey()' cloudflare/api-worker/worker.js
api_keys_definitions4distinct api_keys column sets declared in cloudflare/**/*.sql
Deduplicated by normalised column set, so two files declaring the same columns count once. Files: cloudflare/auth-worker/schema-v3.sql, cloudflare/migrations/0001_initial.sql, cloudflare/migrations/0003_teams.sql, cloudflare/schema.sql
/Users/leocecrojas/Desktop/genelab/.venv/bin/python3 -c 'import sys; sys.path.insert(0,"cloudflare/info-worker"); import derive_api_figures as d; print(d._sql_api_keys_definitions()[0])'
api_known_families11families the scope gate recognises
Parsed out of requiredScopeFor(). Any family NOT in this set demands the literal '*' scope.
/Users/leocecrojas/Desktop/genelab/.venv/bin/python3 -c 'import re,sys; s=open("cloudflare/api-worker/worker.js").read(); m=re.search(r"const KNOWN = new Set\(\[(.*?)\]\);", s, re.S); print(len(re.findall(r"\x27([a-z0-9_]+)\x27", m.group(1))))'
api_known_listanalysis, cad, catalog, codon, crispr, design, dna, health, organisms, protein, rnathe recognised families, as written in the worker
Published as a list so a reader can see which scopes are askable without reading the worker.
/Users/leocecrojas/Desktop/genelab/.venv/bin/python3 -c 'import re,sys; s=open("cloudflare/api-worker/worker.js").read(); m=re.search(r"const KNOWN = new Set\(\[(.*?)\]\);", s, re.S); print(sorted(re.findall(r"\x27([a-z0-9_]+)\x27", m.group(1))))'
api_max_body_mb10maximum request body, MB
Enforced against the bytes actually read, not only the declared Content-Length.
grep 'MAX_BODY_SIZE =' cloudflare/api-worker/worker.js
api_public_prefix_routes0backend routes under the edge's unauthenticated /api/v1/public/ prefix
The size of the anonymous surface. Published so 'no unauthenticated surface' is a measurement rather than an assurance.
PYTHONPATH=. /Users/leocecrojas/Desktop/genelab/.venv/bin/python3 _internal/qa/dump_route_table.py >/dev/null 2>&1; /Users/leocecrojas/Desktop/genelab/.venv/bin/python3 -c 'import json; d=json.load(open("_internal/qa/_route_table.json")); print(len({r["rule"] for r in d["rules"] if r["rule"].startswith("/api/v1/public/")}))'
api_rule_objects1,605/api/* rule objects, method registrations counted separately
Published beside api_endpoints because the two differ and the larger one is the number usually quoted. Both are pinned so neither can be swapped for the other.
PYTHONPATH=. /Users/leocecrojas/Desktop/genelab/.venv/bin/python3 _internal/qa/dump_route_table.py >/dev/null 2>&1; /Users/leocecrojas/Desktop/genelab/.venv/bin/python3 -c 'import json; d=json.load(open("_internal/qa/_route_table.json")); print(len([r for r in d["rules"] if r["rule"].startswith("/api")]))'
api_scope_named527distinct /api endpoints reachable with a named scope
A 'read' or family-named key can reach these. The complement needs a '*' key, which is why both halves are published rather than just the flattering one.
PYTHONPATH=. /Users/leocecrojas/Desktop/genelab/.venv/bin/python3 _internal/qa/dump_route_table.py >/dev/null 2>&1; /Users/leocecrojas/Desktop/genelab/.venv/bin/python3 cloudflare/info-worker/derive_api_figures.py --json | /Users/leocecrojas/Desktop/genelab/.venv/bin/python3 -c 'import json,sys; print(json.load(sys.stdin)["api_scope_named"]["value"])'
api_scope_star1,062distinct /api endpoints that require the literal '*' scope
api_endpoints minus api_scope_named. This is the number that tells an integrator a scoped key is not useful for most of the API.
PYTHONPATH=. /Users/leocecrojas/Desktop/genelab/.venv/bin/python3 _internal/qa/dump_route_table.py >/dev/null 2>&1; /Users/leocecrojas/Desktop/genelab/.venv/bin/python3 cloudflare/info-worker/derive_api_figures.py --json | /Users/leocecrojas/Desktop/genelab/.venv/bin/python3 -c 'import json,sys; print(json.load(sys.stdin)["api_scope_star"]["value"])'
api_timeout_s30backend request timeout, seconds
A call exceeding it returns 504. This is the ceiling on any single unit of work submitted through the API.
grep 'REQUEST_TIMEOUT_MS =' cloudflare/api-worker/worker.js
api_v1_backend_routes0backend routes under /api/v1/
Expected to be 0 and published because it is 0: the versioning FAQ answer rests on this, and a reader is entitled to check it.
PYTHONPATH=. /Users/leocecrojas/Desktop/genelab/.venv/bin/python3 _internal/qa/dump_route_table.py >/dev/null 2>&1; /Users/leocecrojas/Desktop/genelab/.venv/bin/python3 -c 'import json; d=json.load(open("_internal/qa/_route_table.json")); print(len({r["rule"] for r in d["rules"] if r["rule"].startswith("/api/v1/")}))'
engine_modules422Python modules under engine/
Same command and same id the claims register uses, so the two sites cannot publish different values for it.
find engine -name '*.py' | wc -l | tr -d ' '
routes_total1,609total registered url_map rules
Every rule object, including non-/api routes and the separate objects a single path registers for different methods.
PYTHONPATH=. /Users/leocecrojas/Desktop/genelab/.venv/bin/python3 _internal/qa/dump_route_table.py >/dev/null 2>&1; /Users/leocecrojas/Desktop/genelab/.venv/bin/python3 -c 'import json; d=json.load(open("_internal/qa/_route_table.json")); print(len(d["rules"]))'