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:

StandardVersionRegulation Reference
FHIRR4 (4.0.1)§170.215(a)(1)
US Core Implementation Guide6.1.0 (USCDI v3)§170.215(b)(1)
SMART App Launch Framework2.0.0§170.215(c)
Bulk Data Access IG2.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. 1.
    Standalone App: Patient-facing application launched outside the EHR with patient context selection at launch time.
  2. 2.
    EHR-Embedded App: Application launched from within the EHR with pre-established patient and encounter context.
  3. 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. 1.
    Public Client (no client secret): Patient-facing apps using PKCE for authorization code flow
  2. 2.
    Confidential Client - Symmetric (client secret): Server-side apps using client_secret_basic
  3. 3.
    Confidential Client - Asymmetric (private key JWT): Backend services using signed JWT assertions

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. 1.
    Sign Up: Open the Developer Portal and click Sign Up
  2. 2.
    Complete Registration: Fill out the developer registration form
  3. 3.
    Confirm Email: Check email and click Confirm Email Address
  4. 4.
    Set Password: Create password on confirmation page
  5. 5.
    Sign In: Access your developer dashboard

Register a SMART App:

  1. 1.
    From dashboard, click Register New
  2. 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. 3.
    Click Create App
  4. 4.
    Copy the Client ID from your app's draft page
  5. 5.
    Test Launch: Click Test Launch to test with sample patient data (Patient/test-pt-1)
  6. 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

ParameterRequiredDescription
response_typeRequiredFixed value: code
client_idRequiredThe client's identifier from registration
redirect_uriRequiredMust match a pre-registered redirect URI
scopeRequiredSpace-separated list of scopes (see table below)
stateRequiredOpaque value for CSRF protection (minimum 122 bits entropy)
audRequiredFHIR base URL: https://[your-aidbox]/fhir
launchConditionalLaunch token (EHR launch only)
code_challengeConditionalPKCE code challenge (public clients)
code_challenge_methodConditionalFixed value: S256 (public clients)

Scopes Supported

ScopeGrants
patient/*.readPermission to read any resource for the current patient
patient/*.rsPermission to read and search any resource for the current patient (SMART v2)
user/*.readPermission to read any resource the current user can access
user/*.rsPermission to read and search any resource the current user can access (SMART v2)
system/*.readPermission to read any resource (backend services)
system/*.rsPermission to read and search any resource (backend services, SMART v2)
openidPermission to retrieve information about the logged-in user
fhirUserRequest user's FHIR identifier in token
launchPermission to obtain EHR launch context
launch/patientRequest patient selection at launch time (standalone apps)
offline_accessRequest 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 token
  • state: 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

ParameterRequiredDescription
grant_typeRequiredauthorization_code (for initial token) or client_credentials (backend services)
codeRequiredAuthorization code from previous step
redirect_uriRequiredSame redirect URI used in authorization request
client_idConditionalRequired for public clients; omit for confidential clients using HTTP Basic auth
client_secretConditionalRequired for confidential symmetric clients (if not using HTTP Basic)
code_verifierConditionalRequired for public clients (PKCE verifier)
client_assertion_typeConditionalurn:ietf:params:oauth:client-assertion-type:jwt-bearer (asymmetric auth)
client_assertionConditionalSigned 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:

ParameterRequiredDescription
access_tokenRequiredBearer token for API access
token_typeRequiredFixed value: Bearer
expires_inRecommendedToken lifetime in seconds
scopeRequiredGranted scopes (may differ from requested)
refresh_tokenOptionalToken for obtaining new access token
id_tokenOptionalOpenID Connect identity token
patientOptionalPatient 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 ResourceUS Core Profile
PatientUS Core Patient Profile
AllergyIntoleranceUS Core AllergyIntolerance Profile
CarePlanUS Core CarePlan Profile
CareTeamUS Core CareTeam Profile
ConditionUS Core Condition Profile (Problems, Health Concerns)
DeviceUS Core Implantable Device Profile
DiagnosticReportUS Core DiagnosticReport Profile (Lab, Notes)
DocumentReferenceUS Core DocumentReference Profile
EncounterUS Core Encounter Profile
GoalUS Core Goal Profile
ImmunizationUS Core Immunization Profile
LocationUS Core Location Profile
MedicationUS Core Medication Profile
MedicationDispenseUS Core MedicationDispense Profile
MedicationRequestUS Core MedicationRequest Profile
ObservationUS Core Observation Profiles (Lab, Vital Signs, Smoking Status, etc.)
OrganizationUS Core Organization Profile
PractitionerUS Core Practitioner Profile
PractitionerRoleUS Core PractitionerRole Profile
ProcedureUS Core Procedure Profile
ProvenanceUS Core Provenance Profile
QuestionnaireResponseUS Core QuestionnaireResponse Profile
RelatedPersonUS Core RelatedPerson Profile
CoverageUS Core Coverage Profile (USCDI v3)
ServiceRequestUS Core ServiceRequest Profile (USCDI v3)
SpecimenUS 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

EndpointDescription
GET /fhir/Patient/$exportExport all patients
GET /fhir/Group/{id}/$exportExport patients in a specific group
GET /fhir/$exportExport all resources (system-level)

Bulk Export Parameters

ParameterDescription
_typeComma-separated list of resource types (e.g., Patient,Observation,Condition)
_sinceIncremental export (resources modified after this timestamp)
_outputFormatOutput format (default: application/fhir+ndjson)
patientComma-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: