Authentication

One mechanism, everywhere: a tenant API key sent as a bearer token. There are no cookies, no OAuth dance and no session — which is also why the API is safe to call from a browser.

The header

every request
Authorization: Bearer dd_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

No other authentication is accepted — not a query parameter, not a custom header. A request without a parseable Authorization: Bearer … header is a 401 before any handler runs.

Key format

PrefixWhat it is
dd_live_Production key. This is the normal case.
dd_test_Behaves identically and reads the same data — the prefix exists so you can tell environments apart in a log.
ms_live_ / ms_test_Legacy, pre-rebrand. Rejected by default. Mint a dd_ replacement.

After the prefix is 24 bytes of cryptographic randomness in base64url — roughly 192 bits, which is far past anything brute-forceable. We store a SHA-256 hash and the last four characters; the plaintext exists only in the response that created it.

Shown once

There is no endpoint, no support process and no database row that can return a key's plaintext after creation. If it is lost, revoke it in the console and mint another.

Scopes

Every endpoint declares one scope. A key's effective scopes are the intersection of its tier's catalogue and the scopes set on the key itself — so narrowing a key is always possible and widening one never is. Ask for a scope your tier does not carry and you get a 403 naming the missing scope in missing_scope.

What each tier carries

developer

drugs:readatc:readchanges:readchat:write

pro

drugs:readatc:readchanges:readchat:writegraphql:query

starter

drugs:readatc:readchanges:readchat:writegraphql:querywebhooks:write

growth

drugs:readatc:readchanges:readchat:writegraphql:querywebhooks:writeinteractions:writebyol:writech:claims

enterprise

drugs:readatc:readchanges:readchat:writegraphql:querywebhooks:writeinteractions:writebyol:writech:claimssso

Higher tiers strictly extend lower ones — nothing is ever taken away by upgrading.

What each scope unlocks

ScopeEndpoints
drugs:read/v1/drugs · /v1/drugs/{id} · /v1/drugs/{id}/profile · /v1/drugs/{id}/monograph · /v1/drugs/lookup · /v1/packs · /v1/substances · /v1/rxnorm · /v1/markets/overview · /v1/assessment · /v1/signatures · /v1/targets · /v1/guidelines · /v1/hta · and every enrichment resolver
atc:read/v1/atc/{code} and its children/drugs sub-paths · /v1/therapies/{code} · /v1/classes and its sub-paths
changes:read/v1/changes
webhooks:writePOST and DELETE /v1/webhooks
graphql:queryPOST /v1/graphql
interactions:writePOST /v1/interactions/check · POST /v1/prescriptions/validate
ch:claimsPOST /v1/ch/claims/validate · POST /v1/ch/price-comparison
byol:write/v1/byol/{provider} and /v1/byol/{provider}/query
chat:writePOST /v1/chat · POST /v1/chat/packs
ssoSingle sign-on for the console. Grants no API surface.

chat:write is on every tier

What limits the assistant is not the scope but the monthly AI answer allowance each plan includes: Free 5¢ of credit (half an answer at the per-answer ceiling), Pro 50, Starter 170, Growth 850. When it and any prepaid pack are spent, POST /v1/chat answers 402 ai_allowance_exhausted before the model runs. Paid plans can top up with POST /v1/chat/packs (100 answers for $15, 500 for $60).

Quotas

Quotas are counted per account: every key you hold, and the console's drug-profile, market and interaction lookups, share one counter. Two ceilings apply at once and the tighter one wins.

TierMonthlyDaily capAI answers / monthKeys
Free10105¢ credit2
Pro ($29)1,500500505
Starter ($99)20,000none17010
Growth ($490)150,000none85025
Enterpriseeffectively unlimitednoneby contract100

An exhausted quota answers 429

When the account runs out of monthly quota or daily cap, calls return 429 with "error": "quota_exceeded", the limit and window you hit, a resets timestamp, a retry-after header and an upgrade_url. Nothing is billed by usage. Watch x-ratelimit-remaining on successful calls to see it coming.

Response headers

HeaderOnMeaning
x-ratelimit-remainingauthenticated 2xxCalls left before the tighter of the two ceilings is reached.
x-drug-database-tierauthenticated 2xxThe tier the key resolved to.
x-drug-database-cacheenrichment endpointsWhere the payload came from: db (cached mirror), live (fetched from upstream now) or stale (cached copy returned because upstream was unreachable).
etagcacheable GETsSend it back as if-none-match to get a 304.
retry-after429 / 503Seconds to wait.

Revocation timing

Resolved keys are cached in memory for 30 seconds. A revoked key can therefore keep working for up to half a minute on an already-warm instance. Treat revocation as eventually consistent within 30s; if you need it to be instant, rotate the upstream secret your integration reads as well.

Calling from a browser

The API sends access-control-allow-origin: * and deliberately does not send access-control-allow-credentials. There is no ambient cookie for a hostile page to ride, so a wildcard origin grants nobody anything they could not do with curl.

  • Allowed request headers: authorization, content-type, if-none-match.
  • Exposed response headers: etag, x-ratelimit-remaining, x-drug-database-tier, retry-after.
  • Preflights are answered at the edge and cached for 24 hours.

That does not make a key safe in front-end code

CORS being open is about the browser, not about secrecy. A key shipped in client-side JavaScript is a public key — anyone can read it and spend your quota. Proxy through your own backend, or mint a narrowly-scoped key you are willing to lose.