ERP Integration API Contract
How an ERP or pharmacy system integrates with the MDB Medication Data Bank to screen prescriptions for drug, food, disease and pandemic interactions.
Overview
MDB exposes a small, stable HTTP API. You send the drugs a patient is taking; MDB returns any interactions it finds, each with a severity and clinical guidance. All requests and responses are JSON over HTTPS.
Drug ↔ Drug
Pairwise screening across every drug in the request.
Drug ↔ Food
Dietary interactions, e.g. grapefruit with carbamazepine.
Drug ↔ Disease
Contraindications against a patient's existing conditions.
Drug ↔ Pandemic
Guidance tied to an active pandemic protocol.
Base URL
# Production
https://api.mdb.com.saAll endpoints below are relative to this host. HTTPS only — plain HTTP is not served.
Integration model
An organization holds the contract and the request quota. Under it you create one sub-client per branch, pharmacy or store, each with its own API key — so usage is attributable per location and a key can be revoked without disturbing the rest of the estate.
Organization "Demo Pharmacy Group" quota 10,000 requests │ master key: DEM-MASTER-xxxxxxxx │ ├── Sub-client "Cairo Branch" key: DEM-SUB-xxxxxxxx limit 4,000 ├── Sub-client "Alexandria Branch" key: DEM-SUB-xxxxxxxx limit 2,500 └── Sub-client "Giza Branch" key: DEM-SUB-xxxxxxxx no limit → shares the pool
| Key type | Prefix | Use it for | Counts against |
|---|---|---|---|
| Master | *-MASTER-* |
Back-office jobs and central integrations | Organization quota |
| Sub-client | *-SUB-* |
One per branch / POS / store | Its own limit if set, and the organization quota |
Quick start
- Get your keyYour MDB administrator creates the organization and issues a master key; you create a sub-client per branch from the ERP dashboard.
- Resolve drug identifiersSend either MDB
drug_ids(UUIDs) or the localregistration_numbersyou already store. - Call the checkerPOST the drug list on every prescription save or dispense.
- Act on the resultBlock or warn on CRITICAL, surface a soft warning on WARNING.
- Watch your quotaPoll usage stats, or handle
429and alert before you run dry.
Authentication
API key (recommended)
Send the key in the x-api-key header on every request. The server also
accepts it as Authorization: Bearer <key>, but prefer
x-api-key — it keeps API-key traffic clearly separate from JWT traffic.
curl -X POST https://api.mdb.com.sa/api/v1/check-interactions \ -H "x-api-key: DEM-SUB-626e533d87f38185" \ -H "Content-Type: application/json" \ -d '{"drug_ids":["000a22da-add0-4278-85df-9a5fb1cde722"]}'
JWT (ERP dashboards)
Exchange the organization's email and password for a bearer token, then send it as
Authorization: Bearer <token>. Tokens are short-lived — re-authenticate
when you receive a 401.
Check interactions
The core drug–drug screening call. Counts as one request against your quota.
Request body
| Field | Type | Description | |
|---|---|---|---|
drug_ids | string[] (UUID) | ONE OF | MDB drug identifiers. |
registration_numbers | string[] | ONE OF | National registration numbers, if you don't store MDB UUIDs. |
drug_names | string[] | OPTIONAL | Free-text names. Least reliable — prefer an identifier. |
drug_ids or registration_numbers, not both.
At least two distinct drugs are required — identifiers that resolve
to the same active ingredient collapse to one, so sending two brands of the same
molecule returns 400 ValidationError.
Optional headers
| Header | Value | Effect |
|---|---|---|
Accept-Language | en, ar, fr, es, ru, zh | Localizes status messages and clinical text. |
x-include-food-interactions | true | Also return food interactions. |
x-include-disease-interactions | true | Also return disease interactions. |
x-include-pandemic-interactions | true | Also return pandemic interactions. |
Example
curl -X POST https://api.mdb.com.sa/api/v1/check-interactions \ -H "x-api-key: $MDB_API_KEY" \ -H "Content-Type: application/json" \ -H "Accept-Language: en" \ -d '{ "drug_ids": [ "000a22da-add0-4278-85df-9a5fb1cde722", "0016ae1e-0b19-4a0a-8084-d7ae025bba0d" ] }'
const res = await fetch(`${BASE}/api/v1/check-interactions`, { method: "POST", headers: { "x-api-key": process.env.MDB_API_KEY, "Content-Type": "application/json", "Accept-Language": "en" }, body: JSON.stringify({ drug_ids: drugIds }) }); if (res.status === 429) throw new Error("MDB quota exhausted"); const result = await res.json(); if (result.result === "MAJOR") blockDispense(result.interactions);
import os, requests resp = requests.post( f"{BASE}/api/v1/check-interactions", headers={ "x-api-key": os.environ["MDB_API_KEY"], "Accept-Language": "en", }, json={"drug_ids": drug_ids}, timeout=10, ) if resp.status_code == 429: raise RuntimeError("MDB quota exhausted") result = resp.json()
$ch = curl_init("$base/api/v1/check-interactions"); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ "x-api-key: " . getenv("MDB_API_KEY"), "Content-Type: application/json", ], CURLOPT_POSTFIELDS => json_encode(["drug_ids" => $drugIds]), ]); $result = json_decode(curl_exec($ch), true);
Response 200 OK
{
"success": true,
"interactions": [],
"result": "NONE",
"message": "✅ No interaction detected.",
"response_time_ms": 109
}| Field | Type | Description |
|---|---|---|
success | boolean | Request was processed. |
result | string | Highest severity found: NONE, MINOR, MODERATE, MAJOR, DUPLICATION. |
message | string | Human-readable summary, localized. |
interactions | object[] | One entry per interaction found; empty when clear. |
response_time_ms | number | Server-side processing time. |
success: true only means the check ran. Always branch on
result / status — never treat a 200 as "no interactions".
Unified check
Screens all four interaction types in a single call and bills a single request against your quota — the efficient choice when you also know the patient's conditions or diet.
Request body
| Field | Type | Description | |
|---|---|---|---|
drug_ids | string[] (UUID) | ONE OF | MDB drug identifiers. |
registration_numbers | string[] | ONE OF | Mutually exclusive with drug_ids. |
diseases | string[] | OPTIONAL | Patient conditions, e.g. ["Kidney Disease"]. |
food | string | OPTIONAL | Food item, e.g. "grapefruit". Omit to return all food interactions. |
pandemic | string | OPTIONAL | Active pandemic protocol name. |
curl -X POST https://api.mdb.com.sa/api/unified-interactions/check-unified-erp \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{ "drug_ids": ["000a22da-...", "0016ae1e-..."], "diseases": ["Kidney Disease"], "food": "grapefruit" }'
Response 200 OK
{
"success": true,
"status": "WARNING",
"status_message": "Drug interaction warning",
"details": {
"drugs_checked": [
{ "id": "0016ae1e-…",
"trade_name": "TEGRETOL 2% oral suspension",
"scientific_name": "CARBAMAZEPINE" }
],
"total_interactions": 1,
"interactions_by_type": {
"drug": 0, "food": 1,
"disease": 0, "pandemic": 0
},
"interactions": [
{
"type": "FOOD",
"severity": "Moderate",
"drug": { "scientific_name": "CARBAMAZEPINE" },
"food_name": "grapefruit",
"description": "…grapefruit juice increased plasma drug
concentrations by approximately 40%…"
}
]
}
}
type is one of DRUG, FOOD,
DISEASE, PANDEMIC, so you can route each finding to the
right part of your UI.
Usage statistics
Returns remaining quota. The shape depends on which key you present. This call is free.
With a master key
{
"success": true,
"data": {
"organization": "Demo Pharmacy Group",
"usage_limit": 10000,
"current_usage": 3,
"remaining_usage": 9997,
"usage_percentage": 0
}
}With a sub-client key
{
"success": true,
"data": {
"organization": "Demo Pharmacy Group",
"branch": "Cairo Branch",
"organization_usage_limit": 10000,
"organization_current_usage": 3,
"branch_current_usage": 0,
"remaining_usage": 9997,
"usage_percentage": 0
}
}ERP login
Exchanges organization credentials for a bearer token used by the unified endpoint.
curl -X POST https://api.mdb.com.sa/api/erp-auth/login \ -H "Content-Type: application/json" \ -d '{"email":"erpadmin@example.com","password":"••••••••"}'
Response 200 OK
{
"success": true,
"message": "Login successful",
"data": {
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9…",
"erpClient": {
"id": "9223d5e5-…",
"name": "Demo Pharmacy Group",
"email": "erpadmin@example.com",
"usage_limit": 10000,
"active": true
}
}
}data.token. Read it from there
rather than assuming a top-level field.
Status & severity
The unified endpoint returns an overall status; the classic endpoint returns
the highest result severity. Map them to your own UI as follows.
| Status | Raised when | Suggested handling |
|---|---|---|
| CRITICAL | Any MAJOR or CRITICAL interaction |
Block the dispense; require pharmacist override. |
| WARNING | Any MODERATE or MINOR interaction |
Show a dismissible warning with the guidance text. |
| NONE | Nothing found | Proceed normally. |
Severity values on individual interactions
MAJOR · MODERATE · MINOR ·
DUPLICATION · NONE. DUPLICATION flags
therapeutic duplication — two drugs sharing an active ingredient.
Quotas & limits
Every successful interaction check consumes one request from the organization quota. Sub-clients may optionally hold a reserved slice of that quota.
| Scope | Enforced when | Response |
|---|---|---|
| Organization | current_usage ≥ usage_limit |
429 with "Usage limit exceeded" |
| Sub-client | Its own limit is set and reached | 429 with scope: "sub_client" |
A sub-client with no limit is uncapped on its own and simply draws from the shared organization pool. Allocations can never be over-subscribed: the sum of all sub-client limits is validated against the organization quota.
{
"success": false,
"error": "Sub-client usage limit exceeded",
"scope": "sub_client",
"usage_limit": 4000,
"current_usage": 4000
}429 means the check did not run. Never let a quota error fall
through as "no interactions found" — queue the request or surface a hard error to the
pharmacist.
Error reference
| Status | Meaning | What to do |
|---|---|---|
400 | Validation failed — missing or conflicting fields | Fix the payload. Do not retry unchanged. |
401 | Missing/invalid API key, or missing/expired JWT | Re-authenticate; verify the key. |
403 | Organization or sub-client deactivated | Contact your MDB administrator. |
404 | Resource not found — unrecognised identifier | Re-resolve the identifier. |
409 | Conflict — resource already exists, or still referenced | Reconcile state before retrying. |
429 | Quota exhausted (organization or sub-client) | Stop sending; alert operations. |
500 | Unexpected server error | Retry once with backoff, then alert. |
success is present
Authentication and validation errors include success: false, but errors
raised inside a controller (404, 409, 500) return
only error and type. Branch on the HTTP status code, and read
error for the message — treating a missing success field as
success will silently swallow failures.
Authentication error — 401
API-key failures and JWT failures do not share an envelope:
// API key missing or wrong (x-api-key routes) { "success": false, "error": "Invalid API key" } // JWT missing or invalid (bearer routes) — no "success" field { "error": "Authentication failed" }
Validation error — 400
Every failed rule is listed in errors; error repeats the first.
{
"success": false,
"error": "Email is required",
"errors": [
"Email is required",
"Password is required"
]
}Request error — 400 / 403 / 404 / 409 / 500
Raised inside the request handler. Carries a machine-readable
type, but no success field.
// too few drugs { "error": "Provide at least two drug IDs or registration numbers", "type": "ValidationError" } // unknown registration numbers { "error": "Drugs not found for registration numbers: NOPE-1, NOPE-2", "type": "NotFoundError" }
type | Status | Cause |
|---|---|---|
ValidationError | 400 | Fewer than two distinct drugs supplied. |
NotFoundError | 404 | Identifier did not resolve to a drug. |
ForbiddenError | 403 | ERP client deactivated. |
UsageLimitExceeded | 429 | Quota exhausted. |
DuplicateError / ConflictError | 409 | Resource exists, or still referenced. |
Localization
Send Accept-Language to localize status messages and clinical guidance.
Supported: en, ar, fr, es,
ru, zh. Defaults to en.
| Localized | Always English |
|---|---|
| Status messages, description, monitoring and management guidance | Drug names, references and headers — kept in English for clinical accuracy |
Go-live checklist
- API keys stored server-side in a secret manager — never in client code or version control.
- One sub-client key per branch, so usage is attributable and revocable.
429handled as a hard failure, never as "no interactions".- Branch on
status/result, not on the HTTP code alone. - Request timeout set (10s is comfortable) with one bounded retry on
5xx. - Quota monitored via
/api/v1/usage-statswith an alert before exhaustion. - Drug identifiers reconciled against MDB, preferring UUIDs or registration numbers.
- Pharmacist override path defined for
CRITICALresults.