---
title: Working with APIs
---

## Auth API

- The OAuth 2.0 Authorization Code grant flow with PKCE is used to issue Access, Refresh, and ID tokens.
- The Patient authorization code is generated by the server and then shared out-of-band as a secret link with the user.
- OAuth is configured from the Django Admin page (See Getting Started above).
- Endpoints and configuration details can be discovered from the OIDC metadata endpoint:
  `/o/.well-known/openid-configuration`
- The returned Access Token should be included in the `Authorization` header for all API requests with the prefix `Bearer `.
- Because the Patient authorization code is generated by the server, the PKCE code challenge and code verifier must be static values and set by the env vars (example below). The client then sends this `code_verifier` along with the authorization code to obtain tokens.

```bash
PATIENT_AUTHORIZATION_CODE_CHALLENGE = '-2FUJ5UCa7NK9hZWS0bc0W9uJ-Zr_-Pngd4on69oxpU'
PATIENT_AUTHORIZATION_CODE_VERIFIER  = 'f28984eaebcf41d881223399fc8eab27eaa374a9a8134eb3a900a3b7c0e6feab5b427479f3284ebe9c15b698849b0de2'

Client POST
Content-Type: application/x-www-form-urlencoded
code=4AWKhgaaomTSf9PfwxN4ExnXjdSEqh&grant_type=authorization_code&redirect_uri=https%3A%2F%2Fexample.com%2Fauth%2Fcallback
&client_id=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
&code_verifier=f28984eaebcf41d881223399fc8eab27eaa374a9a8134eb3a900a3b7c0e6feab5b427479f3284ebe9c15b698849b0de2
```

## Admin REST API

- The Admin API is used by the Web UI SPA for Practitioner/Patient/Organization/Study management and Patient data provider apps/clients to manage Patient consents.

### Profile

- The `profile` endpoint returns the current user details.

```json
// GET /api/v1/users/profile
{
    "id": 10001,
    "email": "peter@example.com",
    "firstName": "Peter",
    "lastName": "ThePatient",
    "patient": {
        "id": 40001,
        ...
    }
}
```

### Patient Consents

- The `consents` endpoint returns the studies that are pending and consented for the specified Patient. In this example, the Patient has been invited to *Demo Study 2* and has already consented to sharing blood glucose data with *Demo Study 1*.

```json
// GET /api/v1/patients/40001/consents
{
    "patient": {
        "id": 40001,
                //...
    },
    "consolidatedConsentedScopes": [
        {
            "id": 50002,
            "codingSystem": "https://w3id.org/openmhealth",
            "codingCode": "omh:blood-pressure:4.0",
            "text": "Blood pressure"
        }
    ],
    "studiesPendingConsent": [
        {
            "id": 30002,
            "name": "Demo Study 2",
            "organization": { ... }
            "dataSources": [ ... ],
            "pendingScopeConsents": [
                {
                    "code": {
                        "id": 50002,
                        "codingSystem": "https://w3id.org/openmhealth",
                                    "codingCode": "omh:blood-pressure:4.0",
                        "text": "Blood pressure"
                    },
                    "consented": null
                }
            ]
        }
    ],
    "studies": [
        {
            "id": 30001,
            "name": "Demo Study 1",
            "organization": { ... },
            "dataSources": [ ... ],
            "scopeConsents": [
                {
                    "code": {
                        "id": 50001,
                        "codingSystem": "https://w3id.org/openmhealth",
                        "codingCode": "omh:blood-glucose:4.0",
                        "text": "Blood glucose"
                    },
                    "consented": true
                }
            ]
        }
    ]
}
```

- To respond to requested consents, a POST is sent to the same `consents` endpoint with the scope and the `consented` boolean.

```json
// POST /api/v1/patients/40001/consents
{
  "studyScopeConsents": [
    {
      "studyId": 30002,
      "scopeConsents": [
        {
            "codingSystem": "https://w3id.org/openmhealth",
            "codingCode": "omh:blood-pressure:4.0",
            "consented": true
        }
      ]
    }
  ]
}

```

- A `PATCH` request can be sent with the same payload to update an existing Consent
- A `DELETE` request can be sen with the same payload excluding `scopeConsents.consented` to delete the Consent

## FHIR REST API

### Discovery

- The FHIR API publishes two public discovery documents. Both require **no authentication** (they work even if an invalid token is attached), send permissive CORS headers so browser apps can fetch them cross-origin, and are cacheable (`Cache-Control: public, max-age=3600`).
- Use the canonical `/FHIR/R5/` base for these. The lowercase `/FHIR/R5/` alias kept for pre-#661 clients serves the same documents but is **not** CORS-enabled ([jupyterhealth-exchange#779](https://github.com/jupyterhealth/jupyterhealth-exchange/issues/779)), so a browser app fetching it cross-origin will be blocked.
- `GET /FHIR/R5/metadata` returns the server's **CapabilityStatement** (FHIR R5, `kind: instance`). It is rendered from the server's FHIR mapping configuration at request time, so it always reflects what the running deployment actually supports — resource types, allowed interactions, and search parameters — and it negotiates `application/fhir+json` as well as plain JSON.

```json
// GET /FHIR/R5/metadata (abridged)
{
    "resourceType": "CapabilityStatement",
    "status": "active",
    "kind": "instance",
    "fhirVersion": "5.0.0",
    "format": ["json"],
    "rest": [
        {
            "mode": "server",
            "security": {
                "extension": [
                    {
                        "url": "http://fhir-registry.smarthealthit.org/StructureDefinition/oauth-uris",
                        "extension": [
                            { "url": "authorize", "valueUri": "https://jhe.fly.dev/o/authorize/" },
                            { "url": "token", "valueUri": "https://jhe.fly.dev/o/token/" }
                        ]
                    }
                ],
                "service": [{ "coding": [{ "code": "SMART-on-FHIR" }] }]
            },
            "resource": [
                {
                    "type": "Observation",
                    "interaction": [{ "code": "create" }, { "code": "read" }, { "code": "search-type" }, ...],
                    "searchParam": [{ "name": "date", "type": "date" }, { "name": "code", "type": "token" }, ...]
                },
                ...
            ]
        }
    ]
}
```

- Reading the CapabilityStatement correctly requires knowing that JHE serves resources from **two stores**: JHE-native records (backed by Django models) and imported records (opaque FHIR bodies linked to a registered FHIR source). Each resource entry declares the **union** of both stores' capabilities, with the caveats spelled out in its `documentation`:
  - `update`, `patch`, and `delete` apply to **imported records only**; JHE-native records are read-only through FHIR (writes return `405`).
  - Creates routed to the imported-record store must name a FHIR source via the `X-JHE-FHIR-Source-ID` header or the resource's `meta.source`.
  - Search parameters declared only by the imported-record store (their `documentation` says so) apply only when selecting that store with `_source`; the default JHE-native search ignores them.
  - JHE-native Observations code their measurement with system `https://w3id.org/openmhealth` and carry the full Open mHealth data point as a base64 JSON `valueAttachment`.
- Every resource also supports `_id`, `_lastUpdated`, and `_source` search parameters, plus `_sort` (`date`, `lastUpdated`) and `_summary=count`; each declared interaction and parameter carries the US Core expectation extension (`SHALL`).
- `GET /FHIR/R5/.well-known/smart-configuration` returns the **SMART App Launch discovery document** — how a client (especially a public PKCE client) finds the OAuth endpoints without hardcoding them:

```json
// GET /FHIR/R5/.well-known/smart-configuration
{
    "authorization_endpoint": "https://jhe.fly.dev/o/authorize/",
    "token_endpoint": "https://jhe.fly.dev/o/token/",
    "grant_types_supported": ["authorization_code"],
    "response_types_supported": ["code"],
    "code_challenge_methods_supported": ["S256"],
    "capabilities": ["launch-standalone", "client-public", "client-confidential-symmetric"]
}
```

- Both documents derive their absolute URLs from the request host, so every JHE deployment automatically advertises its own endpoints.

### Patients

- The `FHIR Patient` endpoint returns a list of Patients as a FHIR Bundle for a given Study ID passed as query parameter`_has:Group:member:_id` or alternatively a single Patient matching the query parameter `identifier=<system>|<value>`

| Query Parameter         | Example                  | Description                                            |
| ----------------------- | ------------------------ | ------------------------------------------------------ |
| `_has:Group:member:_id` | `30001`                  | Filter by Patients that are in the Study with ID 30001 |
| `identifier`            | \`http://ehr.example.com | abc123\`                                               |

```json
// GET /FHIR/R5/Patient?_has:Group:member:_id=30001
{
    "resourceType": "Bundle",
    "type": "searchset",
    "entry": [
        {
            "resource": {
                "resourceType": "Patient",
                "id": "40001",
                "meta": {
                    "lastUpdated": "2024-10-23T12:35:25.142027+00:00"
                },
                "identifier": [
                    {
                        "value": "fhir-1234",
                        "system": "http://ehr.example.com"
                    }
                ],
                "name": [
                    {
                        "given": [
                            "Peter"
                        ],
                        "family": "ThePatient"
                    }
                ],
                "birthDate": "1980-01-01",
                "telecom": [
                    {
                        "value": "peter@example.com",
                        "system": "email"
                    },
                    {
                        "value": "347-111-1111",
                        "system": "phone"
                    }
                ]
            }
        },
        ...
```

### Observations

- The `FHIR Observation` endpoint returns a list of Observations as a FHIR Bundle
- At least one of Study ID, passed as `patient._has:Group:member:_id` or Patient ID, passed as `patient` or Patient Identifier passed as `patient.identifier=<system>|<value>` query parameters are required
- `subject.reference` references a Patient ID
- `device.reference` references a Data Source ID
- `valueAttachment` is Base 64 Encoded Binary JSON

| Query Parameter                 | Example                        | Description                                            |
| ------------------------------- | ------------------------------ | ------------------------------------------------------ |
| `patient._has:Group:member:_id` | `30001`                        | Filter by Patients that are in the Study with ID 30001 |
| `patient`                       | `40001`                        | Filter by single Patient with ID 40001                 |
| `patient.identifier`            | \`http://ehr.example.com       | abc123\`                                               |
| `code`                          | \`https://w3id.org/openmhealth | omh:blood-pressure:4.0\`                               |

```json
// GET /FHIR/R5/Observation?patient._has:Group:member:_id=30001&patient=40001&code=https://w3id.org/openmhealth|omh:blood-pressure:4.0
{
    "resourceType": "Bundle",
    "type": "searchset",
    "entry": [
        {
            "resource": {
                "resourceType": "Observation",
                "id": "63416",
                "meta": {
                    "lastUpdated": "2024-10-25T21:14:02.871132+00:00"
                },
                "identifier": [
                    {
                        "value": "6e3db887-4a20-3222-9998-2972af6fb091",
                        "system": "https://ehr.example.com"
                    }
                ],
                "status": "final",
                "subject": {
                    "reference": "Patient/40001"
                },
                "device": {
                  "reference": "Device/70001"
                },
                "code": {
                    "coding": [
                        {
                            "code": "omh:blood-pressure:4.0",
                            "system": "https://w3id.org/openmhealth"
                        }
                    ]
                },
                "valueAttachment": {
                    "data": "eyJib2R5IjogeyJlZmZlY3RpdmVfdGltZV9mcmFtZSI6IHsiZGF0ZV90aW1lIjogIjIwMjQtMDUt\nMDJUMDc6MjE6MDAtMDc6MDAifSwgInN5c3RvbGljX2Jsb29kX3ByZXNzdXJlIjogeyJ1bml0Ijog\nIm1tSGciLCAidmFsdWUiOiAxMjJ9LCAiZGlhc3RvbGljX2Jsb29kX3ByZXNzdXJlIjogeyJ1bml0\nIjogIm1tSGciLCAidmFsdWUiOiA3N319LCAiaGVhZGVyIjogeyJ1dWlkIjogIjZlM2RiODg3LTRh\nMjAtMzIyMi05OTk4LTI5NzJhZjZmYjA5MSIsICJtb2RhbGl0eSI6ICJzZW5zZWQiLCAic2NoZW1h\nX2lkIjogeyJuYW1lIjogImJsb29kLXByZXNzdXJlIiwgInZlcnNpb24iOiAiMy4xIiwgIm5hbWVz\ncGFjZSI6ICJvbWgifSwgImNyZWF0aW9uX2RhdGVfdGltZSI6ICIyMDI0LTEwLTI1VDIxOjEzOjMx\nLjQzOFoiLCAiZXh0ZXJuYWxfZGF0YXNoZWV0cyI6IFt7ImRhdGFzaGVldF90eXBlIjogIm1hbnVm\nYWN0dXJlciIsICJkYXRhc2hlZXRfcmVmZXJlbmNlIjogImh0dHBzOi8vaWhlYWx0aGxhYnMuY29t\nL3Byb2R1Y3RzIn1dLCAic291cmNlX2RhdGFfcG9pbnRfaWQiOiAiZTZjMTliMDQyOGM4NWJiYjdj\nMTk4MGNiOTRkZDE3N2YiLCAic291cmNlX2NyZWF0aW9uX2RhdGVfdGltZSI6ICIyMDI0LTA1LTAy\nVDA3OjIxOjAwLTA3OjAwIn19",
                    "contentType": "application/json"
                }
            }
        },
        ...
```

- Observations are uploaded as FHIR Batch bundles sent as a POST to the root endpoint

```json
// POST /FHIR/R5/
{
  "resourceType": "Bundle",
  "type": "batch",
  "entry": [
    {
      "resource": {
        "resourceType": "Observation",
        "status": "final",
        "code": {
          "coding": [
            {
              "system": "https://w3id.org/openmhealth",
              "code": "omh:blood-pressure:4.0"
            }
          ]
        },
        "subject": {
          "reference": "Patient/40001"
        },
        "device": {
          "reference": "Device/70001"
        },
        "identifier": [
            {
                "value": "6e3db887-4a20-3222-9998-2972af6fb091",
                "system": "https://ehr.example.com"
            }
        ],
        "valueAttachment": {
          "contentType": "application/json",
          "data": "eyJzeXN0b2xpY19ibG9vZF9wcmVzc3VyZSI6eyJ2YWx1ZSI6MTQyLCJ1bml0IjoibW1IZyJ9LCJkaWFzdG9saWNfYmxvb2RfcHJlc3N1cmUiOnsidmFsdWUiOjg5LCJ1bml0IjoibW1IZyJ9LCJlZmZlY3RpdmVfdGltZV9mcmFtZSI6eyJkYXRlX3RpbWUiOiIyMDIxLTAzLTE0VDA5OjI1OjAwLTA3OjAwIn19"
        }
      },
      "request": {
        "method": "POST",
        "url": "Observation"
      }
    },
    ...
```
