API Reference

REST API

A structured, versioned API for querying the full TwinFyRx drug intelligence platform. All endpoints return JSON and follow consistent patterns for pagination, errors, and resource nesting.

Setup

Authentication & Access Tiers

Data endpoints require an X-API-Key header; a small set of public endpoints needs none. A missing or wrong key returns 401.

SurfaceAccessPaths
Always publicNo key, ever/v1/demo/*
/v1/data-quality/*
/v1/metadata/*
/health*
Data endpointsX-API-Key required/v1/drugs/*
/v1/pricing/*
/v1/formulary/*
/v1/diseases/*
/v1/ndc/*
/v1/atc/*
/v1/medicare/*
/v1/pharmacies/*
/v1/providers/*
/v1/fhir/*
/v1/canada/*
/v1/ingredients/*

Once you have a key, include it on every request to the data endpoints. In the interactive docs at /docs, click Authorize and paste it once:

curl -H "X-API-Key: your_api_key" \
  https://api.twinfyrx.com/v1/drugs/search?q=atorvastatin

Key provisioning: One key per customer, scoped to the licensed tier. Keys are issued during onboarding and rotated on request. Request access to get started.

Backwards compatibility: The drug_id primary key, all enum values, and the v1 path prefix are stable. Breaking changes will be versioned under a new path prefix with a minimum six-month migration window.

Setup

Base URL

https://api.twinfyrx.com

All endpoints are versioned under /v1.

Setup

Response Headers

Every response carries three diagnostic headers:

HeaderValueNotes
X-Schema-Version1.0Schema version in effect. Increments on breaking changes.
X-Data-As-Of2026-04-28T14:32:00ZUTC timestamp of the most recent data sync to MotherDuck.
X-Request-IDuuid-v4Echo of client-supplied X-Request-ID, or a server-generated UUID. Include in support tickets.

Supply your own X-Request-ID on outbound calls to correlate requests across your own logs. X-Schema-Version and X-Data-As-Of are exposed via CORS so browser clients can read them.

Setup

Error Handling

All error responses follow a consistent structure. The request_id field matches the X-Request-ID header and is the primary reference for support:

{
  "error": {
    "code": "NOT_FOUND",
    "message": "Drug with drug_id 999999 not found",
    "request_id": "a3f2c1d8-7b4e-4e2a-9c1f-0d5e8a3b2f91"
  }
}
StatusCodeMeaning
401UNAUTHORIZEDMissing or invalid API key
404NOT_FOUNDResource not found
422VALIDATION_ERRORInvalid query parameters
500INTERNAL_ERRORUnexpected server error
Core

Drugs

Search, retrieve, and explore the drug concept hierarchy. Every response anchors to a canonical RxNorm drug concept.

GET/v1/drugs/search

Search drugs by name, ingredient, or brand name. Returns paginated results.

Parameters

qstringREQUIREDSearch query (min 2 characters)
limitintegerResults per page (1–100, default 20)
offsetintegerPagination offset

Response

results[]DrugSummaryArray of matching drugs
.drug_idintegerCanonical drug concept ID (RxNorm SCD/SBD RXCUI)
.drug_namestringFull clinical drug name
.ingredientstringActive ingredient(s)
.is_genericbooleanWhether this is a generic concept
totalintegerTotal matching results

Example Response

{
  "results": [
    {
      "drug_id": 617314,
      "drug_name": "atorvastatin 20 MG Oral Tablet",
      "ingredient": "atorvastatin",
      "dose_form": "Oral Tablet",
      "clinical_drug_form": "atorvastatin Oral Tablet",
      "brand_name": null,
      "is_generic": true
    }
  ],
  "total": 12,
  "limit": 20,
  "offset": 0
}
GET/v1/drugs/{drug_id}

Full drug detail including RxNorm hierarchy, therapy attributes, and branded variants.

Parameters

drug_idintegerREQUIREDCanonical drug concept ID (path parameter)

Response

drug_idintegerCanonical drug concept ID
drug_namestringFull drug name
ingredient_rxcuiintegerIngredient concept ID
clinical_drug_form_rxcuiintegerDrug form concept ID
therapy_patternstringTherapy behavior enum
specialty_flagbooleanSpecialty drug indicator
benefit_channelstringPharmacy, medical, or dual
GET/v1/drugs/{drug_id}/icd10-volumes

Utilization-weighted drug-to-diagnosis mappings with percent volume estimates.

Response

drugDrugSummaryDrug summary object
is_single_indication_drugbooleanWhether drug maps to a single diagnosis
icd10_volumes[]ICD10VolumeDiagnoses sorted by volume
.icd10_codestringICD-10-CM code
.pct_volumedecimalEstimated % of prescriptions for this diagnosis
.is_dominant_indicationbooleanWhether this is the primary indication
GET/v1/drugs/{drug_id}/hcc-suspects

Risk-adjustment categories a drug points at, for diagnosis suspecting from pharmacy claims. Optional ?model= cms_hcc_v28, cms_hcc_v24 or rxhcc_v08.

Response

drugDrugSummaryDrug summary
suspect_countintegerTotal categories returned
suspects[]HccSuspectCategories, strongest first
.model_keystringcms_hcc_v28, cms_hcc_v24, or rxhcc_v08
.ccstringCondition category within the model
.cc_labelstringCMS's name for the category
.pct_volumedecimalEstimated % of the drug's prescriptions for diagnoses in the category
.confidence_tierstringhigh (≥ 90%), moderate (70–90%), or unrated
.contributing_icd10_codes[]stringICD-10-CM codes behind the category
Market Intelligence

Pricing

NADAC acquisition costs (weekly cadence) and Medicare Part B ASP payment limits (quarterly). Both resolve through the drug concept hierarchy.

GET/v1/drugs/{drug_id}/pricing

Current pricing snapshot — latest NADAC week and latest ASP quarter for a drug.

Response

drugDrugSummaryDrug summary
nadacNADACSnapshot | nullLatest NADAC pricing (if available)
.median_per_unitdecimalMedian acquisition cost per unit
.pricing_unitstringUnit label (e.g., 'EACH', 'ML')
aspASPSnapshot | nullLatest ASP pricing (if available)
.median_payment_limitdecimalMedian Part B payment limit
GET/v1/drugs/{drug_id}/nadac

Historical NADAC pricing with weekly granularity. Defaults to 2 years of history.

Parameters

startdateStart date (YYYY-MM-DD, default: 2 years ago)
enddateEnd date (YYYY-MM-DD, default: today)

Response

pricing_unitstringUnit of measure
weeks[]NADACWeekWeekly pricing data points
.as_of_datedateEffective date
.median_per_unitdecimalMedian cost per unit
.ndc_countintegerNDCs contributing to this aggregate

Example Response

{
  "drug": { "drug_id": 617314, "drug_name": "atorvastatin 20 MG Oral Tablet" },
  "pricing_unit": "EACH",
  "weeks": [
    { "as_of_date": "2026-03-05", "median_per_unit": 0.0412, "min_per_unit": 0.0389, "max_per_unit": 0.0445, "ndc_count": 14 },
    { "as_of_date": "2026-02-26", "median_per_unit": 0.0415, "min_per_unit": 0.0391, "max_per_unit": 0.0448, "ndc_count": 14 }
  ]
}
GET/v1/drugs/{drug_id}/asp

Historical ASP (Medicare Part B) payment limits by quarter with HCPCS code mappings.

Response

quarters[]ASPQuarterQuarterly ASP data points
.effective_quarterstringe.g., '2026Q1'
.median_payment_limitdecimalMedian payment limit per unit
.hcpcs_codesstring[]Associated HCPCS codes
Reference

Diagnoses

Search and explore ICD-10-CM diagnoses with reverse drug mappings — find which drugs are most commonly prescribed for a given condition.

GET/v1/diagnoses/search

Search ICD-10 diagnoses by code or description text.

Parameters

qstringREQUIREDICD-10 code or description text
limitintegerResults per page (default 20)

Response

results[]DiseaseSummaryMatching diagnoses
.icd10_codestringICD-10-CM code
.diagnosisstringDiagnosis description
.is_billablebooleanWhether this is a billable code
GET/v1/diagnoses/{icd10_code}

Full diagnosis detail including ICD-10 hierarchy and top 20 drugs prescribed for this condition.

Response

icd10_codestringICD-10-CM code
diagnosisstringFull description
chapter_namestringICD-10 chapter
is_billablebooleanBillable indicator
top_drugs[]DrugForDiseaseTop 20 drugs by prescribing volume
.pct_volumedecimalEstimated share of prescriptions
Reference

Classification & Identifiers

ATC therapeutic classification hierarchy and NDC identifier resolution.

GET/v1/atc/search

Search ATC classification codes by code or name.

Parameters

qstringREQUIREDATC code or therapeutic class name
GET/v1/atc/{atc_code_5}

Full 5-level ATC hierarchy for a given code.

Response

atc_code_1 → atc_code_5stringAll 5 ATC hierarchy levels
atc_name_1 → atc_name_5stringNames for each level
GET/v1/drugs/{drug_id}/atc

Every WHO ATC code the drug product carries, including combination and pack codes. primary_atc_code is the one code for the product's route and main use (null when the evidence doesn't settle one); it is listed first, flagged is_primary.

Response

primary_atc_codestring | nullThe product's one ATC level-5 code
atc_classifications[]arrayAll 5 levels per code, plus is_primary, source, is_combination
GET/v1/ndc/{ndc_code}

Resolve an NDC code to its drug concept with therapy attributes and specialty flags. NDCs of retired drug concepts still resolve, flagged obsolete, so historical claims join.

Response

ndcstringInput NDC code
drug_idintegerResolved canonical drug concept ID (last known concept for an obsolete NDC)
drug_namestringDrug name
therapy_patternstringTherapy behavior classification
specialty_flagbooleanSpecialty drug indicator
is_activebooleanNDC is in the current crosswalk
is_obsoletebooleandrug_id is no longer a current RxNorm drug concept
obsolete_reasonstringretired | obsolete | not_prescribable (null when current)
remapped_drug_idintegerCurrent drug concept RxNorm says replaces drug_id, when one exists
Providers

Prescribers

Medicare Part D prescribing per NPI (2024), mapped onto RxNorm ingredients and WHO ATC classes: who prescribes a class or ingredient, how much, and each prescriber's specialty and volume decile. Claims are counted at ingredient and ATC levels 2–4 — CMS drug names carry no strength or form. CMS suppresses rows under 11 claims, so volumes are lower bounds.

GET/v1/providers/search

Search prescribers, largest prescribing volume first. At least one filter is required.

Parameters

qstring10-digit NPI, or name words ("jane smith")
statestringTwo-letter state code
specialtystringSpecialty key, name or group (e.g. cardiology)
cms_prescriber_typestringCMS Part D prescriber type
limitintegerResults per page (1–500, default 50)
offsetintegerPagination offset
GET/v1/providers/{npi}

One prescriber: name, location, best-available specialty, and Part D prescribing totals, breadth and volume deciles.

Response

specialtyobjectprovider_type, specialty_key / name / group, population, primary_care_specialty, practice_setting, specialty_confidence, cms_prescriber_type
prescribing.claimsintegerPart D claims in data_year
prescribing.drug_costdecimalTotal drug cost, USD
prescribing.n_ingredients / n_atc2 / n_atc4integerBreadth of prescribing
prescribing.brand_claim_sharedecimalShare of claims dispensed under a brand name
prescribing.decilesobjectVolume deciles 1–10: national, prescriber_type, state

Example Response

{
  "npi": "1234567893",
  "first_name": "JANE",
  "last_name": "DOE",
  "city": "BOSTON",
  "state": "MA",
  "specialty": {
    "provider_type": "physician",
    "specialty_key": "cardiology",
    "specialty_name": "Cardiology",
    "practice_setting": "office",
    "specialty_confidence": "high",
    "cms_prescriber_type": "Cardiology"
  },
  "prescribing": {
    "data_year": 2024,
    "claims": 9120,
    "drug_cost": 1840250.12,
    "n_ingredients": 64,
    "n_atc4": 41,
    "brand_claim_share": 0.21,
    "top_atc2": "C10",
    "top_atc2_share": 0.27,
    "deciles": { "national": 10, "prescriber_type": 8, "state": 9 }
  }
}
GET/v1/providers/{npi}/prescribing/atc

A prescriber's volume in each ATC class, largest first. Deciles rank the prescriber among everyone who prescribes the class.

Parameters

levelintegerATC level 2, 3 or 4 (default 4)
limitintegerMax rows (1–1000, default 100)

Response

results[]arrayatc_code, atc_name, claims, fills_30day, days_supply, drug_cost, brand_claims, share_of_prescriber, deciles
GET/v1/providers/{npi}/prescribing/ingredients

A prescriber's volume per ingredient (RxNorm IN / MIN), largest first.

Parameters

limitintegerMax rows (1–1000, default 100)
GET/v1/atc/{atc_code}/prescribers

Top prescribers of an ATC level 2–4 class (e.g. C10, C10A, C10AA), ranked by claims.

Parameters

statestringTwo-letter state code
cms_prescriber_typestringCMS Part D prescriber type
specialtystringSpecialty key, name or group
min_decileintegerLowest national decile within the class (1–10)
limit / offsetintegerPagination (1–500, default 50)
GET/v1/atc/{atc_code}/prescriber-mix

Who prescribes a class: prescribers, claims and share of the class's claims by CMS prescriber type.

GET/v1/ingredients/{ingredient_rxcui}/prescribers

Top prescribers of an ingredient, ranked by claims. Same filters as the ATC class endpoint.

Patterns

Pagination

List endpoints return a consistent envelope. The response also carries an X-Total-Count header with the untruncated count, useful for building UI paginators without parsing the body:

{
  "results": [ ... ],
  "total": 142,
  "limit": 20,
  "offset": 0
}

# Response header:
X-Total-Count: 142
RuleDetail
Max limit1000 results per request. Default is 20.
Offset pagingUse limit + offset for result sets up to ~10,000 rows.
Bulk endpointPOST /v1/drugs:lookup accepts up to 500 drug_ids per call — preferred over looping single-drug GETs.
Patterns

Rate Limits

Rate limits are enforced per API key. Specific limits are communicated during onboarding and vary by tier. When a limit is exceeded, the API returns 429 Too Many Requests with a Retry-After header indicating how many seconds to wait before retrying.

HTTP/1.1 429 Too Many Requests
Retry-After: 30
X-Request-ID: a3f2c1d8-7b4e-4e2a-9c1f-0d5e8a3b2f91

{
  "error": {
    "code": "RATE_LIMITED",
    "message": "Rate limit exceeded. Retry after 30 seconds.",
    "request_id": "a3f2c1d8-7b4e-4e2a-9c1f-0d5e8a3b2f91"
  }
}

Recommended retry strategy: exponential backoff starting at 1 second, capped at 60 seconds, with jitter. For batch workloads, use POST /v1/drugs:lookup to reduce request volume — 500 drugs in one call instead of 500 individual GETs.