(g)(10): Standardized API for Patient and Population Services
ONC (g)(10) certification implementation with SMART App Launch, US Core, Bulk FHIR APIs, and Inferno test suite compliance.
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:
- 1.Standalone App: Patient-facing application launched outside the EHR with patient context selection at launch time.
- 2.EHR-Embedded App: Application launched from within the EHR with pre-established patient and encounter context.
- 3.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:
- 1.Public Client (no client secret): Patient-facing apps using PKCE for authorization code flow
- Reference: SMART App Launch - Public Client
- 2.Confidential Client - Symmetric (client secret): Server-side apps using
client_secret_basic- Reference: SMART App Launch - Symmetric Auth
- 3.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:
- 1.Sign Up: Open the Developer Portal and click Sign Up
- 2.Complete Registration: Fill out the developer registration form
- 3.Confirm Email: Check email and click Confirm Email Address
- 4.Set Password: Create password on confirmation page
- 5.Sign In: Access your developer dashboard
Register a SMART App:
- 1.From dashboard, click Register New
- 2.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)
- 3.Click Create App
- 4.Copy the Client ID from your app's draft page
- 5.Test Launch: Click Test Launch to test with sample patient data (
Patient/test-pt-1) - 6.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"]}]}
Last updated: