https://api.nucleora.org — authentication, keys, scopes,
limits, and the things this API does not do.
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.
Two credential types, and they are not interchangeable:
Authorization: Bearer <jwt>)
— a signed session token, verified at the edge with HMAC-SHA256.
Required for key management. Only HS256 is accepted, so an
alg-confusion token is rejected before its claims are read.
X-API-Key: nk_live_…) —
a long-lived credential for machine callers, checked by hash lookup against
the key table. Cannot be used to manage keys.
Unauthenticated requests get 401 with code
AUTH_REQUIRED. That includes unknown paths, so the API cannot be
probed for endpoints without a credential.
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.
curl -s https://api.nucleora.org/healthAnswered 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
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
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
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
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
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
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
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:
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.
| 924 | function 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 | |
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.
| 916 | function apiKeyHasScope(scopes, required) { |
| 917 | if (!required) return true; |
| 918 | return scopes.includes('*') || scopes.includes(required); |
| 919 | } |
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:
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.', |
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.
| 90 | CREATE 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 | ); |
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:
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; |
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.
| 108 | const 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 | }; |
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:
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 |
| 359 | API alike. Do not ship Model 3 as the default.** |
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.
| 374 | What is being traded away, named plainly: **Nucleora can read every sequence |
| 375 | its users send each other, and this remains true after the change.** A user |
| 376 | who needs their collaborator's unpublished construct to be unreadable by |
| 377 | Nucleora does not get that from Model 2 and must not be told otherwise. |
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 | |
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.
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.
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.
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:
/health and /worker-health requires a credential.
The two public path prefixes the worker recognises currently match
0 backend routes, so there is nothing to browse
anonymously. Unknown paths answer 401, not 404,
so you cannot probe for endpoints without a credential.
Content-Length. Larger uploads are rejected with
413.
Origin is refused with 403
CORS_ORIGIN_DENIED before authentication is considered.
Server-to-server calls, which send no Origin, are unaffected.
Evidence, read out of the live repository at build time:
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.
| 116 | const PUBLIC_ROUTES_EXACT = ['/health', '/worker-health']; |
| 117 | const PUBLIC_ROUTES_PREFIX = ['/api/v1/public/', '/.well-known/']; |
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.
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:
401 AUTH_INVALID_KEY, AUTH_KEY_REVOKED,
AUTH_KEY_EXPIRED -- your credential. Re-issue or un-revoke.
403 AUTH_SCOPE_INSUFFICIENT -- your key is valid and
identified, it simply does not carry the scope this path demands. The
message names the missing scope. Re-issuing the same key will fail
identically; issue one with the scope.
503 AUTH_SERVER_CONFIG_ERROR -- not your credential.
It means a signing key or database binding is unset on the server, so
nobody can authenticate. Retry later and report it. This code exists
because the alternative, returning 401, sent users to re-check a login that
was fine while the real cause was invisible in an access log full of
credential failures.
A 503 with that code is the only authentication failure that is Nucleora's to fix. Do not loop on it.
Three things found while writing this page, kept here rather than quietly fixed in the prose.
api_keys table has 4
incompatible definitions in this repository's SQL, all written
CREATE TABLE IF NOT EXISTS. Only one of them carries the
columns the worker's queries name. Whichever file was applied first wins,
and if it was not that one, key issuance fails at the
INSERT. If POST /api/v1/keys returns a server
error on a fresh deployment, this is the first thing to check.
nk_; this worker issues
keys beginning nk_live_. Nothing breaks today, but do not
write a client that assumes the two strings are the same, and do not assume
a nk_test_ prefix would be rejected -- it would not.
/api/v1/public/ is treated as unauthenticated by the
edge and matches 0 backend routes. If a
route is ever added under that prefix it becomes world-readable with no
further change, which is worth knowing if you are assessing this API's
security posture rather than just calling it.
Evidence, read out of the live repository at build time:
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.
| 90 | CREATE 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 | ); |
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.
| 848 | const API_KEY_PREFIX = 'nk_'; |
| 849 | const API_KEY_HASH_RE = /^[a-f0-9]{64}$/; |
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.
| family | distinct endpoints | scope that reaches it |
|---|---|---|
/api/platform/ | 181 | * only |
/api/crispr/ | 156 | read, crispr |
/api/design/ | 139 | read, design |
/api/cad/ | 106 | read, cad |
/api/vaccine/ | 103 | * only |
/api/analysis/ | 91 | read, 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/ | 19 | read, rna |
/api/dna/ | 14 | read, 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/ | 1 | read, 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/ | 1 | read, 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 |
Run from the repository root. These commands are the authority; the numbers above are their output as of this build.
| figure | value | what it counts, and the command that re-derives it |
|---|---|---|
api_endpoints | 1,589 | distinct /api/* endpoint paths Unique rule STRINGS. A path registered for GET and POST is one endpoint; counting rule objects double-counts it. |
api_families | 154 | distinct /api/<family> path families Split on '/', matching how the scope gate parses a path. A substring match would merge rna with rna_structure. |
api_family_analysis | 91 | distinct 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. |
api_family_cad | 106 | distinct 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. |
api_family_crispr | 156 | distinct 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. |
api_family_design | 139 | distinct 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. |
api_family_platform | 181 | distinct 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. |
api_family_vaccine | 103 | distinct 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. |
api_ip_block_s | 300 | block 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. |
api_ip_limit | 100 | per-IP requests allowed per window The limit that IS enforced, as distinct from the per-key limit that is not. |
api_ip_window_s | 60 | rate-limit window, seconds windowMs in the worker, divided by 1000 here so the page reads in seconds. |
api_ip_write_threshold | 10 | requests 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. |
api_key_bytes | 32 | bytes of entropy in an issued key From crypto.getRandomValues. The figure the SHA-256 storage decision rests on. |
api_keys_definitions | 4 | distinct 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 |
api_known_families | 11 | families the scope gate recognises Parsed out of requiredScopeFor(). Any family NOT in this set demands the literal '*' scope. |
api_known_list | analysis, cad, catalog, codon, crispr, design, dna, health, organisms, protein, rna | the recognised families, as written in the worker Published as a list so a reader can see which scopes are askable without reading the worker. |
api_max_body_mb | 10 | maximum request body, MB Enforced against the bytes actually read, not only the declared Content-Length. |
api_public_prefix_routes | 0 | backend 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. |
api_rule_objects | 1,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. |
api_scope_named | 527 | distinct /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. |
api_scope_star | 1,062 | distinct /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. |
api_timeout_s | 30 | backend request timeout, seconds A call exceeding it returns 504. This is the ceiling on any single unit of work submitted through the API. |
api_v1_backend_routes | 0 | backend 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. |
engine_modules | 422 | Python modules under engine/ Same command and same id the claims register uses, so the two sites cannot publish different values for it. |
routes_total | 1,609 | total registered url_map rules Every rule object, including non-/api routes and the separate objects a single path registers for different methods. |