Quickstart
Three steps to a 200: create a key, call an endpoint, read the envelope. The Free plan needs no card and covers 10 requests (search, the ATC tree, the change feed) and 5¢ of AI credit a month.
- 1
Create a key
Keys are minted in the console at Advanced → API keys. The plaintext is shown once, at creation — we store only a SHA-256 hash and the last four characters, so a lost key is replaced, never recovered.
A live key looks like
dd_live_…and a test key likedd_test_…. Both are real credentials against the same data; the prefix exists so you can tell in a log which environment a call came from.dd_, not ms_
Keys issued before the mid-2026 rebrand startms_live_/ms_test_. Those are no longer accepted by default — if you have one in an old config, mint a replacement rather than filing a bug. - 2
Make the call
Every endpoint hangs off
https://drug-database.com/v1and takes the key as a bearer token. This is the whole of the hello-world:curlcurl -s 'https://drug-database.com/v1/drugs?q=ibuprofen&country=ALL&limit=5' \ -H 'Authorization: Bearer dd_live_YOUR_KEY'
javascriptconst res = await fetch( 'https://drug-database.com/v1/drugs?' + new URLSearchParams({ q: 'ibuprofen', country: 'ALL', limit: '5' }), { headers: { Authorization: `Bearer ${process.env.DRUG_DATABASE_API_KEY}` } }, ) if (!res.ok) throw new Error(`${res.status} ${await res.text()}`) const { results, _meta } = await res.json()pythonimport os, requests res = requests.get( "https://drug-database.com/v1/drugs", params={"q": "ibuprofen", "country": "ALL", "limit": 5}, headers={"Authorization": f"Bearer {os.environ['DRUG_DATABASE_API_KEY']}"}, timeout=30, ) res.raise_for_status() payload = res.json()The /v1 and /api/v1 prefixes are the same routes
https://drug-database.com/v1/drugsis the canonical, documented form and is what the OpenAPI document advertises./api/v1/drugsreaches the identical handler — it is the internal path the rewrite points at. Use/v1. - 3
Read what comes back
Successful responses are JSON. Anything derived from an ingested register carries a
_meta.sourcesblock naming every source that contributed and when it last synced cleanly, so you can decide for yourself whether a figure is fresh enough to act on.200 — trimmed{ "results": [ { "id": "…-uuid", "product_name": "Algifor Dolo forte Filmtabl 400 mg", "country_code": "CH", "atc_code": "M01AE01", "manufacturer": "VERFORA SA" } ], "_meta": { "sources": { "bag_sl": { "last_synced_at": "2026-09-11T03:12:00Z", "schema_state": "ok" }, "swissmedic": { "last_synced_at": "2026-09-11T03:40:00Z", "schema_state": "ok" } } } }Two response headers are worth logging on every call:
Header Meaning x-ratelimit-remaining Calls left before the tighter of your monthly quota and daily cap is exhausted. x-drug-database-tier The tier the key resolved to — useful when a 403 says a scope is missing. Both are exposed cross-origin, so browser clients can read them too. See Errors for what each non-200 status means.
What the free tier covers
A Free key resolves to four scopes and one small monthly ceiling, shared by all your keys and the console. When it is hit, calls return 429 quota_exceeded until the month rolls or you upgrade. It is sized for trying the API, not for running on it.
| Free | |
|---|---|
| Scopes | drugs:readatc:readchanges:readchat:write |
| Monthly quota | 10 requests |
| AI credit | 5¢ a month |
| Card required | No |
Authentication has the full scope and quota table for every tier, and explains which endpoints each scope unlocks.
Where to go next
- Endpoint reference — all 43 paths, generated from the OpenAPI document we serve.
- Interactive explorer — run any of them in the browser with your key.
- Webhooks — stop polling the change feed.
- MCP server — the same data as tools inside Claude, Cursor or Windsurf.
- The raw spec lives at /api/v1/openapi.json and is public — point a client generator at it.