Build on the data.

A free, read-only JSON API over every regulation we track. No keys, no signup — the same data that powers this site.

Base URL www.health-signal.com
Authentication None
Rate limit 100 req / min
Format JSON

Endpoints

GET /api/regulations

The core endpoint. Returns tracked regulations, filterable and paginated. Sorted by effective publication date, most recent first.

ParameterTypeDescription
viewstringall (default) · healthtech — the default view is every classified item; healthtech narrows to general-scope, health-tech-relevant ones. The default changed in v6 (it was healthtech) — pass view=healthtech explicitly if you relied on the narrow view
searchstringFull-text search (indexed) across original and English titles, summaries and descriptions
topic[]arrayClassifier topic slug (repeatable; values OR together), e.g. samd, ai-act, ehds, reimbursement. Bare topic= also accepted for one value; /api/topics lists the full vocabulary with display labels
scopestringgeneral (binds a class of actors) · single_entity (one named product or company)
kind[]arrayDocument type (repeatable): rule_change · guidance · consultation · safety_alert · approval · supply_notice · enforcement · administrative · news. Bare kind= also accepted for one value
breadth[]arrayHow widely a document binds (repeatable): framework · sector · product_class. breadth[]=framework&breadth[]=sector is the “big ideas only” query — framework acts and sector rules without single-product-type classifications. Bare breadth= also accepted
relevancestringHealth-tech relevance floor: core · adjacent · all (default, no floor). Narrows within view, never widens it
sortstringrelevance (default, blends health-tech score, obligation level and recency, then demotes news and administrative kinds, single-entity scope and narrow breadth) · newest · impact (obligation level, HIGH first)
sincedateISO date; only items published or first seen on/after it — poll this from an integration
impact[]arrayObligation level (repeatable): HIGH · MEDIUM · LOW. Shown on the site as “Obligation”; the parameter name is unchanged
category[]arrayOne of the ten categories below (repeatable)
region[]arraye.g. EU-wide, Germany, France (repeatable)
sourcestringSource registry key, e.g. ema, uk_mhra — the keys behind /sources. Every item returns its own sourceKey, so a result can be turned straight back into a query. Repeatable as source[]
dateRangestringtoday · week · month · 3months · 6months · year
hasDeadlinesbooleantrue returns only items with extracted deadlines
pageintegerPage number, default 1
limitintegerItems per page, default 50, max 200
collapsestringinstrument groups rows in the returned page that share an instrumentIds value or digest series; the first row in sort order represents the group and lists its siblings in alsoReported. Grouping is per page: a collapsed page may return fewer than limit rows, and pagination.total stays the document count. Default: off — one row per document
curl "https://www.health-signal.com/api/regulations?topic=samd&since=2026-08-01"
curl "https://www.health-signal.com/api/regulations?impact[]=HIGH&dateRange=month&limit=10"
curl "https://www.health-signal.com/api/regulations?source=ema&kind=guidance"

A value outside its vocabulary is a 400, not a filter that quietly does nothing. The body names every offending parameter in invalid, so one request tells you about all the typos in a filter bundle rather than the first. This matters more than it sounds: until August 2026 an unknown value was dropped, so ?category=Devices (not a real category) answered with the entire corpus and looked like a successful query. /feed.xml is deliberately still lenient — a feed reader cannot show you an error body, so a subscription with one stale parameter keeps working instead of going dark.

Every item carries a classification object — scope, breadth, healthTech.relevant/score, kind, topics, an always-populated English display title (titleEn), and a one-sentence extractive summaryEn (null when the source text states no concrete change). The axes are independent: kind is what sort of document it is, scope is whether it binds a class or one named thing, breadth is how widely it binds, and categories is the subject. impact is derived from all three.

GET /feed.xml

RSS 2.0 of the newest ~50 items, chronological. Accepts the same filter parameters as /api/regulationstopic[], kind[], breadth[], relevance, impact[], scope, category[], region[], source, search, view (built by the same code, so the two cannot drift) — which makes any filtered view of the site a subscribable URL. Pagination, sort, since, dateRange and collapse do not apply: a feed is a rolling newest-window of one item per document by design.

curl "https://www.health-signal.com/feed.xml?kind[]=rule_change&kind[]=consultation&relevance=core"
curl "https://www.health-signal.com/feed.xml?topic[]=ai-act&topic[]=samd"

GET /api/topics

The classifier’s controlled topic vocabulary with display labels — the valid values for topic/topic[]. Static; changes only with releases.

Sample response
{
  "status": "success",
  "data": {
    "regulations": [
      {
        "_id": "6908d35e99792966b6f85b38",
        "title": "AI Act General Application (High-Risk AI)",
        "description": "Obligations for high-risk AI systems apply…",
        "link": "https://eur-lex.europa.eu/…",
        "source": "European Commission",
        "sourceKey": "ec_health",
        "impact": "HIGH",
        "impactScore": 3,
        "categories": ["Digital Health & AI"],
        "geographicScope": ["EU-wide"],
        "originalPubDate": "2026-06-14T00:00:00",
        "createdAt": "2026-06-15T05:17:03",
        "classification": { "kind": "rule_change", "scope": "general", "breadth": "framework", "healthTech": { "score": 92, "relevant": true }, "topics": ["ai-act"], "titleEn": "AI Act General Application (High-Risk AI)" },
        "hasDeadlines": true,
        "deadlineStatus": "upcoming",
        "nextDeadline": "2026-08-02",
        "deadlines": [
          { "date": "2026-08-02", "eventType": "ENTERS_FORCE", "originalText": "General application" }
        ]
      }
    ],
    "pagination": { "page": 1, "limit": 10, "total": 1522, "pages": 153 }
  }
}

Response fields

Each item in data.regulations carries the following fields.

FieldTypeDescription
_idstringStable document identifier
titlestringRegulation title
descriptionstringSummary text
linkstringCanonical URL to the official source
sourcestringIssuing body, as a display name (UK MHRA). Prose — it changes if an agency renames itself
sourceKeystringRegistry key of the issuing body (uk_mhra). This is what ?source= accepts. Sparse: absent on a few early documents stored before the field existed
impactstringObligation level — how widely and how hard the document binds: HIGH (creates or changes binding obligations for a class of actors) · MEDIUM (binds one narrow product type, or proposes something that would bind broadly) · LOW (no new obligation for a class). Derived in v7 from kind, scope and breadth, so it no longer signals how interesting an item is — classification.healthTech.score carries that. Displayed as “Obligation”; field name and values unchanged
impactScoreintegerNumeric obligation level: 3 / 2 / 1
categoriesarrayOne or more of the ten categories
geographicScopearraye.g. EU-wide, Germany
originalPubDatestringPublication date (ISO 8601, may be null)
createdAtstringWhen the item entered HealthSignal (ISO 8601)
classificationobjectThe classifier’s verdict: kind, scope, breadth (framework · sector · product_class — how widely the document binds), healthTech.score (0–100 topical proximity), topics[], titleEn, summaryEn, appliesFrom, status
hasDeadlinesbooleanWhether any deadlines were extracted
deadlineStatusstringe.g. upcoming, none
nextDeadlinestringNext upcoming deadline date (may be null)
deadlinesarray{ date, eventType, originalText } per deadline
instrumentIdsarrayNormalized legal-instrument IDs extracted from the raw text — eu:2017/745, mdcg:2024-14, mhra:dsi/2026/5, us-fr:2026-14358. Absent when no instrument is cited
digestKeystringPresent on items in a source’s recurring digest series (e.g. MHRA’s weekly Field Safety Notices round-up)
alsoReportedarrayOnly with collapse=instrument: the grouped siblings this row represents — { id, source, dateLabel, kind }

GET /api/active-countries

The regions that currently have coverage — useful for building region filters against live data.

curl "https://www.health-signal.com/api/active-countries"
Sample response
{
  "status": "success",
  "countries": [
    { "value": "EU-wide", "label": "EU-wide", "code": "EU", "order": 0 },
    { "value": "Germany", "label": "Germany", "code": "DE", "order": 1 }
  ],
  "total": 2
}

GET /api/health

Detailed service health, including database status and response time. Returns 503 with { "status": "unhealthy" } when the database is unreachable. A lighter probe with no database call lives at /health.

curl "https://www.health-signal.com/api/health"
Sample response
{
  "status": "healthy",
  "database": "connected",
  "response_time_seconds": 0.182,
  "timestamp": "2026-07-20T07:08:12.770756"
}

Reference

Categories

  • Pharmaceuticals
  • Medical Devices & IVD
  • Digital Health & AI
  • Clinical Trials & Research
  • Health Data, Privacy & Security
  • Market Access & Reimbursement
  • Health System & Payment
  • Public Health
  • Other Regulated Products
  • General Regulatory

Machine-readable

Fair use

Responses are cached for 5 minutes and rate-limited to 100 requests per minute per IP. The data updates once a day — polling more than a few times a day buys you nothing. Questions: info@heal.capital