API
The raw signal, as JSON
The same graph the feed reads, over HTTP: companies with their momentum and runway, a full dossier payload per company, and the event stream that drives both. Built for a quant pipeline or a BD team that wants the signal inside its own tooling.
The API is the Feed / API tier. It starts at $40,000 a year and it is sold on a call, because the scope is different for every buyer. Keys are issued after an agreement is in place, so there is no self-serve signup here and this page is a spec rather than a quickstart. Talk to us or see the tiers.
Authentication
Every request carries your key as a bearer token. Keys are stored hashed on our side, which means we cannot show you a key again after it is issued: keep it somewhere safe, and if you lose it we revoke it and issue another.
curl https://biodatica.com/api/v1/companies?area=oncology \ -H "Authorization: Bearer YOUR_KEY"
Keys belong to an account, and they carry that account’s plan. A key on an account without API access is rejected rather than silently downgraded. A key can be revoked from your account at any time.
Rate limiting is 120 requests per minute, per key, and it is counted per key rather than per IP so that one customer’s backfill cannot throttle another customer sharing an egress address. Over the limit you get a 429 with a retry-after header. If your integration needs a higher ceiling, that is a conversation and not a support ticket.
Endpoints
Base URL https://biodatica.com/api/v1. Everything is JSON, and all timestamps are epoch milliseconds in UTC, matching the graph’s own storage.
/api/v1/companies
The feed as data: companies matching your filters, each carrying its freshest event. Ranked by momentum unless you say otherwise.
| area | One of oncology, neuro, immunology, rare, other. Omit for every area your plan covers. |
| stage | One of Grant-only, Formed, Preclinical, IND-enabling, Phase I, Phase II, Phase III, Filed/Approved. |
| q | Substring match against the company name and its known aliases. |
| minMomentum | Integer 0 to 100. Only companies at or above it. |
| maxRunway | Integer months. Only companies with a modeled runway at or below it. Companies with no raise on file have no runway and are excluded by this filter. |
| sort | momentum (default), recent, or runway. |
| limit | Rows to return. Defaults to 50, and clamps to your plan's ceiling of 5,000. |
| offset | Rows to skip, for paging. |
/api/v1/companies/:id
One company, by id or by slug: the company record, its resolved crosswalk (grants, trials, patents, filings), its link evidence, and its recent events. This is the dossier, as data. No paging, so no page object.
{
"data": {
"company": { … }, // the shape below
"events": [ … ],
"grants": [ … ], "trials": [ … ],
"patents": [ … ], "filings": [ … ],
"link_evidence": [ … ] // method + score behind every edge
},
"notice": "…"
}Returns 404 for an unknown id, and 403 when the company sits outside the therapeutic areas your plan covers.
/api/v1/events
The raw signal feed: scored events across the graph, newest first. Poll it on a cursor to keep your own store in sync.
| since | Epoch milliseconds. Only events that occurred at or after it. |
| type | One of grant_new, grant_renewal, sbir_phase2, trial_registered, and the rest of the event vocabulary below. |
| company | Restrict to one company, by id or slug. |
| limit | Events to return. Defaults to 100, maximum 500 per request. Page with offset. |
| offset | Events to skip, for paging. |
What comes back
Every response carries the same envelope. data is the payload, page appears on the list endpoints, and notice is the standing disclosure that rides on the JSON as well as the pages. Errors replace all of it with a single error object.
{
"data": [ … ],
"page": { "limit": 50, "offset": 0, "count": 50, "has_more": true },
"notice": "Informational only. Not investment advice. …"
}
// and on an error, at any status:
{ "error": { "code": "rate_limited", "message": "…" } }Inside data, field names match the graph’s own schema, so what you read here is what the product reads internally. A company:
{
"id": "…",
"name": "…",
"slug": "…",
"aka": "…", // JSON array of aliases, or null
"city": "…", "state": "…", "country": "US",
"website": "…", // or null
"therapeutic_area": "oncology",
"modality": "…", // free text, or null
"stage": "Phase I",
"momentum": 84, // 0–100
"momentum_delta": 6, // change since the last recompute
"runway_months": 7, // null = unknown. NEVER 0 for unknown.
"last_raise_cents": 2600000000,
"last_raise_at": 1750000000000,
"ai_brief": "…", // generated commentary, or null
"ai_brief_at": 1750000000000,
"confidence": 0.91, // crosswalk confidence
"curated": 1, // 1 = a human confirmed the edges
"updated_at": 1750000000000
}An event:
{
"id": "…",
"company_id": "…",
"type": "trial_registered",
"weight": 22,
"headline": "Phase I trial registered",
"occurred_at": 1750000000000, // the date the FILER reported
"source": "trials",
"source_url": "https://clinicaltrials.gov/study/NCT…",
"payload": "…" // JSON detail
}Two field notes worth reading twice. runway_months is null when it is unknown and 0 only when the model says the last disclosed raise is spent. Do not coalesce null to zero in your pipeline; they mean opposite things. And occurred_at is the filer’s date, not our ingest date, so a backfilled event can land in your feed with a timestamp in the past.
Enumerations
These are closed sets. We may add to them; we will not silently redefine one.
- Event types
- grant_new · grant_renewal · sbir_phase2 · trial_registered · trial_phase_advance · trial_recruiting · trial_completed · patent_granted · patent_assignment · raise_formd · fda_designation · entity_formed
- Stages, earliest to latest
- Grant-only · Formed · Preclinical · IND-enabling · Phase I · Phase II · Phase III · Filed/Approved
- Therapeutic areas
- oncology · neuro · immunology · rare · other
Errors
| 400 | A parameter is outside its allowed set. |
| 401 | No key, a malformed key, or one that has been revoked. |
| 402 | The key is valid, but its account is not on a tier that includes API access. |
| 403 | The account is suspended, or the company sits outside the therapeutic areas its plan covers. |
| 404 | No company with that id or slug. |
| 429 | Over the 120-per-minute ceiling on this key. Back off and retry after retry-after. |
| 5xx | Ours. Retry with backoff. |
What you can do with it
The underlying federal filings are public domain and yours to do anything with. The resolved crosswalk, the momentum and runway scores, and the generated briefs are the thing you are licensing, and they are for your firm’s internal use. Redistributing them, or building a competing dataset out of them, needs a different agreement. The full terms, and our disclosures, which apply to the API exactly as they apply to the app.
A field you need that is not here? The graph probably has it, and the endpoints are shaped around what buyers actually ask for. signal@biodatica.com