Quickstart
First successful query in under a minute, on any of the three surfaces.
$ claude mcp add --transport http scientia-basis https://scientia-api-go.fly.dev/mcp \ --header "Authorization: Bearer $SCIENTIA_API_KEY" # tools: search_entities · get_entity · screen_name · # get_country_profile · get_country_factbook · list_datasets
$ curl -H "Authorization: Bearer $SCIENTIA_API_KEY" \ 'https://scientia-api-go.fly.dev/v1/countries/VN' { "corpus_release": "2026.08.7", "id": "country:VN", "schema": "Country", "caption": "Vietnam", "properties": { "iso_3166_1_alpha3": ["VNM"], "currency_code": ["VND"], "risk.cpi_score": ["41"], "risk.fatf_list": ["increased_monitoring"], … }, "provenance": { … } }
$ export SCIENTIA_API_URL=https://scientia-api-go.fly.dev $ scientia search "vladivostok" --schema Location $ scientia country VN # reference + risk.fatf_list · risk.cpi_score · risk.wgi_* $ scientia datasets # license, redistribution term and freshness
Core concepts
Entities & statements
An entity is a real-world thing: a country, a port, an airline, a sanctioned person. Entities don't store values directly — they are assembled from statements. A statement is one fact: a property, a value, the dataset that asserted it, the origin URL, the ingest run that produced it, and first-seen/last-seen timestamps. Nothing is hand-edited; nothing is silently merged.
Corpus releases
Ingest runs accumulate into an immutable release (labels look like 2026.08.7 — year, month, and a counter over every build attempted that month). Every response is stamped with the release that answered it, and an unpinned read returns what a read pinned to that current label returns. ?release= currently accepts only the served label. Older-artifact reads, historical time selectors and detailed historical diffs fail closed rather than mixing current identity with old statements. Precomputed totals remain available at /releases/diff?from=&to= as release bookkeeping, not reconstructed historical answers.
Current-release pinning works on: /entities/{id}, /countries/{code}, /countries/{code}/factbook, /datasets and /stats. /search and /screen refuse it with a 400: the full-text index holds current state only, so a pinned search would be right about which entities exist and wrong about their names. Answering plausibly and incompletely is the failure this machinery exists to remove, so those routes say no instead.
Entity ID conventions
Schemas
REST API reference
All endpoints return JSON. Every data route requires Authorization: Bearer <key>; GET /livez and GET /readyz stay open as health endpoints. Data routes are versioned under /v1. Keys are per-caller — hashed at rest, carrying scopes, a plan and its ceilings, and revocable individually. Every payload carries corpus_release. API and MCP references are published on this page; the hosted Go service does not expose a public OpenAPI or Swagger endpoint.
MCP server
Remote MCP over streamable HTTP, behind the same bearer token as the API.
$ claude mcp add --transport http scientia-basis https://scientia-api-go.fly.dev/mcp \
--header "Authorization: Bearer $SCIENTIA_API_KEY"Tools
Tool docstrings are written for the model — they are part of the product surface, not internal documentation.
Worked example — clearing a Vietnam-bound consignment
→ get_country_profile("VN") { "corpus_release": "2026.08.7", "id": "country:VN", "schema": "Country", "caption": "Vietnam", "properties": { "currency": ["Dong"], "currency_code": ["VND"], "iso_3166_1_alpha3": ["VNM"], "risk.cpi_score": ["41"], "risk.fatf_list": ["increased_monitoring"], "risk.wgi_rule_of_law": ["-0.314"], … }, "provenance": { "risk.cpi_score": [{ "dataset": "cpi", "run": 4, "source": "https://images.transparencycdn.org/images/CPI2025_Results.zip", "first_seen": "2026-08-08T16:26:02Z", "valid_from": "2025-01-01", "valid_to": "2026-01-01" }], … }, "notices": [{ "dataset": "cpi", "license": "CC-BY-ND-4.0", "attribution": "Source: Transparency International, Corruption Perceptions Index 2025 …" }], …, "factbook_fields_available": 307, "related_entity_counts": { "Location": 241, "Airport": 71, … } } → screen_name("Viktor Bout", threshold=0.7) { "corpus_release": "2026.08.7", "query": "Viktor Bout", "threshold": 0.7, "engine": "yente", "match_count": 1, "matches": [{ "id": "csl:SDN:8279", "schema": "Person", "caption": "BOUT, Viktor Anatolijevitch", "score": 0.909, "explanations": { "name_match": 0.909, "dob_year_disjoint": 0.0 } }] }
CLI reference
Point the CLI at the hosted corpus with SCIENTIA_API_URL=https://scientia-api-go.fly.dev + SCIENTIA_API_KEY (or SCIENTIA_API_TOKEN). Output is JSON by default — pipe it straight into agent tools; add --pretty for humans.
Screening semantics
Screening returns candidates, not verdicts. Scores are specific to the engine that produced them; the default threshold (0.7) is caller-tunable. Every response names its engine and corpus release. Yente is used only when its verified export generation matches the serving corpus. An unavailable or different-generation index uses the current corpus's lexical fallback with an explicit degradation reason, never an older index labelled as current.
Initial launch is pinned to snapshot 2026.10.2. Its matching index is not yet aligned, so screening currently uses lexical-v0. This fallback does not provide Yente's transliteration-aware matching. Clients that require that capability must reject degraded responses rather than interpret an empty result as clearance. Source timestamps, not the snapshot label, establish the age of individual records.
Plans & limits
One usage-based model. Plans differ in volume alone — how many credits they carry and how fast you may spend them. Not in which data they can see, and not in which matcher answers them. Every plan clears the same datasets and reaches the same screening engine, and tests in the repository exist to keep both true.
The paid tier used to withhold the stronger matcher. That is gone: the engine is a fixed cost whether or not a free key queries it, credits already separate the plans, and shipping a knowingly weaker matcher for sanctions screening — where a miss has consequences — was not a boundary worth defending. A free key screens with exactly what a paying one screens with.
What a credit is
A credit is one unit of the weight each operation declares. It is not a separate currency and there is no conversion table — the weights below are the ones the API meters with, published so you can price a workload before you run it.
The weight is declared per operation and never derived from what came back. A search that matches nothing costs the same as one that matches fifty — which is a deliberate privacy property, not an accounting shortcut: your bill does not disclose your results. Nothing about which names you screened is recoverable from what you were charged.
Reading your balance
Every response carries its own accounting, so you never have to open a dashboard to find out where you stand.
For the full picture — plan, ceilings, spend this period, and a breakdown by key and by operation — call GET /me. It costs no credits, and it keeps answering when you are out of them: a 429 you cannot explain is worse than the refusal itself. It is still rate limited.
Credits and ceilings belong to the account, not to each key. Several keys — production, staging, a colleague's — draw on one allowance and appear on one bill, and /me attributes spend per key so you can see which of them is consuming it.
Exhausting a limit returns 429 with Retry-After where a retry can succeed. A daily ceiling also applies — roughly a third of the month's credits — so a runaway loop cannot spend a month of allowance in an afternoon.
The free plan's honest limit
2,500 credits is roughly 1,250 screens or 2,500 entity fetches per month: enough to build against and evaluate properly, not enough to run a business on. That is the whole of the limit — the data and the matcher are the same ones a paid plan gets.
What every plan shares is the engine's own limits, which are a property of the matcher rather than of the plan. Every screening response names the engine that answered it, so a degraded answer is labelled rather than assumed — see Screening semantics.
Datasets & licensing
license is an SPDX identifier — never prose, never a legal conclusion — and redistributable is the obligation that binds us when we serve it: public, attribution, share-alike, no-derivatives, non-commercial, proprietary, unknown. The two are independent, and unknown fails closed. Both, plus the prose note and the exact attribution line a licence requires, are machine-readable via GET /datasets.
Sanctions screening runs against primary sources: us_csl and ofac_sdn are US government works in the public domain, uk_sanctions is OGL v3.0, and eu_sanctions is CC-BY-4.0. The latter three require attribution wherever their values surface, so every response that carries one of their hits also carries a notices entry naming the source and its licence. Per-key entitlements enforce every dataset's terms at the query layer — a key cannot reach a dataset it does not clear by any route, including as a count.