API Documentation

HealthProcure Intel v1.0.0

Access aggregated healthcare procurement data from TED Europa, SAM.gov, WHO Procurement, and NHS Supply Chain. The REST API provides programmatic access to tenders, contract awards, analytics, and regional benchmarks covering $50B+ in procurement activity.

Authentication

All API requests (except /health) require a valid API key passed in the X-API-Key header.

API keys follow the format hpi_ followed by 64 hexadecimal characters.

# Include your API key in every request curl https://your-domain.railway.app/api/v1/tenders \ -H "X-API-Key: hpi_your_api_key_here"

Base URL

https://your-domain.railway.app/api/v1

All endpoint paths in this documentation are relative to the base URL unless noted otherwise.

Tier Limits

Your API key is associated with a subscription tier that determines rate limits, maximum page sizes, and access to premium endpoints.

Tier Requests / Day Max Page Size Benchmarks Price
Free 100 20 No $0
Basic 1,000 50 No $49/mo
Pro 10,000 200 Yes $199/mo
Enterprise Unlimited 500 Yes $499/mo

Endpoints

All endpoints return JSON responses.

GET /health No auth required

Health check endpoint. Returns server status, current timestamp, API version, and database mode. Does not require authentication.

Response

{ "status": "ok", "timestamp": "2026-06-24T12:00:00.000Z", "version": "1.0.0", "mode": "supabase" }

Try it

curl https://your-domain.railway.app/health
GET /api/v1/tenders

List healthcare procurement tenders with powerful filtering, full-text search, and pagination.

Query Parameters

source
string, optional

Filter by data source. One of: ted_europa, sam_gov, who_procurement, nhs_supply_chain

category
string, optional

One of: medical_devices, pharmaceuticals, health_it, laboratory_equipment, hospital_infrastructure, personal_protective_equipment, diagnostics, surgical_instruments, telemedicine, other

status
string, optional

One of: open, closed, awarded, cancelled, planned

country
string, optional

ISO 3166-1 alpha-2 country code (e.g. DE, US, GB)

region
string, optional

Geographic region (e.g. Europe, North America)

minValue
number, optional

Minimum tender value in USD

maxValue
number, optional

Maximum tender value in USD

publishedAfter
string, optional

ISO 8601 date (e.g. 2026-01-01)

publishedBefore
string, optional

ISO 8601 date

search
string, optional

Full-text search across tender titles and descriptions (max 200 chars)

page
integer, default: 1

Page number for pagination

pageSize
integer, default: 20

Results per page (max depends on your tier)

Response

{ "data": [ { "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "external_id": "TED-2026-123456", "source": "ted_europa", "title": "Supply of MRI Equipment for Regional Hospitals", "description": "Procurement of 12 MRI scanners...", "buyer_name": "Charite - Universitaetsmedizin Berlin", "buyer_country": "DE", "buyer_region": "Europe", "category": "medical_devices", "status": "open", "published_at": "2026-06-15T09:00:00.000Z", "deadline": "2026-08-01T17:00:00.000Z", "original_currency": "EUR", "original_value": 4200000, "value_usd": 4578000, "compliance_criteria": ["ISO 13485", "CE Mark"], "cpv_codes": ["33111000"], "url": "https://ted.europa.eu/udl?uri=TED:NOTICE:123456-2026", "scraped_at": "2026-06-24T06:30:00.000Z", "updated_at": "2026-06-24T06:30:00.000Z" } ], "pagination": { "page": 1, "pageSize": 20, "total": 150, "totalPages": 8 } }

Try it

# List open medical device tenders in Europe curl "https://your-domain.railway.app/api/v1/tenders?status=open&category=medical_devices®ion=Europe" \ -H "X-API-Key: hpi_your_api_key_here"
GET /api/v1/tenders/:id

Retrieve a single tender by its UUID. Returns 404 if the tender does not exist.

Path Parameters

id
UUID, required

The unique identifier of the tender

Response

{ "data": { "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "external_id": "TED-2026-123456", "source": "ted_europa", "title": "Supply of MRI Equipment for Regional Hospitals", "description": "Full procurement description...", "buyer_name": "Charite - Universitaetsmedizin Berlin", "buyer_country": "DE", "buyer_region": "Europe", "category": "medical_devices", "status": "open", "published_at": "2026-06-15T09:00:00.000Z", "deadline": "2026-08-01T17:00:00.000Z", "original_currency": "EUR", "original_value": 4200000, "value_usd": 4578000, "compliance_criteria": ["ISO 13485", "CE Mark"], "cpv_codes": ["33111000"], "url": "https://ted.europa.eu/udl?uri=TED:NOTICE:123456-2026", "scraped_at": "2026-06-24T06:30:00.000Z", "updated_at": "2026-06-24T06:30:00.000Z" } }

Error Response (404)

{ "error": "Tender not found" }

Try it

curl "https://your-domain.railway.app/api/v1/tenders/a1b2c3d4-e5f6-7890-abcd-ef1234567890" \ -H "X-API-Key: hpi_your_api_key_here"
GET /api/v1/awards

List contract awards with filtering by tender, supplier country, source, value range, and date range.

Query Parameters

tenderId
UUID, optional

Filter awards for a specific tender

supplierCountry
string, optional

ISO 3166-1 alpha-2 code of the supplier's country

source
string, optional

Filter by data source

minValue
number, optional

Minimum award value in USD

maxValue
number, optional

Maximum award value in USD

awardedAfter
string, optional

ISO 8601 date

awardedBefore
string, optional

ISO 8601 date

page
integer, default: 1
pageSize
integer, default: 20

Response

{ "data": [ { "id": "f9e8d7c6-b5a4-3210-fedc-ba9876543210", "tender_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "award_date": "2026-05-20T00:00:00.000Z", "supplier_name": "Siemens Healthineers AG", "supplier_country": "DE", "original_currency": "EUR", "award_value": 3850000, "award_value_usd": 4196500, "framework_type": "single_supplier", "duration": "36 months", "source": "ted_europa", "scraped_at": "2026-06-24T06:30:00.000Z" } ], "pagination": { "page": 1, "pageSize": 20, "total": 42, "totalPages": 3 } }

Try it

# List awards for a specific tender curl "https://your-domain.railway.app/api/v1/awards?tenderId=a1b2c3d4-e5f6-7890-abcd-ef1234567890" \ -H "X-API-Key: hpi_your_api_key_here"
GET /api/v1/analytics/stats

Retrieve aggregated dashboard statistics including totals, breakdowns by region/category/source, monthly trends, and top buyers.

Response

{ "totalTenders": 1247, "openTenders": 389, "totalAwardValueUsd": 2847500000, "avgContractValueUsd": 3420000, "tendersByRegion": { "Europe": 542, "North America": 387, "Global": 198, "United Kingdom": 120 }, "tendersByCategory": { "medical_devices": 312, "pharmaceuticals": 278, "health_it": 156, "laboratory_equipment": 134, // ... more categories }, "tendersBySource": { "ted_europa": 542, "sam_gov": 387, "who_procurement": 198, "nhs_supply_chain": 120 }, "monthlyTrend": [ { "month": "2026-01", "count": 98, "totalValue": 245000000 }, { "month": "2026-02", "count": 112, "totalValue": 312000000 } ], "topBuyers": [ { "name": "NHS England", "country": "GB", "count": 45 }, { "name": "VA Medical Center", "country": "US", "count": 38 } ] }

Try it

curl "https://your-domain.railway.app/api/v1/analytics/stats" \ -H "X-API-Key: hpi_your_api_key_here"
GET /api/v1/analytics/benchmarks

Pro / Enterprise only

Retrieve regional supply chain benchmarks including average lead times, contract values, tender counts, and compliance rates. Requires a Pro or Enterprise tier API key.

Query Parameters

region
string, optional

Filter benchmarks by region (e.g. Europe)

category
string, optional

Filter by procurement category

Response

{ "data": [ { "id": "b2c3d4e5-f6a7-8901-bcde-f12345678901", "region": "Europe", "category": "medical_devices", "avg_lead_time_days": 67, "avg_contract_value_usd": 2850000, "tender_count": 312, "award_count": 198, "compliance_rate": 0.94, "period": "2026-Q2", "calculated_at": "2026-06-24T00:00:00.000Z" } ] }

Error Response (403)

{ "error": "This endpoint requires Pro or Enterprise tier" }

Try it

# Requires Pro or Enterprise API key curl "https://your-domain.railway.app/api/v1/analytics/benchmarks?region=Europe&category=medical_devices" \ -H "X-API-Key: hpi_your_pro_api_key_here"
GET /api/v1/sources

List all available procurement data sources with metadata.

Response

{ "data": [ { "id": "ted_europa", "name": "TED Europa", "region": "Europe", "url": "https://ted.europa.eu" }, { "id": "sam_gov", "name": "SAM.gov", "region": "North America", "url": "https://sam.gov" }, { "id": "who_procurement", "name": "WHO Procurement", "region": "Global", "url": "https://www.who.int/procurement" }, { "id": "nhs_supply_chain", "name": "NHS Supply Chain", "region": "United Kingdom", "url": "https://www.supplychain.nhs.uk" } ] }

Try it

curl "https://your-domain.railway.app/api/v1/sources" \ -H "X-API-Key: hpi_your_api_key_here"

Error Responses

All errors return a JSON object with an error field.

Status Meaning Description
400 Bad Request Invalid query parameters. Zod validation details are included in the details field.
401 Unauthorized Missing or invalid API key. Ensure you are sending the X-API-Key header.
403 Forbidden Your tier does not have access to this endpoint (e.g. benchmarks require Pro or Enterprise).
404 Not Found The requested resource does not exist.
429 Rate Limited You have exceeded the daily request limit for your tier. Upgrade or wait until the limit resets.
500 Server Error An unexpected server error occurred. Contact support if the issue persists.

Example Error Response (400)

{ "error": "Invalid query parameters", "details": [ { "code": "invalid_enum_value", "expected": ["open", "closed", "awarded", "cancelled", "planned"], "received": "active", "path": ["status"], "message": "Invalid enum value. Expected 'open' | 'closed' | 'awarded' | 'cancelled' | 'planned', received 'active'" } ] }

Example Error Response (401)

{ "error": "Missing or invalid API key" }

Example Error Response (429)

{ "error": "Rate limit exceeded", "limit": 100, "tier": "free" }

Code Examples

Ready-to-use examples in popular languages.

List open tenders for medical devices

curl -X GET "https://your-domain.railway.app/api/v1/tenders?status=open&category=medical_devices&pageSize=10" \ -H "X-API-Key: hpi_your_api_key_here" \ -H "Accept: application/json"

Get dashboard statistics

curl "https://your-domain.railway.app/api/v1/analytics/stats" \ -H "X-API-Key: hpi_your_api_key_here"

Search tenders by keyword

curl "https://your-domain.railway.app/api/v1/tenders?search=ventilator&minValue=100000&publishedAfter=2026-01-01" \ -H "X-API-Key: hpi_your_api_key_here"

List open tenders for medical devices

const API_KEY = "hpi_your_api_key_here"; const BASE_URL = "https://your-domain.railway.app/api/v1"; const params = new URLSearchParams({ status: "open", category: "medical_devices", pageSize: "10", }); const response = await fetch(`${BASE_URL}/tenders?${params}`, { headers: { "X-API-Key": API_KEY, "Accept": "application/json", }, }); const { data, pagination } = await response.json(); console.log(`Found ${pagination.total} tenders`); for (const tender of data) { console.log(`${tender.title} — $${tender.value_usd?.toLocaleString()}`); }

Get regional benchmarks

// Requires Pro or Enterprise tier const benchmarks = await fetch( `${BASE_URL}/analytics/benchmarks?region=Europe&category=pharmaceuticals`, { headers: { "X-API-Key": API_KEY } } ).then(r => r.json()); for (const b of benchmarks.data) { console.log( `${b.region} / ${b.category}: avg $${b.avg_contract_value_usd?.toLocaleString()}, ` + `lead time ${b.avg_lead_time_days}d, compliance ${(b.compliance_rate * 100).toFixed(0)}%` ); }

List open tenders for medical devices

import requests API_KEY = "hpi_your_api_key_here" BASE_URL = "https://your-domain.railway.app/api/v1" response = requests.get( f"{BASE_URL}/tenders", headers={"X-API-Key": API_KEY}, params={ "status": "open", "category": "medical_devices", "pageSize": 10, }, ) response.raise_for_status() data = response.json() print(f"Found {data['pagination']['total']} tenders") for tender in data["data"]: print(f"{tender['title']} — ${tender['value_usd']:,.0f}")

Paginate through all awards

import requests API_KEY = "hpi_your_api_key_here" BASE_URL = "https://your-domain.railway.app/api/v1" page = 1 all_awards = [] while True: resp = requests.get( f"{BASE_URL}/awards", headers={"X-API-Key": API_KEY}, params={"page": page, "pageSize": 50}, ) resp.raise_for_status() result = resp.json() all_awards.extend(result["data"]) if page >= result["pagination"]["totalPages"]: break page += 1 print(f"Fetched {len(all_awards)} total awards")