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.sa

All endpoints below are relative to this host. HTTPS only — plain HTTP is not served.

Two ways in Machine-to-machine traffic uses a long-lived API key. Interactive ERP dashboards use a short-lived JWT from the ERP login endpoint. Most integrations only need the API key.

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 typePrefixUse it forCounts 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
Treat API keys as secrets Keep them server-side. Never ship a key in a mobile app, browser bundle or public repository. Rotate immediately if one is exposed — regenerating a key takes effect at once and invalidates the previous value.

Quick start

  1. Get your keyYour MDB administrator creates the organization and issues a master key; you create a sub-client per branch from the ERP dashboard.
  2. Resolve drug identifiersSend either MDB drug_ids (UUIDs) or the local registration_numbers you already store.
  3. Call the checkerPOST the drug list on every prescription save or dispense.
  4. Act on the resultBlock or warn on CRITICAL, surface a soft warning on WARNING.
  5. Watch your quotaPoll usage stats, or handle 429 and 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

POST /api/v1/check-interactions x-api-key

The core drug–drug screening call. Counts as one request against your quota.

Request body

FieldTypeDescription
drug_idsstring[] (UUID)ONE OF MDB drug identifiers.
registration_numbersstring[]ONE OF National registration numbers, if you don't store MDB UUIDs.
drug_namesstring[]OPTIONAL Free-text names. Least reliable — prefer an identifier.
Provide exactly one identifier list Send 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

HeaderValueEffect
Accept-Languageen, ar, fr, es, ru, zhLocalizes status messages and clinical text.
x-include-food-interactionstrueAlso return food interactions.
x-include-disease-interactionstrueAlso return disease interactions.
x-include-pandemic-interactionstrueAlso 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"
    ]
  }'

Response 200 OK

{
  "success": true,
  "interactions": [],
  "result": "NONE",
  "message": "✅ No interaction detected.",
  "response_time_ms": 109
}
FieldTypeDescription
successbooleanRequest was processed.
resultstringHighest severity found: NONE, MINOR, MODERATE, MAJOR, DUPLICATION.
messagestringHuman-readable summary, localized.
interactionsobject[]One entry per interaction found; empty when clear.
response_time_msnumberServer-side processing time.
success is not the same as safe success: true only means the check ran. Always branch on result / status — never treat a 200 as "no interactions".

Unified check

POST /api/unified-interactions/check-unified-erp Bearer JWT

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

FieldTypeDescription
drug_idsstring[] (UUID)ONE OFMDB drug identifiers.
registration_numbersstring[]ONE OFMutually exclusive with drug_ids.
diseasesstring[]OPTIONALPatient conditions, e.g. ["Kidney Disease"].
foodstringOPTIONALFood item, e.g. "grapefruit". Omit to return all food interactions.
pandemicstringOPTIONALActive 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

GET /api/v1/usage-stats x-api-key

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

POST /api/erp-auth/login public

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
    }
  }
}
Note the envelope This endpoint returns the token at 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.

StatusRaised whenSuggested 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.

ScopeEnforced whenResponse
Organizationcurrent_usage ≥ usage_limit 429 with "Usage limit exceeded"
Sub-clientIts 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
}
Fail safe, not silent A 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

StatusMeaningWhat to do
400Validation failed — missing or conflicting fields Fix the payload. Do not retry unchanged.
401Missing/invalid API key, or missing/expired JWT Re-authenticate; verify the key.
403Organization or sub-client deactivated Contact your MDB administrator.
404Resource not found — unrecognised identifier Re-resolve the identifier.
409Conflict — resource already exists, or still referenced Reconcile state before retrying.
429Quota exhausted (organization or sub-client) Stop sending; alert operations.
500Unexpected server error Retry once with backoff, then alert.
Error bodies come in three shapes — don't assume 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"
}
typeStatusCause
ValidationError400Fewer than two distinct drugs supplied.
NotFoundError404Identifier did not resolve to a drug.
ForbiddenError403ERP client deactivated.
UsageLimitExceeded429Quota exhausted.
DuplicateError / ConflictError409Resource 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.

LocalizedAlways English
Status messages, description, monitoring and management guidance Drug names, references and headers — kept in English for clinical accuracy

Go-live checklist