Physician Registry API

Centralized physician credentials, licensing, and routing. Single source of truth for both GuideGLP and YourEra platforms.

Overview

The Physician Registry is a standalone microservice that consolidates physician credentials, state licenses, and prescriber routing rules into a single API. It replaces hardcoded prescriber data in both the YourEra orchestrator (medication-config.ts) and GuideGLP's local physician tables.

Base URL

https://api.yourera.com

Endpoints are at /physicians/* and /route/* directly (no prefix).

Key Concepts

Workflow: YourEra Prescription Approval

// 1. Physician approves prescription for patient in LA
// 2. Prescription Orchestrator queries the registry
GET /route?platform=yourera&state=LA

// 3. Registry returns Ali Nolan (priority 10 for LA)
{
  "physician": {
    "firstName": "Alison",
    "lastName": "Nolan",
    "npi": "1982609764",
    ...
  },
  "licenseNumber": "APRN.027876",
  "priority": 10
}

// 4. Orchestrator submits to Pharmacy Router with Ali's credentials

Workflow: GuideGLP Order

// 1. Physician submits order for patient in TX
// 2. GuideGLP verifies license
GET /physicians/{id}/verify-license?state=TX

// 3. Registry confirms authorization
{
  "licensed": true,
  "licenseNumber": "TEST-TX-001",
  "type": "TEST"
}

// 4. GuideGLP fetches full profile for pharmacy submission
GET /physicians/{id}

Authentication

All endpoints (except /health) require HMAC-SHA256 authentication, using the same pattern as the Pharmacy Router.

Request Headers

HeaderDescription
X-API-Key Your API key (from registry_api_keys table)
X-Timestamp ISO 8601 timestamp of the request (must be within 5 minutes)
X-Signature HMAC-SHA256 hex digest of timestamp + '.' + body

Signing a Request

import crypto from 'node:crypto';

const timestamp = new Date().toISOString();
const body = JSON.stringify(payload);

const signature = crypto
  .createHmac('sha256', API_SECRET)
  .update(timestamp + '.' + body)
  .digest('hex');

const headers = {
  'X-API-Key': API_KEY,
  'X-Timestamp': timestamp,
  'X-Signature': signature,
  'Content-Type': 'application/json',
};
Timing Safety Signatures are verified using crypto.timingSafeEqual() to prevent timing attacks. Requests older than 5 minutes are rejected.

List Physicians

GET /physicians

Returns all active physicians. Supports filtering by platform, organization, and active status.

Query Parameters

ParameterTypeDescription
platform string optional Filter by platform: yourera, guideglp. Also returns physicians with both.
organization string optional Filter by organization (case-insensitive)
active boolean optional Filter by active status. Defaults to true.

Response 200

{
  "physicians": [
    {
      "id": "550e8400-e29b-41d4-a716-446655440000",
      "email": "ali@supportivbar.com",
      "firstName": "Alison",
      "lastName": "Nolan",
      "suffix": "FNP-C",
      "npi": "1982609764",
      "organization": "Biologics",
      "platform": "both",
      "isActive": true,
      ...
    }
  ]
}

Get Physician

GET /physicians/:id

Returns a physician's full profile including all active licenses.

Response 200

{
  "physician": {
    "id": "550e8400-...",
    "firstName": "Alison",
    "lastName": "Nolan",
    "suffix": "FNP-C",
    "npi": "1982609764",
    "deaNumber": null,
    "phone": "5042504866",
    "addressLine1": "1331 Ochsner Blvd",
    "addressCity": "Covington",
    "addressState": "LA",
    "addressZip": "70433",
    "canvasPractitionerId": "de87795cca...",
    "organization": "Biologics",
    "platform": "both",
    "isActive": true
  },
  "licenses": [
    {
      "state": "LA",
      "licenseNumber": "APRN.027876",
      "expirationDate": "2027-01-31",
      "licenseType": "APRN"
    },
    ...
  ]
}

Error 404

{ "error": "Physician not found" }

Create Physician

POST /physicians

Creates a new physician record.

Request Body

FieldTypeDescription
firstNamestring requiredFirst name (max 100)
lastNamestring requiredLast name (max 100)
npistring requiredNational Provider Identifier
emailstring optionalEmail address (unique)
suffixstring optionalCredential suffix (FNP-C, MD, DO, PA)
deaNumberstring optionalDEA registration number
phonestring optionalPhone number
faxstring optionalFax number
addressLine1string optionalStreet address
addressCitystring optionalCity
addressStatestring optionalState (2-letter code)
addressZipstring optionalZIP code
signatureImagestring optionalBase64-encoded signature PNG
organizationstring optionalOrganization name
platformstring optionalguideglp, yourera, or both
canvasPractitionerIdstring optionalCanvas EMR practitioner UUID

Response 201

{
  "physician": { /* full physician object */ }
}

Error 409

{ "error": "A physician with this email already exists" }

Update Physician

PUT /physicians/:id

Updates a physician's profile. All fields are optional — only provided fields are updated.

Request Body

Same fields as Create Physician, all optional.

Response 200

{
  "physician": { /* updated physician object */ }
}

Delete Physician

DELETE /physicians/:id

Soft-deletes a physician by setting isActive = false. The record is preserved for audit purposes.

Response 200

{
  "success": true,
  "physician": { /* physician with isActive: false */ }
}

List Licenses

GET /physicians/:id/licenses

Returns all active licenses for a physician.

Response 200

{
  "licenses": [
    {
      "id": "...",
      "physicianId": "...",
      "state": "LA",
      "licenseNumber": "APRN.027876",
      "expirationDate": "2027-01-31",
      "licenseType": "APRN",
      "isActive": true
    }
  ]
}

Add License

POST /physicians/:id/licenses

Adds a new state license to a physician.

Request Body

FieldTypeDescription
statestring required2-letter state code (auto-uppercased)
licenseNumberstring requiredLicense number
expirationDatestring optionalExpiration date (YYYY-MM-DD)
licenseTypestring optionalLicense type (MD, DO, NP, FNP, PA, APRN)

Response 201

{
  "license": { /* created license object */ }
}

Update License

PUT /physicians/:id/licenses/:lid

Updates a license record. All fields are optional.

Response 200

{
  "license": { /* updated license object */ }
}

Delete License

DELETE /physicians/:id/licenses/:lid

Soft-deletes a license by setting isActive = false.

Response 200

{
  "success": true,
  "license": { /* license with isActive: false */ }
}

Verify License

GET /physicians/:id/verify-license?state=XX

Checks whether a physician has an active, non-expired license in the specified state. Used by GuideGLP before submitting orders.

Query Parameters

ParameterTypeDescription
state string required 2-letter state code (case-insensitive)

Response: Licensed 200

{
  "licensed": true,
  "state": "LA",
  "licenseNumber": "APRN.027876",
  "expirationDate": "2027-01-31",
  "type": "APRN"
}

Response: Not Licensed 200

{
  "licensed": false,
  "state": "TX"
}

Response: Expired 200

{
  "licensed": false,
  "state": "NM",
  "reason": "License expired",
  "expirationDate": "2024-01-01"
}

Route Physician

GET /route?platform=yourera&state=LA

Returns the best physician for a given platform and state based on priority routing rules. The highest-priority physician with an active license wins.

Query Parameters

ParameterTypeDescription
platform string required yourera or guideglp
state string required 2-letter state code
physicianId string optional If provided, verifies this specific physician instead of finding the best one

Response: Best Physician Found 200

{
  "physician": {
    "id": "550e8400-...",
    "firstName": "Alison",
    "lastName": "Nolan",
    "suffix": "FNP-C",
    "npi": "1982609764",
    "deaNumber": null,
    "phone": "5042504866",
    "fax": null,
    "addressLine1": "1331 Ochsner Blvd",
    "addressCity": "Covington",
    "addressState": "LA",
    "addressZip": "70433",
    "canvasPractitionerId": "de87795cca..."
  },
  "licenseNumber": "APRN.027876",
  "priority": 10
}

Response: No Physician Available 200

{
  "physician": null,
  "reason": "No physician available for yourera in ZZ"
}

Response: Specific Physician Verification 200

When physicianId is provided:

// Authorized
{
  "authorized": true,
  "licenseNumber": "APRN.027876"
}

// Not authorized
{
  "authorized": false,
  "reason": "No active license in TX"
}

Self-Service Endpoints

These endpoints allow physicians to manage their own profiles, scoped by their API key. Used by the GuideGLP settings page.

GET /physicians/me

Returns the physician's own profile and licenses, based on the API key's associated physician.

Response 200

{
  "physician": { ... },
  "licenses": [ ... ]
}
PUT /physicians/me

Updates the physician's own profile.

POST /physicians/me/signature

Uploads a signature image (base64 PNG).

Request Body

{
  "signatureImage": "data:image/png;base64,iVBOR..."
}

Response 200

{ "success": true }

Routing Logic

The routing engine uses a priority-based system. When a caller requests a physician for a platform and state, the registry:

  1. Finds all active routing rules matching the platform + state
  2. Sorts by priority descending (highest first)
  3. For each candidate, verifies the physician is active and has a non-expired license in that state
  4. Returns the first match

Current Routing Rules

PhysicianPlatformStatesPriority
Ali Nolan (FNP-C) yourera AK, CT, HI, LA, NM, NY 10 (preferred)
Neelima Singh (MD) yourera All 50 states 5 (fallback)
Example A request for platform=yourera&state=LA returns Ali Nolan (priority 10), since she outranks Neelima Singh (priority 5) for LA. A request for state=OH returns Neelima Singh, since Ali has no routing rule for OH.

Adding a New Physician

To add a new physician to the routing:

  1. Create the physician: POST /physicians
  2. Add state licenses: POST /physicians/:id/licenses (one per state)
  3. Insert routing rules into the physician_routing table with the desired priority
No Code Changes Required Unlike the previous system where adding a prescriber required modifying medication-config.ts and redeploying, the registry is entirely data-driven. New physicians are added via API calls.

Health Check

GET /health

Returns service status. No authentication required.

Response 200

{
  "status": "ok",
  "service": "physician-registry",
  "timestamp": "2026-03-20T12:00:00.000Z"
}