(g)(10): Standardized API for Patient and Population Services
Introduction
This document describes how to access protected health information through Aidbox's ONC g(10) certified API. The API provides health information using FHIR® R4, a set of clinical interoperability resources under the HL7 standards organization. FHIR is based on common web standards and can be accessed through a RESTful protocol where each FHIR resource has a known URL.
Aidbox implements the §170.315(g)(10) Standardized API for Patient and Population Services certification criterion, enabling:
- Single-patient data access via US Core profiles
- Population-level data access via Bulk Data Export
- Secure authorization using SMART App Launch Framework (OAuth 2.0)
FHIR Endpoints
Note: Production and test endpoints are deployment-specific. Replace [your-aidbox] with your Aidbox instance subdomain.
Production Endpoint
FHIR Base URL: https://[your-aidbox]/fhir
Authorization Server: https://[your-aidbox]/auth
Standards Compliance
Aidbox complies with the following ONC-specified standards:
| Standard | Version | Regulation Reference |
|---|---|---|
| FHIR | R4 (4.0.1) | §170.215(a)(1) |
| US Core Implementation Guide | 6.1.0 (USCDI v3) | §170.215(b)(1) |
| SMART App Launch Framework | 2.0.0 | §170.215(c) |
| Bulk Data Access IG | 2.0.0 | §170.215(d) |
Compliance Deadline: US Core 6.1.0 / USCDI v3 required by December 31, 2025.
Additional Versions Supported:
- US Core 3.1.1 (USCDI v1)
- US Core 7.0.0 (USCDI v4, SVAP)
- US Core 8.0.0+ (USCDI v5, SVAP)
Types of SMART-on-FHIR Applications
Aidbox supports three application types:
- Standalone App: Patient-facing application launched outside the EHR with patient context selection at launch time.
- EHR-Embedded App: Application launched from within the EHR with pre-established patient and encounter context.
- Backend Services App: Server-to-server application for bulk data export and multi-patient operations without user interface (system-level scopes).
Types of Authentication Supported
Aidbox supports the following OAuth 2.0 client authentication methods:
- Public Client (no client secret): Patient-facing apps using PKCE for authorization code flow
- Reference: SMART App Launch - Public Client
- Confidential Client - Symmetric (client secret): Server-side apps using
client_secret_basic- Reference: SMART App Launch - Symmetric Auth
- Confidential Client - Asymmetric (private key JWT): Backend services using signed JWT assertions
- Reference: SMART App Launch - Asymmetric Auth
Steps for SMART App Launch
1. Client App Registration
Aidbox provides a Developer Sandbox Portal for self-service app registration, testing, and submission.
Developer Registration Process:
- Sign Up: Open the Developer Portal and click Sign Up
- Complete Registration: Fill out the developer registration form
- Confirm Email: Check email and click Confirm Email Address
- Set Password: Create password on confirmation page
- Sign In: Access your developer dashboard
Register a SMART App:
- From dashboard, click Register New
- Fill in app details:
- App Name: Your application name
- Confidentiality: Public or Confidential
- Redirect URL: Where users return after authorization (e.g.,
https://myapp.example.com/callback) - Launch URL: Your app's SMART launch endpoint (e.g.,
https://myapp.example.com/launch.html)
- Click Create App
- Copy the Client ID from your app's draft page
- Test Launch: Click Test Launch to test with sample patient data (
Patient/test-pt-1) - Submit for Review: Click Submit for Review when ready
Developer portal documentation
2. Retrieve .well-known/smart-configuration
To discover Aidbox's SMART capabilities and OAuth endpoints, query the well-known configuration endpoint.
Request:
GET /.well-known/smart-configuration
Response (200 OK):
{
"introspection_endpoint": "[your-aidbox]/auth/introspect",
"capabilities": [
"client-confidential-symmetric",
"client-confidential-asymmetric",
"launch-ehr",
"context-banner",
"context-style",
"context-ehr-patient",
"context-ehr-encounter",
"authorize-post",
"client-public",
"permission-patient",
"permission-user",
"launch-standalone",
"sso-openid-connect",
"context-standalone-patient",
"permission-offline",
"permission-v1",
"permission-v2"
],
"grant_types_supported": [
"authorization_code",
"urn:ietf:params:oauth:grant-type:token-exchange",
"implicit",
"password",
"client_credentials"
],
"userinfo_endpoint": "[your-aidbox]/auth/userinfo",
"token_endpoint_auth_methods_supported": [
"client_secret_post",
"client_secret_basic",
"private_key_jwt"
],
"claims_supported": [
"sub",
"aud",
"email",
"exp",
"iat",
"iss",
"locale",
"family_name",
"given_name",
"name",
"picture"
],
"subject_types_supported": [
"public"
],
"authorization_endpoint": "[your-aidbox]/auth/authorize",
"scopes_supported": [
"openid",
"profile",
"email",
"groups",
"launch",
"launch/patient",
"patient/*.cruds",
"system/*.cruds",
"user/*.cruds",
"fhirUser",
"offline_access",
"online_access"
],
"issuer": "[your-aidbox]",
"code_challenge_methods_supported": [
"S256"
],
"response_types_supported": [
"code",
"token",
"token id_token",
"code id_token"
],
"token_endpoint_auth_signing_alg_values_supported": [
"RS384"
],
"token_endpoint": "[your-aidbox]/auth/token",
"id_token_signing_alg_values_supported": [
"RS256"
],
"jwks_uri": "[your-aidbox]/.well-known/jwks.json"
}
3. Obtain Authorization Code
The app initiates the OAuth 2.0 authorization flow by redirecting the user to Aidbox's authorization endpoint.
Authorization Parameters
| Parameter | Required | Description |
|---|---|---|
response_type | Required | Fixed value: code |
client_id | Required | The client's identifier from registration |
redirect_uri | Required | Must match a pre-registered redirect URI |
scope | Required | Space-separated list of scopes (see table below) |
state | Required | Opaque value for CSRF protection (minimum 122 bits entropy) |
aud | Required | FHIR base URL: https://[your-aidbox]/fhir |
launch | Conditional | Launch token (EHR launch only) |
code_challenge | Conditional | PKCE code challenge (public clients) |
code_challenge_method | Conditional | Fixed value: S256 (public clients) |
Scopes Supported
| Scope | Grants |
|---|---|
patient/*.read | Permission to read any resource for the current patient |
patient/*.rs | Permission to read and search any resource for the current patient (SMART v2) |
user/*.read | Permission to read any resource the current user can access |
user/*.rs | Permission to read and search any resource the current user can access (SMART v2) |
system/*.read | Permission to read any resource (backend services) |
system/*.rs | Permission to read and search any resource (backend services, SMART v2) |
openid | Permission to retrieve information about the logged-in user |
fhirUser | Request user's FHIR identifier in token |
launch | Permission to obtain EHR launch context |
launch/patient | Request patient selection at launch time (standalone apps) |
offline_access | Request refresh token for offline access |
Specific Resource Scopes: You may request granular scopes like patient/Patient.read, patient/Observation.rs, user/MedicationRequest.rs, etc.
Example Authorization Request (Standalone Patient App)
Request:
GET /auth/authorize?
response_type=code&
client_id=my-smart-app&
redirect_uri=https%3A%2F%2Fmyapp.example.com%2Fcallback&
scope=launch%2Fpatient+openid+fhirUser+offline_access+patient%2FPatient.rs+patient%2FObservation.rs+patient%2FCondition.rs&
state=random-state-12345&
code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM&
code_challenge_method=S256&
aud=https%3A%2F%2F[your-aidbox]%2Ffhir
Host: [your-aidbox]
Authorization Response
After user authentication and consent, Aidbox redirects to the app's redirect_uri with:
Response (302 Redirect):
https://myapp.example.com/callback?
code=AUTH_CODE_12345&
state=random-state-12345
Parameters:
code: Authorization code to exchange for an access tokenstate: Exact value from the request (must match)
4. Obtain Access Token
After receiving the authorization code, the app exchanges it for an access token.
Token Request Parameters
| Parameter | Required | Description |
|---|---|---|
grant_type | Required | authorization_code (for initial token) or client_credentials (backend services) |
code | Required | Authorization code from previous step |
redirect_uri | Required | Same redirect URI used in authorization request |
client_id | Conditional | Required for public clients; omit for confidential clients using HTTP Basic auth |
client_secret | Conditional | Required for confidential symmetric clients (if not using HTTP Basic) |
code_verifier | Conditional | Required for public clients (PKCE verifier) |
client_assertion_type | Conditional | urn:ietf:params:oauth:client-assertion-type:jwt-bearer (asymmetric auth) |
client_assertion | Conditional | Signed JWT (asymmetric auth) |
Example Token Request (Public Client with PKCE)
Request:
POST /auth/token
Host: [your-aidbox]
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code&
code=AUTH_CODE_12345&
redirect_uri=https%3A%2F%2Fmyapp.example.com%2Fcallback&
client_id=my-smart-app&
code_verifier=dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk
Example Token Request (Confidential Client with Secret)
Request:
POST /auth/token
Host: [your-aidbox]
Content-Type: application/x-www-form-urlencoded
Authorization: Basic base64(client_id:client_secret)
grant_type=authorization_code&
code=AUTH_CODE_12345&
redirect_uri=https%3A%2F%2Fbackend.example.com%2Fcallback
Token Response
Response (200 OK):
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "launch/patient openid fhirUser offline_access patient/Patient.rs patient/Observation.rs patient/Condition.rs",
"refresh_token": "refresh-token-value",
"id_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"patient": "patient-id-123"
}
Response Parameters:
| Parameter | Required | Description |
|---|---|---|
access_token | Required | Bearer token for API access |
token_type | Required | Fixed value: Bearer |
expires_in | Recommended | Token lifetime in seconds |
scope | Required | Granted scopes (may differ from requested) |
refresh_token | Optional | Token for obtaining new access token |
id_token | Optional | OpenID Connect identity token |
patient | Optional | Patient ID for patient-scoped access |
5. Access FHIR API
With a valid access token, the app can access FHIR resources by including the token in the Authorization header.
Request Format:
GET /fhir/{ResourceType}?{searchParams}
Host: [your-aidbox]
Authorization: Bearer {access_token}
Accept: application/fhir+json
Supported HTTP Methods: GET (read and search operations)
Supported Resources: All US Core profiles and USCDI v3 data elements.
6. Refresh Access Token
When an access token expires, use the refresh token to obtain a new access token without user interaction.
Request:
POST /auth/token
Host: [your-aidbox]
Content-Type: application/x-www-form-urlencoded
grant_type=refresh_token&
refresh_token=refresh-token-value&
client_id=my-smart-app&
scope=patient/Patient.rs+patient/Observation.rs
Response (200 OK):
{
"access_token": "new-access-token",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "patient/Patient.rs patient/Observation.rs",
"refresh_token": "new-refresh-token"
}
Access FHIR Resources
Aidbox supports the following US Core profiles for single-patient data access:
| FHIR Resource | US Core Profile |
|---|---|
| Patient | US Core Patient Profile |
| AllergyIntolerance | US Core AllergyIntolerance Profile |
| CarePlan | US Core CarePlan Profile |
| CareTeam | US Core CareTeam Profile |
| Condition | US Core Condition Profile (Problems, Health Concerns) |
| Device | US Core Implantable Device Profile |
| DiagnosticReport | US Core DiagnosticReport Profile (Lab, Notes) |
| DocumentReference | US Core DocumentReference Profile |
| Encounter | US Core Encounter Profile |
| Goal | US Core Goal Profile |
| Immunization | US Core Immunization Profile |
| Location | US Core Location Profile |
| Medication | US Core Medication Profile |
| MedicationDispense | US Core MedicationDispense Profile |
| MedicationRequest | US Core MedicationRequest Profile |
| Observation | US Core Observation Profiles (Lab, Vital Signs, Smoking Status, etc.) |
| Organization | US Core Organization Profile |
| Practitioner | US Core Practitioner Profile |
| PractitionerRole | US Core PractitionerRole Profile |
| Procedure | US Core Procedure Profile |
| Provenance | US Core Provenance Profile |
| QuestionnaireResponse | US Core QuestionnaireResponse Profile |
| RelatedPerson | US Core RelatedPerson Profile |
| Coverage | US Core Coverage Profile (USCDI v3) |
| ServiceRequest | US Core ServiceRequest Profile (USCDI v3) |
| Specimen | US Core Specimen Profile |
Example: Search Patient by ID
Request:
GET /fhir/Patient?_id=patient-123
Host: [your-aidbox]
Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...
Accept: application/fhir+json
Response (200 OK):
{
"resourceType": "Bundle",
"type": "searchset",
"total": 1,
"link": [
{
"relation": "self",
"url": "https://[your-aidbox]/fhir/Patient?_id=patient-123"
}
],
"entry": [
{
"fullUrl": "https://[your-aidbox]/fhir/Patient/patient-123",
"resource": {
"resourceType": "Patient",
"id": "patient-123",
"meta": {
"profile": [
"http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient"
]
},
"identifier": [
{
"system": "http://hospital.example.org/patients",
"value": "12345"
}
],
"name": [
{
"use": "official",
"family": "Smith",
"given": ["John"]
}
],
"gender": "male",
"birthDate": "1980-01-01"
}
}
]
}
Example: Search Observations for Patient
Request:
GET /fhir/Observation?patient=patient-123&category=laboratory
Host: [your-aidbox]
Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...
Accept: application/fhir+json
Response (200 OK):
{
"resourceType": "Bundle",
"type": "searchset",
"total": 50,
"link": [
{
"relation": "self",
"url": "https://[your-aidbox]/fhir/Observation?patient=patient-123&category=laboratory"
}
],
"entry": [
{
"fullUrl": "https://[your-aidbox]/fhir/Observation/obs-1",
"resource": {
"resourceType": "Observation",
"id": "obs-1",
"meta": {
"profile": [
"http://hl7.org/fhir/us/core/StructureDefinition/us-core-observation-lab"
]
},
"status": "final",
"category": [
{
"coding": [
{
"system": "http://terminology.hl7.org/CodeSystem/observation-category",
"code": "laboratory"
}
]
}
],
"code": {
"coding": [
{
"system": "http://loinc.org",
"code": "2093-3",
"display": "Cholesterol [Mass/volume] in Serum or Plasma"
}
]
},
"subject": {
"reference": "Patient/patient-123"
},
"effectiveDateTime": "2025-01-10T10:00:00Z",
"valueQuantity": {
"value": 180,
"unit": "mg/dL",
"system": "http://unitsofmeasure.org",
"code": "mg/dL"
}
}
}
]
}
Bulk Data Export
Aidbox implements the FHIR Bulk Data Access IG for population-level data export.
Bulk Export Endpoints
| Endpoint | Description |
|---|---|
GET /fhir/Patient/$export | Export all patients |
GET /fhir/Group/{id}/$export | Export patients in a specific group |
GET /fhir/$export | Export all resources (system-level) |
Bulk Export Parameters
| Parameter | Description |
|---|---|
_type | Comma-separated list of resource types (e.g., Patient,Observation,Condition) |
_since | Incremental export (resources modified after this timestamp) |
_outputFormat | Output format (default: application/fhir+ndjson) |
patient | Comma-separated list of patient IDs. Exports data only for the listed patients. Available only for patient-level export. |
Bulk Export Workflow
Step 1: Initiate Export (Kickoff)
Request:
GET /fhir/Patient/$export?_type=Patient,Observation,Condition
Host: [your-aidbox]
Authorization: Bearer {access_token}
Accept: application/fhir+json
Prefer: respond-async
Response (202 Accepted):
HTTP/1.1 202 Accepted
Content-Location: https://[your-aidbox]/fhir/$export-status/<id>
Step 2: Poll Export Status
Request:
GET /fhir/$export-status/{job-id}
Host: [your-aidbox]
Authorization: Bearer {access_token}
Response (In Progress) (202 Accepted):
{
"transactionTime": "2025-01-16T10:00:00Z",
"request": "/fhir/Patient/$export?_type=Patient,Observation,Condition",
"requiresAccessToken": false
}
Step 3: Download Export Files
Response (Complete) (200 OK):
{
"transactionTime": "2025-01-16T10:00:00Z",
"request": "/fhir/Patient/$export?_type=Patient,Observation,Condition",
"requiresAccessToken": false,
"output": [
{
"type": "Patient",
"url": "https://storage.example.com/export-1/Patient.ndjson",
"count": 1000
},
{
"type": "Observation",
"url": "https://storage.example.com/export-1/Observation.ndjson",
"count": 50000
},
{
"type": "Condition",
"url": "https://storage.example.com/export-1/Condition.ndjson",
"count": 5000
}
],
"error": []
}
Step 4: Download NDJSON Files
Each file contains one FHIR resource per line in JSON format:
{"resourceType":"Patient","id":"pt-1","name":[{"family":"Smith","given":["Alice"]}]}
{"resourceType":"Patient","id":"pt-2","name":[{"family":"Jones","given":["Bob"]}]}