The reef public API
A read-only window onto reef's biotech entity graph: 370,885 resolved companies, researchers, papers, patents, trials, and funding, each with provenance. No key required for the preview. This is the guide; the full, interactive API reference (params, schemas, language samples, try-it) is generated from the spec.
Overview
Base URL https://api.reef.bio/v1. Every endpoint is GET and returns JSON. Responses are stable, documented shapes (decoupled from our internals), and every record carries its source so you can judge it.
- format
- JSON; UTF-8
- methods
- GET only (read-only preview)
- versioning
- path-versioned at
/v1
Auth & rate limits
No authentication in the preview tier. Call the endpoints directly. Requests are rate-limited per IP; every response carries the headers:
- X-RateLimit-Limit
- requests allowed per window
- X-RateLimit-Remaining
- requests left in the current window
- X-RateLimit-Reset
- unix time when the window resets
Over the limit returns 429 with a Retry-After header.
A free API key raises the per-minute ceiling to 20 requests/min and adds a 500 requests/month budget on top, enough to build and test against, not to run in production. Pro drops the monthly budget and raises the rate to a flat 60 requests/min, for $99/mo.
Quickstart
# coverage by kindcurl https://api.reef.bio/v1/stats# search, then open a resultcurl "https://api.reef.bio/v1/entities?q=glutaminase&kind=company"curl https://api.reef.bio/v1/entities/<id>MCP server
The same graph as agent tools, over the Model Context Protocol. Point Claude, ChatGPT, or any MCP-capable client at the endpoint and it can search, traverse, rank, and pull dossiers from the graph as native tools, no glue code. It is a thin wrapper over this REST API, so it carries the identical read-only, public, rate-limited contract.
MCP https://core.reef.bio/mcp/
Streamable-HTTP transport. Add it to your client's MCP server config (most take a URL); no key required in the preview tier.
The tools mirror the endpoints on this page: search_entities, get_entity, traverse, aggregate, whitespace, concentration, rank_people, collaborators, dossier, comparables, and catalyst_calendar, each returning the same JSON shapes documented below.
Autocomplete
GET /v1/autocomplete
Fast type-ahead: lean {id, name, kind, slug} suggestions for a partial query. The entry point: resolve a name to an entity, then look it up or search.
- q
- partial text (required; empty returns no suggestions).
- kind
- restrict to one kind.
- limit
- suggestions to return, default 10, max 25.
curl "https://api.reef.bio/v1/autocomplete?q=crispr"Search & browse
GET /v1/entities
Full-text search (with q) or filtered browse (without). Returns lean records.
- q
- text query (name, ticker, domain…). Omit to browse newest-first.
- kind
- restrict to one kind.
- limit
- page size, default 25, max 100.
- offset
- pagination offset; use the response's
next_offset. - sort
- browse order (no
q):recent(default) orname.
Example response:
{
"query": "",
"kind": "company",
"filters": {},
"count": 2,
"total": null,
"offset": 0,
"limit": 2,
"next_offset": 2,
"next_cursor": "eyJrIjogIjIwMjYtMDgtMTJUMDI6MDQ6MDkuMjA0MDkyKzAwOjAwIiwgImkiOiAiMGQ4YTM4YTYtYmRkNy00YjMzLWFlMjItMWY3NzIzMzJlZWU2IiwgInMiOiAicmVjZW50In0=",
"results": [
{
"id": "78feeec8-25c7-4d7a-aa19-2db1c7aa1131",
"kind": "company",
"name": "Merz Aesthetics GmbH",
"slug": "merz-aesthetics-gmbh",
"identifiers": {
"domain": null,
"cik": null,
"ticker": null,
"uei": null,
"orcid": null,
"doi": null
},
"moa": {
"target": null,
"target_id": null,
"target_symbol": null,
"modality": null,
"secondary_modalities": [],
"mechanism": null,
"therapeutic_area": null,
"disease_id": null,
"disease_label": null,
"confidence": null
},
"platform_type": "therapeutics",
"product": null,
"pos": null,
"funding": null,
"signals": {
"prominence": 51.6,
"momentum": 0,
"rank_in_kind": 5339,
"degree": 5,
"activity_90d": 0,
"computed_at": "2026-08-11T06:17:19.634629+00:00"
},
"attributes": {
"tech_extraction": {
"count": 0,
"scanned_at": "2026-07-17T23:39:47.936386+00:00"
}
},
"provenance": {
"source": "clinicaltrials",
"observed_at": "2025-10-01T00:00:00+00:00",
"identity_confidence": null
},
"updated_at": "2026-08-12T02:04:09.204092+00:00",
"_links": {
"self": "/v1/entities/78feeec8-25c7-4d7a-aa19-2db1c7aa1131",
"edges": "/v1/entities/78feeec8-25c7-4d7a-aa19-2db1c7aa1131/edges"
}
}
]
}Filter by mechanism
GET /v1/entities?modality=…
Filter the graph by biology, not just text. reef's mechanism-of-action enrichment, index-backed and combinable with q and kind. Get valid values from /facets.
- target
- molecular target (e.g.
EGFR). - modality
- e.g.
antibody,small molecule. - mechanism
- mechanism of action (e.g.
agonist). - therapeutic_area
- e.g.
oncology.
curl "https://api.reef.bio/v1/entities?modality=antibody&therapeutic_area=oncology"The response envelope echoes the active filters under filters.
target matches an entity by either its primary target or any additional target it carries. A bispecific antibody or a combination trial can act on more than one target: target=EGFR and target=ERBB2 both return a patent that targets both, even though only one is its primary.
modality works the same way: an antibody-drug conjugate is both an antibody and a small-molecule payload, so modality=antibody and modality=small molecule can both return it. The secondary form is returned under moa.secondary_modalities.
technology is a separate axis from the mechanism filters above: a named method or platform (e.g. CRISPR-Cas9, directed evolution), not a drug's therapeutic form. One entity can employ several technologies and one technology spans many entities, so it's a graph edge (employs), not a single-valued field. Combine it with kind the same way as the other filters.
curl "https://api.reef.bio/v1/entities?kind=patent&technology=CRISPR-Cas9"Field selection
GET /v1/entities?fields=…
Trim any /entities query to just the top-level keys you need: smaller, faster payloads. id is always returned; sub-blocks (like moa) come whole. See /schema for the full field list.
curl "https://api.reef.bio/v1/entities?q=crispr&fields=id,name,moa"Get one entity
GET /v1/entities/{id}
The full record including per-field provenance. Supports ETag / If-None-Match (a match returns 304); a merged duplicate 301s to the surviving entity.
curl https://api.reef.bio/v1/entities/78feeec8-25c7-4d7a-aa19-2db1c7aa1131Example response (Merz Aesthetics GmbH, trimmed):
{
"id": "78feeec8-25c7-4d7a-aa19-2db1c7aa1131",
"kind": "company",
"name": "Merz Aesthetics GmbH",
"slug": "merz-aesthetics-gmbh",
"identifiers": {
"domain": null,
"cik": null,
"ticker": null,
"uei": null,
"orcid": null,
"doi": null
},
"moa": {
"target": null,
"target_id": null,
"target_symbol": null,
"modality": null,
"secondary_modalities": [],
"mechanism": null,
"therapeutic_area": null,
"disease_id": null,
"disease_label": null,
"confidence": null
},
"platform_type": "therapeutics",
"product": null,
"pos": null,
"funding": null,
"signals": {
"prominence": 51.6,
"momentum": 0,
"rank_in_kind": 5339,
"degree": 5,
"activity_90d": 0,
"computed_at": "2026-08-11T06:17:19.634629+00:00"
},
"attributes": {
"tech_extraction": {
"count": 0,
"scanned_at": "2026-07-17T23:39:47.936386+00:00"
}
},
"provenance": {
"source": "clinicaltrials",
"observed_at": "2025-10-01T00:00:00+00:00",
"identity_confidence": null,
"fields": [
{
"field": "canonical_name",
"source": "clinicaltrials",
"confidence": 1,
"observed_at": "2025-10-01T00:00:00+00:00"
},
{
"field": "platform",
"source": "llm:global.anthropic.claude-haiku-4-5-20251001-v1:0",
"confidence": 0.65,
"observed_at": "2026-08-12T02:04:09.204092+00:00"
}
]
},
"updated_at": "2026-08-12T02:04:09.204092+00:00",
"_links": {
"self": "/v1/entities/78feeec8-25c7-4d7a-aa19-2db1c7aa1131",
"edges": "/v1/entities/78feeec8-25c7-4d7a-aa19-2db1c7aa1131/edges"
}
}Get by slug
GET /v1/entities/by-slug/{kind}/{slug}
Resolve a stable, shareable URL (kind + slug) to the same record as the id form.
curl https://api.reef.bio/v1/entities/by-slug/company/<slug>Edges
GET /v1/entities/{id}/edges
One-hop relationships, both directions, live edges only. The response carries the flat edges list plus by_relation: the same edges grouped into typed cards ({relation: {count, edges}}), and relation_counts.
Example response:
{
"entity_id": "78feeec8-25c7-4d7a-aa19-2db1c7aa1131",
"edges": [
{
"relation": "sponsors",
"direction": "out",
"observed_at": null,
"neighbor": {
"id": "5a0fb65f-a5ef-4466-b0f6-48177dd61f21",
"kind": "trial",
"name": "Investigation of the Effectiveness and Safety of MRZF111 in the Treatment of Décolleté Wrinkles",
"slug": "nct-nct04177212",
"_links": {
"self": "/v1/entities/5a0fb65f-a5ef-4466-b0f6-48177dd61f21"
}
}
},
{
"relation": "sponsors",
"direction": "out",
"observed_at": null,
"neighbor": {
"id": "bed5caab-20f6-4f34-ad15-1e61c291f9b3",
"kind": "trial",
"name": "A Study of NT 201 in Adults With Moderate to Severe Platysma Prominence in Europe (PLATINUM EU)",
"slug": "nct-nct07210463",
"_links": {
"self": "/v1/entities/bed5caab-20f6-4f34-ad15-1e61c291f9b3"
}
}
}
],
"relation_counts": {
"founded_by": 1
}
}Traverse
GET /v1/entities/{id}/traverse
Bounded multi-hop walk from an entity (depth + fan-out capped).
- depth
- hops, default 2 (max 4).
- relations
- optional comma-separated allowlist of edge relations to follow.
curl "https://api.reef.bio/v1/entities/<id>/traverse?depth=2"Facets
GET /v1/facets
Valid mechanism-of-action filter values (target / modality / area) with counts. Call this first to discover what you can filter on.
curl "https://api.reef.bio/v1/facets?kind=company"Schema
GET /v1/schema
The data dictionary in one call: entity kinds + counts, the selectable entity_fields (for field selection), the moa_facets you can filter by, and the relations that edges use.
curl https://api.reef.bio/v1/schemaCoverage
GET /v1/stats
Live per-kind counts: the honest coverage of the graph.
{
"counts": {
"paper": 134574,
"person": 93280,
"trial": 44776,
"patent": 29848,
"catalyst": 19641,
"technology": 12371,
"company": 11559,
"product": 8757,
"filing": 6170,
"funding_event": 5376,
"institution": 1938,
"target": 1313,
"approval": 760,
"opportunity": 270,
"funder": 242,
"program": 10
},
"total": 370885
}The entity object
Every entity serializes to this shape:
- id
- stable UUID
- kind
- one of the entity kinds
- name
- canonical name
- slug
- stable slug for the by-slug URL
- identifiers
- external ids:
domain, cik, ticker, uei, orcid, doi - moa
- mechanism:
target, modality, mechanism, therapeutic_area, confidence - platform_type
- chassis/platform classification (companies)
- attributes
- curated, source-specific fields (raw internal data is never exposed)
- provenance
source, observed_at, identity_confidence, plus per-fieldfields[]on the detail endpoint- updated_at
- last change
- _links
- self + edges URLs
provenance is first-class: every field can tell you where it came from. List items omit the per-field provenance.fields.
Entity kinds
- company
- biotech / life-science companies · 11,559
- person
- researchers, founders, inventors · 93,280
- paper
- publications (PMID / DOI) · 134,574
- patent
- patents + inventors / assignees · 29,848
- trial
- clinical trials (NCT) · 44,776
- funder
- funding organizations (VCs, agencies) · 242
- funding_event
- rounds + grants · 5,376
- approval
- FDA drug approvals · 760
- opportunity
- grant / funding opportunities · 270
- program
- drug programs / assets, clustered by target + modality · 10
- product
- the engineered/offered thing a company makes or sells · 8,757
- institution
- research institutions / universities / labs · 1,938
- catalyst
- a dated upcoming or recent milestone (readout, PDUFA date, approval) · 19,641
- technology
- a named method or platform (e.g. CRISPR-Cas9, directed evolution) · 12,371
- target
- a biological target (gene/protein/pathway) linked to entities beyond their primary target · 1,313
Errors & status codes
Errors use a consistent envelope:
{ "error": { "code": "not_found", "message": "entity not found" } }- 200
- ok
- 301
- a merged entity → redirects to its surviving record
- 304
- not modified (your
If-None-MatchETag still matches) - 400
bad_request: malformed id or parameter- 404
not_found: no such entity- 429
rate_limited: slow down (carriesRetry-After)
Pagination
Browse responses (no q) carry an exact total plus offset, limit, and next_offset. Page by passing next_offset back as offset; when it's null, you've reached the end. Text search (q) is capped at 50 relevance-ranked results. Deep pagination over a ranking isn't meaningful.
Sync & change feed
GET /v1/changes
Keep a local mirror in sync without polling the whole graph. Pass since=<ISO 8601> for the first call, then echo back the response's next_cursor as after on each following call to walk forward without gaps or repeats. Rows are oldest-first; each carries op: upsert (created or updated) or merged (a duplicate folded into another record via merged_into, treat as a delete/redirect of id). This is a pull feed, not a push webhook: there is no server-initiated delivery, so poll on whatever cadence fits your use case.
curl "https://api.reef.bio/v1/changes?since=2026-01-01"Continue from where you left off: ?after=<next_cursor>. One of since or after is required; limit caps at 200 per page.
Caching
GETs send Cache-Control and a weak ETag. Send the ETag back as If-None-Match to revalidate cheaply. An unchanged entity returns 304 with no body. Detail + list responses cache for 60s; aggregates (/stats, /facets) for 300s.