All documentation Download (Markdown) Technical

Provider · provider1.pdhc.se

Technical manual


Provider Portal — Technical Integration Guide

For developers integrating with the PDHC provider delivery system.
Covers API endpoints, authentication, data formats, and security model.


1) Architecture overview

                    PDHC Platform                         Provider Side
                    ─────────────                         ─────────────

              ┌──────────────────┐
              │  request.pdhc    │
              │  (orchestrator)  │
              └──────┬───────────┘
                     │
           ┌─────────┴──────────┐
           │                    │
      Push delivery        Poll/Subscribe
      (Type 1)             (Type 2)
           │                    │
           ▼                    ▼
    POST /inbound/push    GET /provider/feed  ──►  GET /provider/download/<sr>
           │                    │                          │
           └────────┬───────────┘                          │
                    │                                      │
                    ▼                                      ▼
              ┌──────────────────┐              ┌──────────────────┐
              │  provider.pdhc   │              │  FHIR Bundle     │
              │  (portal)        │              │  + grant_token   │
              └──────┬───────────┘              └──────────────────┘
                     │
                     │  Provider completes work
                     │
                     ▼
              POST /provider/report/<sr>
              (PAT + grant_token)
                     │
                     ▼
              ┌──────────────────┐
              │  gateway.pdhc    │  ◄── validates report, derives org/contract
              │  derives context │      from PAT + grant + SR context
              │  & stores report │
              └──────────────────┘

2) Authentication

2.1 Provider Access Token (PAT)

All communication between provider.pdhc and request.pdhc uses a Provider Access Token. This replaces the legacy SSO API key.

A PAT is issued by an administrator on the request platform and binds to:
- A specific provider organisation (org GUID)
- A specific contract (contract GUID)
- Allowed scopes (read, write, or read,write)
- A delivery mode (push or poll)

The PAT determines your identity. You never pass your organisation GUID as a parameter — it is derived from the token.

2.2 Using the PAT

Include the token in every request to request.pdhc:

GET /api/v1/provider/feed
X-Provider-Token: <your-pat-token>
Accept: application/json

2.3 PAT lifecycle

Event What happens
Issued Admin creates PAT; raw token shown once, never stored
Active Token validates against bcrypt hash; org/contract bound
Expired Past expires_at; rejected with 401
Revoked Admin revokes; rejected with 401

2.4 Push secret (Type 1 providers)

If your organisation receives pushed deliveries, request.pdhc sends a X-Push-Secret header with each push. Your endpoint must validate this header matches the shared secret established during PAT issuance.

2.5 Local API keys

The provider portal itself uses local API keys (separate from PATs) for its own API and web UI. These are managed via the portal's key management endpoints and stored as bcrypt hashes.


3) Data flow — Poll mode (Type 2)

3.1 Step 1: Fetch the feed

GET /api/v1/provider/feed?since=2026-03-24T00:00:00Z&limit=50
X-Provider-Token: <pat>

Response (metadata only — no patient data):

{
  "items": [
    {
      "service_request_guid": "sr-abc-123",
      "match_guid": "match-456",
      "status": "pending",
      "title": "Mobility Assessment",
      "intent": "order",
      "priority": "routine",
      "contract_guid": "con-789",
      "created_at": "2026-03-24T10:00:00Z",
      "updated_at": "2026-03-24T10:00:00Z",
      "download_url": "/api/v1/provider/download/sr-abc-123"
    }
  ],
  "total": 1
}

GDPR data minimization: The feed contains only operational metadata. No patient names, diagnoses, or clinical data. This reduces exposure if the feed is cached or logged.

3.2 Step 2: Download the FHIR Bundle

GET /api/v1/provider/download/sr-abc-123
X-Provider-Token: <pat>

Response:

{
  "fhir_resource": {
    "resourceType": "ServiceRequest",
    "id": "sr-abc-123",
    "status": "active",
    "intent": "order",
    "priority": "routine",
    "subject": { "reference": "Patient/pat-001", "display": "John Doe" },
    "authoredOn": "2026-03-24T10:00:00Z",
    "occurrencePeriod": {
      "start": "2026-03-24",
      "end": "2026-04-24"
    },
    "contained": [
      {
        "resourceType": "CarePlan",
        "id": "cp-001",
        "title": "Mobility Assessment",
        "goal": [ ... ],
        "activity": [ ... ]
      },
      {
        "resourceType": "Patient",
        "id": "pat-001",
        "name": [{ "given": ["John"], "family": "Doe" }]
      }
    ],
    "basedOn": [
      { "reference": "Contract/con-789" }
    ]
  },
  "grant_token": "a1b2c3d4e5f6...",
  "service_request_guid": "sr-abc-123",
  "patient_guid": "pat-001",
  "contract_guid": "con-789",
  "provider_org_guid": "org-xyz"
}

Important: Store the grant_token — you need it to submit reports back. It is an HMAC-signed composite key that proves the server authorised you to access this specific patient's data.


4) Data flow — Push mode (Type 1)

4.1 Receiving a push

request.pdhc sends a FHIR Bundle to your registered endpoint:

POST /api/v1/inbound/push
X-Push-Secret: <shared-secret>
Content-Type: application/fhir+json

{
  "resourceType": "Bundle",
  "type": "message",
  "meta": {
    "tag": [
      { "system": "https://pdhc.se/delivery", "code": "receipt_token", "display": "rt-abc" },
      { "system": "https://pdhc.se/delivery", "code": "grant_token", "display": "a1b2c3..." }
    ]
  },
  "entry": [
    { "resource": { "resourceType": "ServiceRequest", ... } }
  ]
}

4.2 Processing the push

  1. Validate X-Push-Secret matches your configured secret
  2. Extract receipt_token and grant_token from meta.tag
  3. Extract the ServiceRequest from entry[0].resource
  4. Store the request, care plan, and grant token locally
  5. Return 202 Accepted

4.3 Acknowledging receipt

After processing, acknowledge the delivery:

POST /api/v1/provider/receipt/rt-abc/ack
X-Provider-Token: <pat>
Content-Type: application/json

{ "status": "acknowledged" }

5) Submitting a report

When the provider has completed the work, submit a minimal report to gateway.pdhc. The gateway derives organisation_guid from the PAT and contract_guid from the grant — providers do not need to send these fields.

POST /api/v1/provider/report/sr-abc-123
X-Provider-Token: <pat>
Content-Type: application/json

{
  "patient_guid": "pat-001",
  "grant_token": "a1b2c3d4e5f6...",
  "status": "completed",
  "report_payload": {
    "observations": [
      {
        "transaction_guid": "tx-001",
        "value": "42",
        "recorded_at": "2026-03-25T14:30:00Z",
        "notes": "Full range of motion restored"
      }
    ]
  }
}

Per observation, send only:

Field Required Description
transaction_guid yes Links to a transaction in the SR's CarePlan
value yes The measured/observed value
recorded_at yes ISO-8601 timestamp of observation
notes no Free-text note (omit entirely if empty)

Do NOT send concept_guid or unit — the gateway derives these from the ServiceRequest context (transaction map).

5.1 Validation chain (gateway.pdhc)

The gateway validates the report through a 10-step chain:

Step What is verified
1. PAT Token is valid, not expired, not revoked; org_guid derived from token
2. Body org cross-check If organisation_guid is provided in body, must match PAT org (backward compat)
3. Grant validation Delegated to request.pdhc internal API; returns contract_guid, grant_type
4. Body contract cross-check If contract_guid is provided in body, must match grant's contract (backward compat)
5. SR context Fetched from request.pdhc internal API; returns transactions, goals, patient_guid
6. Patient cross-check patient_guid in body must match SR subject
7. Observation enrichment concept_guid and unit derived from SR transaction map
8. Observation validation Values validated against transaction definitions
9. Contract scope Concepts checked against contract return_scope (obligatory/optional)
10. Store & audit Report stored, audit trail created, receipt returned

If any check fails, the request is rejected with an appropriate error code and the attempt is audit-logged.

5.2 Response

{
  "status": "recorded",
  "service_request_guid": "sr-abc-123",
  "match_status": "completed"
}

6) FHIR ServiceRequest structure

The ServiceRequest is a FHIR R5 envelope containing:

Field Content
id ServiceRequest GUID
status active
intent order, plan, or proposal
priority routine, urgent, asap, stat
subject Reference to contained Patient
authoredOn When the request was sent
occurrencePeriod Validity period (start/end)
requester Who created the request (with organisation extension)
performer Receiver organisations
basedOn Reference to governing Contract
contained Array of: CarePlan, Patient, Goals

6.1 Contained CarePlan

The CarePlan contains the clinical instructions:

{
  "resourceType": "CarePlan",
  "title": "Mobility Assessment",
  "goal": [
    {
      "description": { "text": "Assess range of motion" },
      "target": [
        { "measure": { "text": "Shoulder flexion" }, "detailString": "degrees" }
      ]
    }
  ],
  "activity": [
    {
      "detail": {
        "description": "Measure shoulder flexion bilaterally",
        "status": "not-started"
      }
    }
  ]
}

7) Provider portal API (local)

The provider portal exposes its own API for local integrations (EHR systems, mobile apps).

7.1 Authentication

All endpoints require X-API-Key header with a local portal API key.

7.2 Endpoints

Method Path Scope Purpose
GET /api/v1/provider-tasks/my read List all tasks
GET /api/v1/provider-tasks/{receipt_token} read Get task detail
POST /api/v1/provider-tasks/{receipt_token}/accept write Acknowledge task
POST /api/v1/provider-tasks/{receipt_token}/report write Submit report
GET /api/v1/provider-tasks/{receipt_token}/careplan-details read Get care plan
GET /api/v1/provider-receipts read List submission receipts
GET /api/v1/audit-log read View audit trail
POST /api/v1/api-keys write Create API key
POST /api/v1/api-keys/{guid}/revoke write Revoke key
POST /api/v1/api-keys/{guid}/rotate write Rotate key
POST /api/v1/inbound/push Receive push (X-Push-Secret)
GET /api/v1/health Health check (no auth)

7.3 Report submission format

POST /api/v1/provider-tasks/{receipt_token}/report
X-API-Key: <local-key>
Content-Type: application/json

{
  "provider_payload": {
    "observations": [
      {
        "transaction_guid": "tx-001",
        "value": "normal",
        "notes": "No abnormalities detected"
      }
    ]
  },
  "notes": "Patient cooperative, assessment complete",
  "receipt_message": "Assessment completed on 2026-03-25"
}

Observations are validated against the care plan's transaction definitions when available.


8) Configuration

8.1 Environment variables

# Instance identity
PROVIDER_GUID=<your-org-guid>          # assigned by PDHC admin
PROVIDER_NAME="Clinic Name"

# Connection to request.pdhc
REQUEST_SERVICE_URL=https://request.pdhc.se/api/v1
PROVIDER_TOKEN=<pat-from-admin>        # Provider Access Token

# Push reception (Type 1 only)
PUSH_SECRET=<shared-secret>            # must match PAT's push_auth_key

# Sync settings (Type 2 — poll)
SYNC_ENABLED=true
SYNC_INTERVAL_SECONDS=60              # how often to check for new requests

# Database
DATABASE_URL=postgresql://user:pass@localhost:5432/provider_db

# Security
SECRET_KEY=<random-string>
BOOTSTRAP_API_KEY=<initial-admin-key>  # used on first run only

8.2 Legacy configuration (deprecated)

SSO_API_KEY=<sso-key>                  # replaced by PROVIDER_TOKEN

If PROVIDER_TOKEN is set, the portal uses PAT-based authentication. If only SSO_API_KEY is set, it falls back to legacy mode.


9) Security model

9.1 Defense in depth

Layer 1: PAT  ──────────  Authenticates provider org identity (gateway derives org_guid)
Layer 2: Grant token  ───  Validated by request.pdhc; proves authorised exchange; yields contract_guid
Layer 3: SR context  ────  Fetched from request.pdhc; yields transactions, patient, contract
Layer 4: Contract scope ─  Fetched from contract.pdhc; enforces return_scope (obligatory/optional)
Layer 5: Audit trail  ───  Every access logged with patient GUID (GDPR)

9.2 What each token protects

Token Scope Issued by Stored where
PAT Org + contract identity request.pdhc admin Provider config (.env)
Grant token Single SR + patient exchange request.pdhc (auto) InboundRequest.grant_token
Push secret Mutual auth for push delivery request.pdhc admin Provider config (.env)
Local API key Portal access Portal admin Portal DB (bcrypt)

9.3 HMAC stays on request.pdhc

The HMAC secret used to compute grant tokens is held only by request.pdhc. The gateway never sees or computes HMAC — it delegates grant validation to request.pdhc via an internal service-to-service API call (POST /internal/grant/validate with X-Service-Key).

9.3 Key rotation

9.4 GDPR compliance


10) Error codes

HTTP Code Meaning
400 bad_request Missing or invalid fields
400 VALIDATION_ERROR Observation validation failed (see details)
401 unauthenticated Missing or invalid PAT / API key / push secret
403 unauthorized Valid token but insufficient scope or org mismatch
404 not_found Resource does not exist
409 CONFLICT Task in wrong state for this action
422 VALIDATION_ERROR Guided response validation failed
502 UPSTREAM_ERROR request.pdhc unreachable or returned error

All errors follow the format:

{
  "code": "error_code",
  "message": "Human-readable description"
}

11) Database schema

11.1 Core tables

Table Purpose
providers Provider instance identity
api_keys Local API keys (bcrypt hashed)
inbound_requests Synced requests from request.pdhc
provider_tasks Working task queue
submission_receipts Report submission outcomes
task_audit_logs Audit trail
sync_states Sync cursor and status
careplan_caches Cached care plan data (1h TTL)

11.2 Key columns on inbound_requests

Column Type Purpose
request_guid String(36) ServiceRequest GUID from request.pdhc
fhir_resource JSON Full FHIR ServiceRequest envelope
patient_guid String(36) For composite key report submission
contract_guid String(36) For composite key report submission
grant_token String(128) HMAC token for authorised data exchange
careplan_json JSON Extracted CarePlan from FHIR resource
checksum String(64) SHA-256 for change detection