# Quickstart Go from nothing to a working SMART on FHIR app against your synthetic Ava sandbox: register a public client, complete an EHR launch with authorization code + PKCE S256, and read your first Patient resource. No client secret is involved anywhere in this flow. ## Your sandbox connection Values in `{braces}` are placeholders — sign in to the portal to see your own. | Field | Value | | --- | --- | | Client ID | `{client_id}` | | Issuer | `https://sandbox.avaemr.ca` | | Authorization endpoint | `https://sandbox.avaemr.ca/oauth/authorize` | | Token endpoint | `https://sandbox.avaemr.ca/oauth/token` | | FHIR base URL | `https://sandbox.avaemr.ca/fhir/r4/{partition_id}` | ## Before you start - An Ava Ecosystem account and an organization ([sign up](/users/sign_up) takes a minute). - Ruby 3.x locally if you want to run the sample app (`ruby -v`). ## 1. Activate your sandbox From your organization dashboard, press **Activate sandbox**. Ava provisions a synthetic clinic (dataset `ontario_primary_care_v1`) and a sandbox EMR login for you. Wait for the sandbox card to show **Active** — it also gives you the EMR login email and sign-in link you'll use in step 5. ## 2. Register your app Create a developer app, then open its **sandbox configuration** and set exactly: | Field | Value | | --- | --- | | Launch URI | `http://127.0.0.1:4567/launch` | | Redirect URI | `http://127.0.0.1:4567/callback` | Sandbox HTTP URIs must use a loopback **IP literal** — `localhost` is rejected, `127.0.0.1` is accepted. (Deployed apps use HTTPS URIs.) ## 3. Choose scopes In your app's permission schema, select `openid`, `fhirUser`, and `patient/Patient.r`, with a short rationale for each. The core `launch` scope is always included automatically. You can expand scopes later — users are asked to re-consent when you add access. ## 4. Deploy to your sandbox Press **Deploy to my sandbox** on the app page. When the sandbox registration card shows your public client ID, the registration is live: sign in to see yours. ## 5. Run the sample app and launch from the EMR The repository ships a single-file SMART client that proves the whole flow — EHR launch → authorization code + PKCE S256 → token exchange → Patient read: ```bash CLIENT_ID= ruby app.rb ``` Then sign in to the sandbox EMR with the login from step 1, open any synthetic patient, and press your app's launch button. Approve the SMART authorization screen and the test app shows each completed step: token exchange, OpenID identity, and the launched patient fetched over FHIR. The sandbox supports the **EHR launch only** — browsing to the app directly shows a waiting page until a launch arrives. ## 6. Call the API directly Every FHIR request is a plain HTTPS call with the access token from the launch: ```bash curl "https://sandbox.avaemr.ca/fhir/r4/{partition_id}/Patient/{id}" \ -H "Authorization: Bearer $SMART_ACCESS_TOKEN" \ -H "Accept: application/fhir+json" ``` Responses are FHIR R4 JSON (`application/fhir+json`); errors are FHIR `OperationOutcome` resources. The discovery documents at `https://sandbox.avaemr.ca/fhir/r4/{partition_id}/metadata` and `https://sandbox.avaemr.ca/.well-known/smart-configuration` are the authoritative, machine-readable description of the API surface. ## Next steps - [App launch & PKCE in depth](/docs/guides/app-launch-and-pkce) — every request in the flow. - [Integration brief](/docs/integration-brief) — the condensed one-page contract, ideal context for coding agents. --- # Integration brief Everything an integrator (human or agent) needs to hold in context to build against the Ava EMR FHIR API. Values in `{braces}` are per-registration; find yours in the [portal](/) or the [quickstart](/docs/quickstart). ## What this API is - **FHIR R4 (4.0.1), JSON only.** Requests and responses use `application/fhir+json`; errors are FHIR `OperationOutcome` resources. - **SMART App Launch 2.2, EHR launch only.** Public clients, authorization code + **PKCE S256**, OpenID Connect. There are **no client secrets** and **no refresh tokens**; standalone launch and Backend Services registration are not available through this portal today. - **Canadian profiles.** Most resources advertise CA Core+ profiles (`ca.infoway.io.core`); responses may include profile extensions — ignore elements you don't recognize. ## URLs | Thing | Shape | | --- | --- | | FHIR base | `{issuer}/fhir/r4/{partition_id}` — every clinic partition has its own base URL | | CapabilityStatement | `{fhir_base_url}/metadata` | | SMART discovery | `{issuer}/.well-known/smart-configuration` | | Sandbox issuer | `https://sandbox.avaemr.ca` (your portal shows your real one) | The two discovery documents are authoritative: anything advertised there is implemented; anything absent must not be relied on. ## Authorization in five lines 1. EMR opens `{launch_uri}?iss=…&launch=…`. 2. Fetch `{iss}/.well-known/smart-configuration`. 3. Redirect to `authorization_endpoint` with `response_type=code`, `client_id`, `redirect_uri`, `scope`, `state`, `aud={iss}`, `launch`, `code_challenge` (+`=S256`). 4. Exchange the returned `code` + `code_verifier` at `token_endpoint` (no secret). 5. Call FHIR with `Authorization: Bearer …`; the token response's `patient` field is the launched patient's id. Full walkthrough: [App launch & PKCE](/docs/guides/app-launch-and-pkce). ## Scope grammar ``` /. e.g. patient/Patient.rs ``` - Contexts: `patient/` (confined to the launched patient) and `user/` (whatever the signed-in clinician can see). - Interactions: SMART v2 granular letters — `c` create, `r` read, `s` search (so `.rs` = read + search, `.c` = create-only). - Protocol scopes: `launch` (always required), `openid`, `fhirUser` (requires `openid`), `launch/servicerequest`. - Scopes are selected per app in a versioned **permission schema**, each with a written rationale shown to users at consent. Requesting a scope outside your deployed schema fails with `invalid_scope`. ## Sharp edges (read before coding) - **The write surface is narrow and create-only**: `DocumentReference` and `Media` support `create` and nothing else. No resource supports update, patch, or delete. - **Observation has no launch scopes** — `patient/Observation.*` and `user/Observation.*` do not exist; do not request them. - **ServiceRequest is read-only without search** (`user/ServiceRequest.r`). - **Sandbox HTTP URIs must use a loopback IP literal** — `127.0.0.1` works, `localhost` is rejected. Deployed apps use HTTPS. - **EHR launch only** — your app cannot start a session on its own; it is launched from a patient chart in the EMR. - The granted `scope` in the token response may be narrower than requested. ## Search & pagination - Search results are `searchset` Bundles. - Universal parameters: `_id`, `_lastUpdated`, `_count`, and opaque `_page_token` cursor pagination — follow the Bundle's `next` link rather than constructing page URLs. - Per-resource search parameters are advertised in the CapabilityStatement. ## Errors Non-2xx FHIR responses carry an `OperationOutcome` with machine-readable `issue[].code` and human diagnostics. OAuth endpoints return standard OAuth error codes (`invalid_scope`, `invalid_grant`, …). ## Sandbox facts - Synthetic dataset `ontario_primary_care_v1`: a generated Ontario primary-care clinic — realistic, deterministic, and containing no real patient data. - Your sandbox is private to your organization and comes with its own EMR login for launching apps from charts. - Every API change reaches the sandbox at least one release cycle before production. ## Versioning promises - The FHIR major version is in the URL path (`/fhir/r4/…`); meanings of existing paths never change in place. - Additions (resources, elements, scopes) ship continuously — write clients defensively. - Breaking changes get `Deprecation`/`Sunset` headers and a minimum 6-month window (12 months when response shapes change), announced to registered app owners. --- # App launch & PKCE Ava apps are **public** SMART App Launch 2.2 clients using the **EHR launch**: the flow always starts inside Ava EMR, with a clinician (and patient chart) already in context. Your app never holds a client secret; authorization-code interception is mitigated by PKCE S256 instead. Before hand-rolling this flow, consider adapting the runnable single-file client in `examples/smart-test-app/` — it performs every step below and prints each intermediate result. This guide explains what it is doing. ## The flow at a glance 1. Ava EMR opens your **launch URI** with `iss` and `launch` parameters. 2. You fetch the SMART discovery document from the issuer. 3. You redirect the browser to the **authorization endpoint** with a PKCE challenge. 4. The user approves your requested scopes. 5. The EMR redirects back to your **redirect URI** with an authorization code. 6. You exchange the code (plus your PKCE verifier) at the **token endpoint**. 7. You call the FHIR API with the access token, scoped to the launched patient context. ## 1. The EMR opens your launch URI ``` GET {your_launch_uri}?iss=https://sandbox.avaemr.ca&launch= ``` - `iss` — the FHIR authorization issuer. **Verify it is an issuer you expect** before proceeding; do not blindly follow `iss` values. - `launch` — an opaque, single-use token binding this authorization to the clinician's current EMR session and open patient chart. Pass it through unchanged in step 3. Browsing to your app directly (without a launch) is not supported — the Ava sandbox implements the EHR launch only, so show a friendly waiting page. ## 2. Discover the authorization endpoints ```bash curl "{iss}/.well-known/smart-configuration" -H "Accept: application/json" ``` The response includes `authorization_endpoint`, `token_endpoint`, `jwks_uri`, and the supported capabilities and PKCE methods. Always resolve endpoints from discovery rather than hardcoding them. ## 3. Redirect to the authorization endpoint Generate a PKCE pair first: - `code_verifier` — a high-entropy random string (43–128 characters), stored in the user's session. - `code_challenge` — `BASE64URL(SHA256(code_verifier))`, sent now. Then redirect the browser: ``` {authorization_endpoint}? response_type=code &client_id={client_id} &redirect_uri={your_redirect_uri} &scope=launch openid fhirUser patient/Patient.r &state={random anti-CSRF value} &aud={iss} &launch={launch token from step 1} &code_challenge={code_challenge} &code_challenge_method=S256 ``` - `scope` must be a subset of the scopes in your app's deployed permission schema; `launch` is always required. - `aud` must be the FHIR issuer from step 1 — the EMR rejects mismatches. - `redirect_uri` must exactly match one of the URIs registered for your app. In the sandbox, HTTP redirect URIs must use a loopback IP literal (`127.0.0.1`, not `localhost`). The user sees Ava's authorization screen listing each requested scope with the rationale you provided in your permission schema, and approves or declines. ## 4–5. Receive the authorization code ``` GET {your_redirect_uri}?code=&state= ``` Reject the response unless `state` matches the value you sent. The code is short-lived and single-use — exchange it immediately. ## 6. Exchange the code for tokens ```bash curl -X POST "{token_endpoint}" \ -H "Content-Type: application/x-www-form-urlencoded" \ -d grant_type=authorization_code \ -d "code=" \ -d "redirect_uri={your_redirect_uri}" \ -d "client_id={client_id}" \ -d "code_verifier=" ``` No `Authorization` header and no client secret — the `code_verifier` proves you are the client that started the flow. A successful response looks like: ```json { "access_token": "", "token_type": "Bearer", "expires_in": 3600, "scope": "launch openid fhirUser patient/Patient.r", "id_token": "", "patient": "" } ``` - `scope` is the set actually granted — it may be narrower than requested. Respect it. - `patient` carries the launch context: the id of the patient chart open in the EMR. Your `patient/` scopes are confined to this patient. - `id_token` (present with `openid`) identifies the signed-in clinician; with `fhirUser` it includes a `fhirUser` claim pointing at their FHIR resource. Validate it against the issuer's `jwks_uri`. ## 7. Call the FHIR API ```bash curl "{fhir_base_url}/Patient/{patient id from the token response}" \ -H "Authorization: Bearer $SMART_ACCESS_TOKEN" \ -H "Accept: application/fhir+json" ``` Access tokens are short-lived. When one expires, complete a new EHR launch — public Ava clients are not issued refresh tokens. ## Common failures | Symptom | Likely cause | | --- | --- | | `invalid_scope` at authorization | A requested scope is not in your deployed permission schema, or is not implemented (for example, `patient/Observation.*` does not exist). | | Redirect URI error at authorization | The `redirect_uri` does not exactly match a registered URI — check scheme, host, port, path, and the loopback-IP rule. | | `invalid_grant` at token exchange | The code expired, was already used, or the `code_verifier` does not match the challenge. Restart the launch. | | `401` from FHIR calls | Missing/expired token, or the token's granted scopes do not cover the resource and interaction. | | Waiting page never advances | You opened the app directly — start from a patient chart inside the EMR instead. | ## Choose launcher locations In an app's launch configuration, select **Chart Launch**, **Consult Launch**, or both. Existing apps default to Chart Launch. Save the configuration, then update the sandbox deployment to send the versioned settings through the outbox. Production location changes are part of a new security review snapshot. Chart Launch appears in the chart's bottom toolbar SMART App Launcher. Consult Launch appears in the Consults header and inside an existing consult. Launching from the header creates an Incomplete consult and tracking task; launching inside a consult reuses it. A saved SMART app link supports later launches for that consult. The app opens in a new tab; refresh the chart on return to see a newly created consult. The consult is the FHIR **ServiceRequest**, while its request and response PDFs are **DocumentReference** resources linked through `context.related`. The saved SMART launcher link is local navigation metadata. Neither its ID nor a document ID should be used as the ServiceRequest ID. Existing FHIR logical IDs and consult UUID identifiers are unchanged. Use the cached permissions catalog to request `launch/servicerequest` and `user/ServiceRequest.r` for referral context and reads. Document uploads separately require `user/DocumentReference.c`. The token response supplies the referral as `fhirContext: [{ "reference": "ServiceRequest/{id}" }]`; the browser launch URL continues to contain only `iss` and the opaque `launch` handle. Selecting a launcher location grants no additional FHIR access. --- # Scopes & permissions Every capability your app has is expressed as a SMART v2 scope, and every scope your app can request comes from its **permission schema** — a versioned, reviewable list you manage in the portal. ## The grammar ``` /. ``` - **`patient/`** — confined to the patient chart your app was launched from. `patient/Condition.rs` reads and searches only the launched patient's conditions. - **`user/`** — follows the signed-in clinician's own access. `user/Appointment.rs` sees the clinician's schedule across patients. - **Interaction letters** are SMART v2 granular: `c` create, `r` read, `s` search. `.rs` means read + search; `.c` means create-only. Protocol scopes sit alongside resource scopes: - `launch` — required for every app; the portal adds it automatically. - `openid` — requests an OpenID Connect `id_token` identifying the user. - `fhirUser` — adds a claim pointing at the user's FHIR resource. Requires `openid`. - `launch/servicerequest` — asks for a ServiceRequest id in launch context when the launch originates from an order. Wildcards (`patient/*.read`), free-form scopes, and anything not in the capability catalog are rejected — at schema save time in the portal, and again at authorization time by the EMR. ## Permission schemas A schema is an immutable, numbered version of your app's requested scopes. Each scope carries a **written rationale**; clinicians see those rationales on the authorization screen, so write them for the person deciding whether to trust your app ("Show the launched patient's active medications", not "med access"). Editing permissions creates a **new draft version**; deployed versions never change underneath a clinic. When a new version only narrows access, existing consents stand. When it **broadens** access — any new `(context, resource, interaction)` atom — users are asked to re-consent on their next launch, so batch scope additions deliberately rather than dripping them out. ## Requesting less at runtime Your authorization request may ask for any **subset** of the deployed schema. The granted `scope` in the token response is authoritative and may be narrower than requested — always read it back rather than assuming. ## What's requestable The selectable catalog is exactly what the sandbox EMR advertises — see the [API reference](/docs/reference) for the per-resource scope tables. The sharp edges people hit: - **Observation has no launch scopes at all** (Backend Services only). - **ServiceRequest** is `user/ServiceRequest.r` — read-only, no search. - **DocumentReference and Media** are create-only (`.c`). - `system/` (Backend Services) scopes exist in the EMR but are not yet registrable through this portal. --- # Sandbox & test data Every organization gets one private **synthetic sandbox**: a real Ava EMR clinic partition filled with generated data, where your app can launch, read, and write without ever touching real patient information. ## Activating your sandbox Press **Activate sandbox** on your organization dashboard. Provisioning takes a moment; the dashboard card updates live and shows, once active: - an **EMR login** (your email) and sign-in link for the sandbox EMR, - your sandbox **FHIR base URL**, - the dataset identifier and generation. The sandbox administrator login is how you drive launches: sign in to the EMR, open a synthetic patient's chart, and press your app's launch button. ## The dataset Sandboxes run the `ontario_primary_care_v1` dataset — a generated Ontario primary-care clinic with patients, practitioners, appointments, encounters, conditions, medications, and allergies that hang together the way a real chart does. - **Entirely synthetic.** No real patients, providers, or health numbers. - **Consistent within a generation.** The dataset has a generation number and content digest; data doesn't shift underneath you mid-integration. - **Realistic shapes.** Resources carry CA Core+ profile elements, SNOMED CT CA / LOINC codings, and Ontario-flavoured identifiers, so parsing code you write against the sandbox holds up in production. Browse the data as an EMR user (via your sandbox login) or over FHIR with your app's granted scopes — what you see over the API is exactly what your scopes allow, which makes the sandbox the right place to verify your permission schema is sufficient *before* a security review. ## Sandbox vs production | | Sandbox | Production | | --- | --- | --- | | Data | Synthetic (`ontario_primary_care_v1`) | Real clinic data | | Registration | Instant, self-serve | Requires an approved security review | | Client | Distinct public client ID | Distinct public client ID | | HTTP URIs | Allowed for loopback IP literals (`127.0.0.1`) | HTTPS only | | API surface | Receives changes at least one release cycle early | Stable per the [versioning policy](/docs/versioning-policy) | The sandbox and production registrations are **separate clients** — deploying to the sandbox never changes anything in production. ## Troubleshooting - **Sandbox card stuck on provisioning** — provisioning is asynchronous; if it lands in a failed state the card says so with a retryable reason. - **Empty search results** — check the granted `scope` on your token first; a missing scope commonly reads as "no data". Remember Observation has no launch scopes at all. - **Launch button missing in the EMR** — your app deploys to the sandbox when you press **Deploy to my sandbox** on the app page; check the registration card shows an active client ID. --- # Search & pagination Type-level search is a GET against the resource type with query parameters: ```bash curl "{fhir_base_url}/Condition?patient=Patient/synthetic-patient-1&clinical-status=active" \ -H "Authorization: Bearer $SMART_ACCESS_TOKEN" \ -H "Accept: application/fhir+json" ``` Search requires an `s` in your granted scope for that resource (`patient/Condition.rs`), and only searchable resources advertise a search route at all — check the [API reference](/docs/reference) per resource. ## Results are searchset Bundles ```json { "resourceType": "Bundle", "type": "searchset", "link": [ { "relation": "self", "url": "…" }, { "relation": "next", "url": "…&_page_token=eyJvZmZzZXQiOjEwfQ" } ], "entry": [ { "fullUrl": "…", "resource": { "resourceType": "Condition", … }, "search": { "mode": "match" } } ] } ``` An empty `entry` array with a 200 status is a valid result: nothing matched (or your scope's context contains no matching data). ## Universal parameters Every searchable resource supports: | Parameter | Purpose | | --- | --- | | `_id` | Match a specific logical id | | `_lastUpdated` | Filter by modification time (`ge`, `lt`, … prefixes) | | `_count` | Page-size hint | | `_page_token` | Opaque pagination cursor | Resource-specific parameters (like `Patient?family=` or `Condition?clinical-status=`) are listed per resource in the reference and advertised in the CapabilityStatement. ## Pagination is cursor-based Follow the Bundle's `next` link until it disappears: - `_page_token` is **opaque** — never parse, construct, or store one long-term; tokens belong to the query that issued them. - There is no `total` count guarantee and no way to jump to page N; iterate. - `_count` is a hint; the server may return fewer entries per page. ## Date parameters Date-typed parameters accept FHIR prefixes: `ge` (on or after), `le`, `gt`, `lt`, `eq`. Ranges combine two parameters: ``` /Appointment?patient=Patient/{id}&date=ge2026-08-01&date=lt2026-09-01 ``` ## Deliberately not supported `_sort`, `_include`, `_revinclude`, chained parameters, bulk export, and FHIR Subscriptions are not part of the current registry. Don't send them — requests that were already invalid may be rejected more strictly over time. Fetch referenced resources (like an Appointment's Practitioner) with follow-up reads under their own scopes. --- # Writing documents & media Ava's write surface is deliberately narrow: apps contribute **documents** (`DocumentReference`) and **captures** (`Media`) to the chart, create-only. There is no update, patch, or delete anywhere in the API, and no way to read back what you wrote — the clinic's record is the EMR's to manage. | Resource | Scope | Use for | | --- | --- | --- | | DocumentReference | `user/DocumentReference.c` | PDFs, notes, reports — documents | | Media | `user/Media.c` | Photos, video, audio captures | Both are clinician-context (`user/`) scopes: writes happen as the signed-in user, onto the chart of a patient that user can access. ## Creating a document ```bash curl -X POST "{fhir_base_url}/DocumentReference" \ -H "Authorization: Bearer $SMART_ACCESS_TOKEN" \ -H "Content-Type: application/fhir+json" \ -H "Accept: application/fhir+json" \ -d '{ "resourceType": "DocumentReference", "status": "current", "type": { "coding": [{ "system": "http://loinc.org", "code": "34133-9" }] }, "subject": { "reference": "Patient/{id}" }, "content": [{ "attachment": { "contentType": "application/pdf", "data": "", "title": "Visit summary" } }] }' ``` A successful create returns **`201 Created`** with the stored resource in the body and its address in the `Location` header. Persist the returned id in your own system if you need to refer to the write later — you cannot search for it afterwards. Guidelines: - Send content inline as base64 `attachment.data` with an accurate `contentType`; give every attachment a human-meaningful `title` — that's what clinicians see. - Use a specific LOINC document `type` code; it drives where the document files in the chart. - `subject` must reference the patient the document belongs to. In a patient launch, that should be the launched patient. ## Creating media Same pattern against `/Media`, with a single `content` attachment and a `type` of `image`, `video`, or `audio` — see the [Media reference](/docs/reference/media) for a complete example. ## Failure modes - `403` — your granted scopes lack `user/DocumentReference.c` / `user/Media.c`, or the user cannot write to that patient's chart. - `422` with an `OperationOutcome` — the resource failed validation (missing required elements, unknown codes, malformed base64). The outcome's `issue[]` pinpoints the element. - Duplicate submissions create duplicate documents — POST is not idempotent. Guard your own retry logic. --- # Errors & troubleshooting Two error vocabularies apply: **OAuth error codes** during authorization and token exchange, and **FHIR OperationOutcome** resources on API calls. This page catalogs both, worst-first by how often they burn an afternoon. ## Reading an OperationOutcome Every non-2xx FHIR response is `application/fhir+json` with: ```json { "resourceType": "OperationOutcome", "issue": [ { "severity": "error", "code": "forbidden", "diagnostics": "Scope patient/Observation.rs is not granted" } ] } ``` `issue[].code` is the machine-readable key; `diagnostics` is for humans. Handle by `code` and HTTP status, and log `diagnostics` — don't parse it. ## HTTP statuses on FHIR calls | Status | Meaning | Usual fix | | --- | --- | --- | | `401` | Missing, expired, or malformed bearer token | Complete a new EHR launch; tokens are short-lived and there are no refresh tokens | | `403` | Token valid, scopes insufficient (or patient-context violation) | Check the granted `scope` on your token; broaden the schema and re-deploy if needed | | `404` | Unknown id, wrong partition, or a resource/route that isn't advertised | Verify the FHIR base URL and that the interaction exists in the reference | | `422` | Resource failed validation on create | Read `issue[]`; fix the flagged element | | `429` | Rate limited | Back off and retry with jitter | ## Authorization-time failures | Error | Likely cause | Fix | | --- | --- | --- | | `invalid_scope` | Requested scope not in the deployed schema, or not implemented at all (`patient/Observation.*` doesn't exist; wildcards are rejected) | Request a subset of your schema; consult the [reference](/docs/reference) | | redirect URI mismatch | Registered and requested `redirect_uri` differ — scheme, host, port, or path | Match exactly; sandbox HTTP URIs must use `127.0.0.1`, not `localhost` | | `invalid_grant` at token exchange | Code expired, already used, or `code_verifier` doesn't match the challenge | Restart the launch; verify verifier storage across the redirect | | `aud` rejection | The `aud` parameter isn't the issuer that launched you | Pass the `iss` value from the launch as `aud`, verbatim | | Consent screen never appears / launch fails immediately | `launch` token missing, reused, or stale | Pass `launch` through unchanged; launch fresh from a chart | ## The classics **"My search returns nothing but the data exists in the EMR."** Almost always scope context: a `patient/` scope only sees the launched patient, and missing scopes commonly present as empty results rather than errors. Check the token response's granted `scope` first. **"Works with my token from yesterday" — then 401.** Access tokens are short-lived and public clients get no refresh tokens; every session starts with an EHR launch. **"Everything works in the sandbox but the portal won't let me add a scope."** The selectable catalog is the sandbox EMR's advertised surface. If it's not in the [reference](/docs/reference), it isn't requestable — Observation launch scopes being the most-hit example. **"My deployed URI change isn't taking effect."** Configuration changes must be re-deployed to the sandbox; the registration card shows what's actually live. ## When you're stuck The runtime contracts out-rank every doc, this one included: fetch `{fhir_base_url}/metadata` and `{issuer}/.well-known/smart-configuration` and trust what they advertise. If behavior contradicts *them*, contact the Ava integration team through your organization's portal workspace with the request, response, and timestamp. --- # Going to production Sandbox access is self-serve; production access is reviewed. Three things are deliberately independent decisions: 1. **Security review** — approves a specific versioned snapshot of your app's configuration and permissions for production. 2. **Production registration** — provisions your production client after approval. 3. **Marketplace listing** — an optional, separately reviewed public listing. Private and invite-only distribution don't need it. ## Before you submit - Finish your integration against the sandbox — reviews evaluate the exact configuration and permission schema you submit, frozen as an immutable snapshot. - Production URIs must be **HTTPS** — the loopback-HTTP allowance is sandbox-only. - Tighten your permission schema to what you actually use, and write rationales for the reviewer *and* the consenting clinician. Over-broad scope requests are the most common reason for change requests. - Make sure your app's support email and URLs are current; deprecation and security notices go to the registered owner. ## The security review Submit from your app's page in the portal. The submission captures your configuration and permission schema at that version; editing after submission creates new draft versions and does not alter what's under review. Review ends in approval, a request for changes, or rejection — with reasons — and an approved app **stays approved** while a newer version is under review, so iterating is safe. Broadening scopes later means a new schema version and a new review; clinics see a re-consent prompt only after the new version is approved and deployed. ## Production registration After approval you can provision production: a **separate public client** with its own `client_id` and endpoints — your sandbox registration keeps working unchanged. The EMR remains authoritative for clinic-by-clinic enablement and consent; production registration makes your app available, not installed. ## Marketplace listing If you want public distribution, submit your listing content (name, description, imagery, links) for the independent marketplace-content review. Publication and unpublication never touch production access — unpublishing a listing does not suspend running installations. ## After launch - Watch the [changelog](/docs/changelog) — breaking changes carry `Deprecation`/`Sunset` headers and a 6-to-12-month window per the [versioning policy](/docs/versioning-policy). - Every API change reaches your sandbox at least one release cycle before production: keep your sandbox integration alive as your early-warning system. --- # Clinic partitions Ava EMR is multi-tenant at the **clinic** level, and the FHIR API makes that explicit in the URL: ``` {issuer}/fhir/r4/{partition_id} ``` Every clinic — including your synthetic sandbox — is its own partition with its own FHIR base URL. There is no cross-partition endpoint: a token is issued for one partition and resource ids are only meaningful within it. ## What this means for your code - **Never hardcode a base URL.** Take `iss` from the launch parameters (and the token response's context) each time — an app installed at ten clinics sees ten different base URLs. - **Scope your caches and stored ids by partition.** `Patient/abc` in one clinic is unrelated to `Patient/abc` in another. - **Discovery is per-issuer.** Fetch `.well-known/smart-configuration` and `metadata` from the issuer that launched you, not from a cached copy for a different clinic. Your sandbox behaves exactly like a production clinic in this respect, so an integration that works there generalizes: the only thing that changes at each installation is the pair of issuer and partition id the launch hands you. --- # Environments There are two environments, and they share almost nothing at runtime: | | Sandbox | Production | | --- | --- | --- | | Purpose | Build and test | Real clinics | | Data | Synthetic dataset | Real patient data | | Client registration | Separate public client | Separate public client | | `client_id` | Sandbox-specific | Production-specific | | Availability | Self-serve, instant | After security review | | Redirect URIs | HTTPS, or HTTP on loopback IP literals | HTTPS only | What *does* carry over is your configuration as intent: the same developer app, permission schema versions, and URIs are the source for both registrations — the sandbox is where you prove a configuration before the same snapshot goes to security review for production. ## Practical consequences - **Configuration is per-environment where it must be.** Your local development redirect URI (`http://127.0.0.1:…`) belongs to the sandbox configuration; production carries your deployed HTTPS URIs. - **Store credentials per environment.** Your app should treat (environment, issuer, client_id) as a unit — never send a sandbox client id to a production issuer. - **Sandbox is the canary.** API changes land in the sandbox at least one release cycle before production, so a continuously exercised sandbox integration is your cheapest early-warning system. - **Nothing you do in the sandbox affects production** — deploys, scope changes, and revocations are isolated to their environment. --- # The review pipeline The portal separates concerns that other platforms blur together. Four independent state machines govern your app: - **App lifecycle** — the stable identity of your app (draft, approved, deprecated, retired). - **Security review** — a decision about one immutable snapshot of your configuration and permission schema. - **Distribution intent** — private, invite-only, or marketplace. - **Marketplace listing review** — a decision about your public listing content, independent of security approval. ## Everything is a versioned snapshot Permission schemas are monotonically versioned and immutable once submitted. A security review evaluates a frozen snapshot, so: - You can keep editing while a review is in flight — edits become new draft versions and never mutate what the reviewer sees. - An **approved app stays approved** while its next version is under review. Iteration is never punished with downtime. - Approval is of a specific version; deploying a broader schema version to production requires that version to pass review first. ## Independence, concretely - **Production approval ≠ marketplace publication.** Approved apps can be distributed privately or by invitation without ever being listed. - **Unpublishing a listing ≠ revoking access.** Taking your marketplace listing down does not suspend clinics already running your app. - **The EMR is the authority.** The portal orchestrates registration and review, but effective grants, clinic consent, suspension, and revocation live in Ava EMR. The portal shows you the last EMR-confirmed state of your registrations (and its own delivery progress) as distinct fields. ## What reviewers look at The security review sees your app metadata, URIs, and permission schema with rationales — the same rationale text clinicians see at consent. The marketplace review sees your listing content. Writing honest, specific rationales and keeping scopes minimal is the fastest path through both. --- # API reference Every implemented FHIR resource. Interactions come from the same capability catalog that drives authorization — the runtime CapabilityStatement at `https://sandbox.avaemr.ca/fhir/r4/{partition_id}/metadata` is the authoritative version of this table. | Resource | Profile | App launch | Backend services | | --- | --- | --- | --- | | [Patient](/docs/reference/patient) | CA Core+ | read + search | read + search | | [Practitioner](/docs/reference/practitioner) | CA Core+ | read + search | read + search | | [PractitionerRole](/docs/reference/practitioner-role) | CA Core+ | read + search | read + search | | [Organization](/docs/reference/organization) | CA Core+ | read + search | read + search | | [Location](/docs/reference/location) | CA Core+ | read + search | read + search | | [Appointment](/docs/reference/appointment) | CA Core+ | read + search | read + search | | [Encounter](/docs/reference/encounter) | CA Core+ | read + search | read + search | | [Observation](/docs/reference/observation) | CA Core+ | — | read + search | | [Condition](/docs/reference/condition) | CA Core+ | read + search | read + search | | [AllergyIntolerance](/docs/reference/allergy-intolerance) | CA Core+ | read + search | read + search | | [ServiceRequest](/docs/reference/service-request) | CA Core+ | read | — | | [MedicationRequest](/docs/reference/medication-request) | CA Core+ | read + search | — | | [FamilyMemberHistory](/docs/reference/family-member-history) | CA Core+ | read + search | — | | [DocumentReference](/docs/reference/document-reference) | CA Core+ | create | create | | [Media](/docs/reference/media) | FHIR R4 | create | — | Backend Services registration is not yet available through this portal; the backend column documents the EMR's implemented surface. --- # Authorization Ava Ecosystem provisions **public SMART App Launch clients**: authorization code + PKCE `S256`, EHR launch, OpenID Connect. There are no client secrets. See the [App launch & PKCE guide](/docs/guides/app-launch-and-pkce) for the step-by-step flow. ## Endpoints | Endpoint | URL | | --- | --- | | SMART discovery | `https://sandbox.avaemr.ca/.well-known/smart-configuration` | | Authorization | `https://sandbox.avaemr.ca/oauth/authorize` | | Token | `https://sandbox.avaemr.ca/oauth/token` | | FHIR base | `https://sandbox.avaemr.ca/fhir/r4/{partition_id}` | Always resolve endpoints from the discovery document at runtime. ## Protocol details - Standards: FHIR R4 `4.0.1`, SMART App Launch `2.2.0`. - Grant types: `authorization_code`. - PKCE methods: `S256`. - Launch modes: `ehr`. - SMART capabilities: `launch-ehr`, `client-public`, `sso-openid-connect`, `context-ehr-patient`, `permission-patient`, `permission-user`, `permission-v2`. ## Scope grammar ``` /. e.g. patient/Patient.rs ``` `patient/` scopes are confined to the launched patient; `user/` scopes follow the signed-in clinician's access. Interaction letters are SMART v2 granular: `c` create, `r` read, `s` search. Protocol scopes: `launch`, `openid`, `fhirUser`, `launch/servicerequest` — `launch` is always required and `fhirUser` requires `openid`. ## Supported scopes - `launch` - `openid` - `fhirUser` - `launch/servicerequest` - `patient/Patient.r` - `patient/Patient.rs` - `user/Patient.rs` - `user/Practitioner.r` - `user/Practitioner.rs` - `user/PractitionerRole.rs` - `user/Organization.rs` - `user/Location.rs` - `patient/Appointment.rs` - `user/Appointment.rs` - `patient/Encounter.rs` - `user/Encounter.rs` - `patient/Condition.rs` - `user/Condition.rs` - `patient/AllergyIntolerance.rs` - `user/AllergyIntolerance.rs` - `user/ServiceRequest.r` - `patient/MedicationRequest.rs` - `patient/FamilyMemberHistory.rs` - `user/DocumentReference.c` - `user/Media.c` --- # Patient Demographics and identifiers for a person receiving care. The anchor resource for almost every integration. In an EHR launch, the token response's `patient` field carries the id of the launched chart, and `patient/Patient.*` scopes are confined to that patient; `user/Patient.rs` follows the signed-in clinician's access instead. Advertises a validated CA Core+ profile. ## Access | Mode | Interactions | Scopes | | --- | --- | --- | | App launch | read + search | `patient/Patient.r`, `patient/Patient.rs`, `user/Patient.rs` | | Backend services | read + search | `system/Patient.rs` | ## Search parameters Search results are `searchset` Bundles; follow the Bundle's `next` link for cursor pagination. | Parameter | Type | Description | | --- | --- | --- | | `_id` | token | Match a specific logical resource id. | | `_lastUpdated` | date | Filter by last modification time; supports FHIR date prefixes such as ge and lt. | | `_count` | number | Page size hint for the searchset Bundle. | | `_page_token` | string | Opaque pagination cursor. Never construct one — follow the Bundle's next link. | | `identifier` | token | Business identifier, such as an Ontario health number. | | `name` | string | Matches across family and given names. | | `family` | string | Family (last) name. | | `given` | string | Given (first) name. | | `birthdate` | date | Date of birth; supports FHIR date prefixes. | | `gender` | token | Administrative gender code. | | `phone` | token | Phone telecom value. | | `email` | token | Email telecom value. | | `active` | token | Whether the record is in active use (true/false). | ## Read the launched patient `GET /Patient/{id}` — launch context, returns `200 OK`. ```bash curl "https://sandbox.avaemr.ca/fhir/r4/{partition_id}/Patient/synthetic-patient-1" \ -H "Authorization: Bearer $SMART_ACCESS_TOKEN" \ -H "Accept: application/fhir+json" ``` ```json { "resourceType": "Patient", "id": "synthetic-patient-1", "meta": { "profile": ["http://fhir.infoway-inforoute.ca/io/CA-Core/StructureDefinition/patient-ca-core|1.1.0"] }, "identifier": [ { "system": "https://fhir.infoway-inforoute.ca/NamingSystem/ca-on-patient-hcn", "value": "1234567890" } ], "active": true, "name": [{ "family": "Example", "given": ["Avery"] }], "telecom": [{ "system": "phone", "value": "555-0100", "use": "home" }], "gender": "unknown", "birthDate": "1987-04-12" } ``` ## Search patients by name `GET /Patient?family={name}` — launch context, returns `200 OK`. ```bash curl "https://sandbox.avaemr.ca/fhir/r4/{partition_id}/Patient?family=Example&_count=10" \ -H "Authorization: Bearer $SMART_ACCESS_TOKEN" \ -H "Accept: application/fhir+json" ``` ```json { "resourceType": "Bundle", "type": "searchset", "link": [ { "relation": "self", "url": "https://sandbox.avaemr.ca/fhir/r4/{partition_id}/Patient?family=Example&_count=10" }, { "relation": "next", "url": "https://sandbox.avaemr.ca/fhir/r4/{partition_id}/Patient?family=Example&_count=10&_page_token=eyJvZmZzZXQiOjEwfQ" } ], "entry": [ { "fullUrl": "https://sandbox.avaemr.ca/fhir/r4/{partition_id}/Patient/synthetic-patient-1", "resource": { "resourceType": "Patient", "id": "synthetic-patient-1", "name": [{ "family": "Example", "given": ["Avery"] }], "gender": "unknown", "birthDate": "1987-04-12" }, "search": { "mode": "match" } } ] } ``` --- # Practitioner A clinician or other professional involved in care delivery. Practitioner access is clinician-context only (`user/` scopes): your app sees the practitioners the signed-in user can see. Use `fhirUser` on your launch to identify which Practitioner is driving the session. Advertises a validated CA Core+ profile. ## Access | Mode | Interactions | Scopes | | --- | --- | --- | | App launch | read + search | `user/Practitioner.r`, `user/Practitioner.rs` | | Backend services | read + search | `system/Practitioner.r`, `system/Practitioner.rs` | ## Search parameters Search results are `searchset` Bundles; follow the Bundle's `next` link for cursor pagination. | Parameter | Type | Description | | --- | --- | --- | | `_id` | token | Match a specific logical resource id. | | `_lastUpdated` | date | Filter by last modification time; supports FHIR date prefixes such as ge and lt. | | `_count` | number | Page size hint for the searchset Bundle. | | `_page_token` | string | Opaque pagination cursor. Never construct one — follow the Bundle's next link. | | `name` | string | Matches across family and given names. | | `family` | string | Family (last) name. | | `given` | string | Given (first) name. | | `active` | token | Whether the record is in active use (true/false). | ## Read a practitioner `GET /Practitioner/{id}` — launch context, returns `200 OK`. ```bash curl "https://sandbox.avaemr.ca/fhir/r4/{partition_id}/Practitioner/synthetic-practitioner-1" \ -H "Authorization: Bearer $SMART_ACCESS_TOKEN" \ -H "Accept: application/fhir+json" ``` ```json { "resourceType": "Practitioner", "id": "synthetic-practitioner-1", "meta": { "profile": ["http://fhir.infoway-inforoute.ca/io/CA-Core/StructureDefinition/practitioner-ca-core|1.1.0"] }, "active": true, "name": [{ "family": "Clinician", "given": ["Morgan"], "prefix": ["Dr."] }], "telecom": [{ "system": "email", "value": "morgan.clinician@clinic.example" }] } ``` --- # PractitionerRole A practitioner's roles, specialties, and locations within an organization. Connects Practitioner, Organization, and Location: which clinician works where, in what capacity. Useful for building provider directories or resolving who can be booked at which site. Advertises a validated CA Core+ profile. ## Access | Mode | Interactions | Scopes | | --- | --- | --- | | App launch | read + search | `user/PractitionerRole.rs` | | Backend services | read + search | `system/PractitionerRole.rs` | ## Search parameters Search results are `searchset` Bundles; follow the Bundle's `next` link for cursor pagination. | Parameter | Type | Description | | --- | --- | --- | | `_id` | token | Match a specific logical resource id. | | `_lastUpdated` | date | Filter by last modification time; supports FHIR date prefixes such as ge and lt. | | `_count` | number | Page size hint for the searchset Bundle. | | `_page_token` | string | Opaque pagination cursor. Never construct one — follow the Bundle's next link. | | `practitioner` | reference | Roles held by a specific Practitioner. | | `organization` | reference | Roles within a specific Organization. | | `location` | reference | Roles delivered at a specific Location. | | `active` | token | Whether the role is in active use (true/false). | ## Find a practitioner's roles `GET /PractitionerRole?practitioner={id}` — launch context, returns `200 OK`. ```bash curl "https://sandbox.avaemr.ca/fhir/r4/{partition_id}/PractitionerRole?practitioner=Practitioner/synthetic-practitioner-1" \ -H "Authorization: Bearer $SMART_ACCESS_TOKEN" \ -H "Accept: application/fhir+json" ``` ```json { "resourceType": "Bundle", "type": "searchset", "link": [ { "relation": "self", "url": "https://sandbox.avaemr.ca/fhir/r4/{partition_id}/PractitionerRole?practitioner=Practitioner/synthetic-practitioner-1" } ], "entry": [ { "resource": { "resourceType": "PractitionerRole", "id": "synthetic-practitioner-role-1", "active": true, "practitioner": { "reference": "Practitioner/synthetic-practitioner-1" }, "organization": { "reference": "Organization/synthetic-organization-1" }, "location": [{ "reference": "Location/synthetic-location-1" }], "code": [{ "text": "Family physician" }] }, "search": { "mode": "match" } } ] } ``` --- # Organization A clinic, practice group, or other healthcare organization. Organizations appear as the managing entity behind locations, roles, and encounters. In the sandbox there is a single synthetic clinic organization. Advertises a validated CA Core+ profile. ## Access | Mode | Interactions | Scopes | | --- | --- | --- | | App launch | read + search | `user/Organization.rs` | | Backend services | read + search | `system/Organization.rs` | ## Search parameters Search results are `searchset` Bundles; follow the Bundle's `next` link for cursor pagination. | Parameter | Type | Description | | --- | --- | --- | | `_id` | token | Match a specific logical resource id. | | `_lastUpdated` | date | Filter by last modification time; supports FHIR date prefixes such as ge and lt. | | `_count` | number | Page size hint for the searchset Bundle. | | `_page_token` | string | Opaque pagination cursor. Never construct one — follow the Bundle's next link. | | `identifier` | token | Business identifier for the organization. | | `name` | string | Organization name. | | `active` | token | Whether the record is in active use (true/false). | ## Read an organization `GET /Organization/{id}` — launch context, returns `200 OK`. ```bash curl "https://sandbox.avaemr.ca/fhir/r4/{partition_id}/Organization/synthetic-organization-1" \ -H "Authorization: Bearer $SMART_ACCESS_TOKEN" \ -H "Accept: application/fhir+json" ``` ```json { "resourceType": "Organization", "id": "synthetic-organization-1", "meta": { "profile": ["http://fhir.infoway-inforoute.ca/io/CA-Core/StructureDefinition/organization-ca-core|1.1.0"] }, "active": true, "name": "Maplewood Family Health Team", "telecom": [{ "system": "phone", "value": "555-0199" }] } ``` --- # Location A physical place where care is delivered. Locations back appointments and practitioner roles — exam rooms, clinic sites, and other bookable places within the organization. Advertises a validated CA Core+ profile. ## Access | Mode | Interactions | Scopes | | --- | --- | --- | | App launch | read + search | `user/Location.rs` | | Backend services | read + search | `system/Location.rs` | ## Search parameters Search results are `searchset` Bundles; follow the Bundle's `next` link for cursor pagination. | Parameter | Type | Description | | --- | --- | --- | | `_id` | token | Match a specific logical resource id. | | `_lastUpdated` | date | Filter by last modification time; supports FHIR date prefixes such as ge and lt. | | `_count` | number | Page size hint for the searchset Bundle. | | `_page_token` | string | Opaque pagination cursor. Never construct one — follow the Bundle's next link. | | `name` | string | Location name. | | `status` | token | active, suspended, or inactive. | | `type` | token | Kind of location. | | `partof` | reference | Locations contained within another Location. | ## Read a location `GET /Location/{id}` — launch context, returns `200 OK`. ```bash curl "https://sandbox.avaemr.ca/fhir/r4/{partition_id}/Location/synthetic-location-1" \ -H "Authorization: Bearer $SMART_ACCESS_TOKEN" \ -H "Accept: application/fhir+json" ``` ```json { "resourceType": "Location", "id": "synthetic-location-1", "meta": { "profile": ["http://fhir.infoway-inforoute.ca/io/CA-Core/StructureDefinition/location-ca-core|1.1.0"] }, "status": "active", "name": "Maplewood Clinic — Main Site", "managingOrganization": { "reference": "Organization/synthetic-organization-1" } } ``` --- # Appointment A booked or proposed visit between a patient and practitioners. With `patient/Appointment.rs` you see the launched patient's appointments; `user/Appointment.rs` covers everything on the signed-in clinician's schedule. Appointments reference their participants (Patient, Practitioner, Location) — read those with matching scopes. Advertises a validated CA Core+ profile. ## Access | Mode | Interactions | Scopes | | --- | --- | --- | | App launch | read + search | `patient/Appointment.rs`, `user/Appointment.rs` | | Backend services | read + search | `system/Appointment.rs` | ## Search parameters Search results are `searchset` Bundles; follow the Bundle's `next` link for cursor pagination. | Parameter | Type | Description | | --- | --- | --- | | `_id` | token | Match a specific logical resource id. | | `_lastUpdated` | date | Filter by last modification time; supports FHIR date prefixes such as ge and lt. | | `_count` | number | Page size hint for the searchset Bundle. | | `_page_token` | string | Opaque pagination cursor. Never construct one — follow the Bundle's next link. | | `date` | date | Appointment start time; supports FHIR date prefixes for ranges. | | `patient` | reference | Appointments for a specific Patient. | | `practitioner` | reference | Appointments involving a specific Practitioner. | | `status` | token | booked, arrived, fulfilled, cancelled, and other appointment statuses. | ## Upcoming appointments for the launched patient `GET /Appointment?patient={id}&date=ge{today}` — launch context, returns `200 OK`. ```bash curl "https://sandbox.avaemr.ca/fhir/r4/{partition_id}/Appointment?patient=Patient/synthetic-patient-1&date=ge2026-08-24&status=booked" \ -H "Authorization: Bearer $SMART_ACCESS_TOKEN" \ -H "Accept: application/fhir+json" ``` ```json { "resourceType": "Bundle", "type": "searchset", "link": [ { "relation": "self", "url": "https://sandbox.avaemr.ca/fhir/r4/{partition_id}/Appointment?patient=Patient/synthetic-patient-1&date=ge2026-08-24&status=booked" } ], "entry": [ { "resource": { "resourceType": "Appointment", "id": "synthetic-appointment-1", "status": "booked", "start": "2026-09-02T14:30:00-04:00", "end": "2026-09-02T14:50:00-04:00", "participant": [ { "actor": { "reference": "Patient/synthetic-patient-1" }, "status": "accepted" }, { "actor": { "reference": "Practitioner/synthetic-practitioner-1" }, "status": "accepted" } ] }, "search": { "mode": "match" } } ] } ``` --- # Encounter An interaction between a patient and the care team — a visit. Encounters record what actually happened: who was seen, by whom, when, and in connection with which appointment. Clinical resources such as Condition and Observation commonly reference their originating encounter. Advertises a validated CA Core+ profile. ## Access | Mode | Interactions | Scopes | | --- | --- | --- | | App launch | read + search | `patient/Encounter.rs`, `user/Encounter.rs` | | Backend services | read + search | `system/Encounter.rs` | ## Search parameters Search results are `searchset` Bundles; follow the Bundle's `next` link for cursor pagination. | Parameter | Type | Description | | --- | --- | --- | | `_id` | token | Match a specific logical resource id. | | `_lastUpdated` | date | Filter by last modification time; supports FHIR date prefixes such as ge and lt. | | `_count` | number | Page size hint for the searchset Bundle. | | `_page_token` | string | Opaque pagination cursor. Never construct one — follow the Bundle's next link. | | `patient` | reference | Encounters for a specific Patient. | | `practitioner` | reference | Encounters involving a specific Practitioner. | | `appointment` | reference | Encounters fulfilling a specific Appointment. | | `date` | date | Encounter period; supports FHIR date prefixes for ranges. | | `status` | token | planned, in-progress, finished, cancelled, and other encounter statuses. | ## Read an encounter `GET /Encounter/{id}` — launch context, returns `200 OK`. ```bash curl "https://sandbox.avaemr.ca/fhir/r4/{partition_id}/Encounter/synthetic-encounter-1" \ -H "Authorization: Bearer $SMART_ACCESS_TOKEN" \ -H "Accept: application/fhir+json" ``` ```json { "resourceType": "Encounter", "id": "synthetic-encounter-1", "meta": { "profile": ["http://fhir.infoway-inforoute.ca/io/CA-Core/StructureDefinition/encounter-ca-core|1.1.0"] }, "status": "finished", "class": { "system": "http://terminology.hl7.org/CodeSystem/v3-ActCode", "code": "AMB" }, "subject": { "reference": "Patient/synthetic-patient-1" }, "period": { "start": "2026-07-15T09:00:00-04:00", "end": "2026-07-15T09:20:00-04:00" } } ``` --- # Observation Measurements and findings — vitals, laboratory results, and similar. Observation is **Backend Services only** in the current registry: there are no `patient/Observation.*` or `user/Observation.*` launch scopes, so interactive SMART apps cannot request Observation access. Do not request or advertise launch scopes for it. Backend Services registration is not yet available through this portal. Advertises a validated CA Core+ profile. ## Access | Mode | Interactions | Scopes | | --- | --- | --- | | App launch | — | — | | Backend services | read + search | `system/Observation.rs` | > No launch scopes exist for Observation — requesting them fails with invalid_scope. ## Search parameters Search results are `searchset` Bundles; follow the Bundle's `next` link for cursor pagination. | Parameter | Type | Description | | --- | --- | --- | | `_id` | token | Match a specific logical resource id. | | `_lastUpdated` | date | Filter by last modification time; supports FHIR date prefixes such as ge and lt. | | `_count` | number | Page size hint for the searchset Bundle. | | `_page_token` | string | Opaque pagination cursor. Never construct one — follow the Bundle's next link. | | `patient` | reference | Observations for a specific Patient. | | `category` | token | Observation category, such as vital-signs or laboratory. | | `code` | token | The kind of observation, by code. | | `date` | date | Clinically effective time; supports FHIR date prefixes. | ## Search vitals for a patient (Backend Services) `GET /Observation?patient={id}&category=vital-signs` — backend context, returns `200 OK`. ```bash curl "https://sandbox.avaemr.ca/fhir/r4/{partition_id}/Observation?patient=Patient/synthetic-patient-1&category=vital-signs" \ -H "Authorization: Bearer $BACKEND_ACCESS_TOKEN" \ -H "Accept: application/fhir+json" ``` ```json { "resourceType": "Bundle", "type": "searchset", "link": [ { "relation": "self", "url": "https://sandbox.avaemr.ca/fhir/r4/{partition_id}/Observation?patient=Patient/synthetic-patient-1&category=vital-signs" } ], "entry": [ { "resource": { "resourceType": "Observation", "id": "synthetic-observation-1", "status": "final", "category": [{ "coding": [{ "system": "http://terminology.hl7.org/CodeSystem/observation-category", "code": "vital-signs" }] }], "code": { "coding": [{ "system": "http://loinc.org", "code": "8867-4", "display": "Heart rate" }] }, "subject": { "reference": "Patient/synthetic-patient-1" }, "effectiveDateTime": "2026-07-15T09:05:00-04:00", "valueQuantity": { "value": 72, "unit": "beats/minute", "system": "http://unitsofmeasure.org", "code": "/min" } }, "search": { "mode": "match" } } ] } ``` --- # Condition Problems, diagnoses, and health concerns on the patient's record. The problem list. Filter by `clinical-status` to separate active problems from resolved history, and by `category` for problem-list-item versus encounter-diagnosis entries. Advertises a validated CA Core+ profile. ## Access | Mode | Interactions | Scopes | | --- | --- | --- | | App launch | read + search | `patient/Condition.rs`, `user/Condition.rs` | | Backend services | read + search | `system/Condition.rs` | ## Search parameters Search results are `searchset` Bundles; follow the Bundle's `next` link for cursor pagination. | Parameter | Type | Description | | --- | --- | --- | | `_id` | token | Match a specific logical resource id. | | `_lastUpdated` | date | Filter by last modification time; supports FHIR date prefixes such as ge and lt. | | `_count` | number | Page size hint for the searchset Bundle. | | `_page_token` | string | Opaque pagination cursor. Never construct one — follow the Bundle's next link. | | `patient` | reference | Conditions for a specific Patient. | | `clinical-status` | token | active, recurrence, remission, resolved, and other clinical statuses. | | `verification-status` | token | confirmed, provisional, refuted, and other verification statuses. | | `category` | token | problem-list-item or encounter-diagnosis. | | `code` | token | The condition, by code. | | `onset-date` | date | When the condition began; supports FHIR date prefixes. | | `recorded-date` | date | When the condition was recorded; supports FHIR date prefixes. | ## Active problems for the launched patient `GET /Condition?patient={id}&clinical-status=active` — launch context, returns `200 OK`. ```bash curl "https://sandbox.avaemr.ca/fhir/r4/{partition_id}/Condition?patient=Patient/synthetic-patient-1&clinical-status=active" \ -H "Authorization: Bearer $SMART_ACCESS_TOKEN" \ -H "Accept: application/fhir+json" ``` ```json { "resourceType": "Bundle", "type": "searchset", "link": [ { "relation": "self", "url": "https://sandbox.avaemr.ca/fhir/r4/{partition_id}/Condition?patient=Patient/synthetic-patient-1&clinical-status=active" } ], "entry": [ { "resource": { "resourceType": "Condition", "id": "synthetic-condition-1", "clinicalStatus": { "coding": [{ "system": "http://terminology.hl7.org/CodeSystem/condition-clinical", "code": "active" }] }, "verificationStatus": { "coding": [{ "system": "http://terminology.hl7.org/CodeSystem/condition-ver-status", "code": "confirmed" }] }, "category": [{ "coding": [{ "system": "http://terminology.hl7.org/CodeSystem/condition-category", "code": "problem-list-item" }] }], "code": { "coding": [{ "system": "http://snomed.info/sct", "code": "44054006", "display": "Diabetes mellitus type 2" }] }, "subject": { "reference": "Patient/synthetic-patient-1" }, "recordedDate": "2024-11-03" }, "search": { "mode": "match" } } ] } ``` --- # AllergyIntolerance Allergies and intolerances recorded for the patient. Includes drug, food, and environmental entries, each with clinical and verification statuses and a criticality. Treat an empty search result as "no allergies recorded", not "no known allergies" — check the record's verification status semantics before displaying safety-critical banners. Advertises a validated CA Core+ profile. ## Access | Mode | Interactions | Scopes | | --- | --- | --- | | App launch | read + search | `patient/AllergyIntolerance.rs`, `user/AllergyIntolerance.rs` | | Backend services | read + search | `system/AllergyIntolerance.rs` | ## Search parameters Search results are `searchset` Bundles; follow the Bundle's `next` link for cursor pagination. | Parameter | Type | Description | | --- | --- | --- | | `_id` | token | Match a specific logical resource id. | | `_lastUpdated` | date | Filter by last modification time; supports FHIR date prefixes such as ge and lt. | | `_count` | number | Page size hint for the searchset Bundle. | | `_page_token` | string | Opaque pagination cursor. Never construct one — follow the Bundle's next link. | | `patient` | reference | Allergies for a specific Patient. | | `clinical-status` | token | active, inactive, or resolved. | | `verification-status` | token | confirmed, unconfirmed, refuted, or entered-in-error. | | `category` | token | food, medication, environment, or biologic. | | `type` | token | allergy or intolerance. | | `criticality` | token | low, high, or unable-to-assess. | | `code` | token | The substance or product, by code. | | `date` | date | Record date; supports FHIR date prefixes. | ## Read an allergy record `GET /AllergyIntolerance/{id}` — launch context, returns `200 OK`. ```bash curl "https://sandbox.avaemr.ca/fhir/r4/{partition_id}/AllergyIntolerance/synthetic-allergy-1" \ -H "Authorization: Bearer $SMART_ACCESS_TOKEN" \ -H "Accept: application/fhir+json" ``` ```json { "resourceType": "AllergyIntolerance", "id": "synthetic-allergy-1", "meta": { "profile": ["http://fhir.infoway-inforoute.ca/io/CA-Core/StructureDefinition/allergyintolerance-ca-core|1.1.0"] }, "clinicalStatus": { "coding": [{ "system": "http://terminology.hl7.org/CodeSystem/allergyintolerance-clinical", "code": "active" }] }, "verificationStatus": { "coding": [{ "system": "http://terminology.hl7.org/CodeSystem/allergyintolerance-verification", "code": "confirmed" }] }, "type": "allergy", "category": ["medication"], "criticality": "high", "code": { "coding": [{ "system": "http://snomed.info/sct", "code": "7980", "display": "Penicillin" }] }, "patient": { "reference": "Patient/synthetic-patient-1" } } ``` --- # ServiceRequest An order or referral for a procedure, test, or service. ServiceRequest is **read-only, by id, in clinician context** — there is no type-level search route and no patient-context scope. The id to read typically arrives through launch context: request the `launch/servicerequest` protocol scope and the EMR includes a `serviceRequest` id in the token response when the launch originates from an order. Advertises a validated CA Core+ profile. ## Access | Mode | Interactions | Scopes | | --- | --- | --- | | App launch | read | `user/ServiceRequest.r` | | Backend services | — | — | > Read-only (`user/ServiceRequest.r`); no search route is advertised. > Pair with the `launch/servicerequest` protocol scope to receive an id in launch context. ## Read the launched service request `GET /ServiceRequest/{id}` — launch context, returns `200 OK`. ```bash curl "https://sandbox.avaemr.ca/fhir/r4/{partition_id}/ServiceRequest/synthetic-servicerequest-1" \ -H "Authorization: Bearer $SMART_ACCESS_TOKEN" \ -H "Accept: application/fhir+json" ``` ```json { "resourceType": "ServiceRequest", "id": "synthetic-servicerequest-1", "meta": { "profile": ["http://fhir.infoway-inforoute.ca/io/CA-Core/StructureDefinition/servicerequest-ca-core|1.1.0"] }, "status": "active", "intent": "order", "code": { "coding": [{ "system": "http://snomed.info/sct", "code": "26604007", "display": "Complete blood count" }] }, "subject": { "reference": "Patient/synthetic-patient-1" }, "requester": { "reference": "Practitioner/synthetic-practitioner-1" }, "authoredOn": "2026-08-20" } ``` --- # MedicationRequest Prescriptions and medication orders for the patient. Patient-context only: `patient/MedicationRequest.rs` is confined to the launched patient's medication orders. Filter by `status` to separate active prescriptions from completed or stopped ones. Advertises a validated CA Core+ profile. ## Access | Mode | Interactions | Scopes | | --- | --- | --- | | App launch | read + search | `patient/MedicationRequest.rs` | | Backend services | — | — | ## Search parameters Search results are `searchset` Bundles; follow the Bundle's `next` link for cursor pagination. | Parameter | Type | Description | | --- | --- | --- | | `_id` | token | Match a specific logical resource id. | | `_lastUpdated` | date | Filter by last modification time; supports FHIR date prefixes such as ge and lt. | | `_count` | number | Page size hint for the searchset Bundle. | | `_page_token` | string | Opaque pagination cursor. Never construct one — follow the Bundle's next link. | | `patient` | reference | Medication requests for a specific Patient. | | `status` | token | active, completed, stopped, cancelled, and other request statuses. | ## Active medications for the launched patient `GET /MedicationRequest?patient={id}&status=active` — launch context, returns `200 OK`. ```bash curl "https://sandbox.avaemr.ca/fhir/r4/{partition_id}/MedicationRequest?patient=Patient/synthetic-patient-1&status=active" \ -H "Authorization: Bearer $SMART_ACCESS_TOKEN" \ -H "Accept: application/fhir+json" ``` ```json { "resourceType": "Bundle", "type": "searchset", "link": [ { "relation": "self", "url": "https://sandbox.avaemr.ca/fhir/r4/{partition_id}/MedicationRequest?patient=Patient/synthetic-patient-1&status=active" } ], "entry": [ { "resource": { "resourceType": "MedicationRequest", "id": "synthetic-medicationrequest-1", "status": "active", "intent": "order", "medicationCodeableConcept": { "coding": [{ "system": "http://snomed.info/sct", "code": "325278007", "display": "Metformin 500 mg oral tablet" }] }, "subject": { "reference": "Patient/synthetic-patient-1" }, "authoredOn": "2026-06-01", "requester": { "reference": "Practitioner/synthetic-practitioner-1" } }, "search": { "mode": "match" } } ] } ``` --- # FamilyMemberHistory Health events in the patient's family history. Patient-context only: `patient/FamilyMemberHistory.rs` returns the family history entries recorded on the launched patient's chart. Advertises a validated CA Core+ profile. ## Access | Mode | Interactions | Scopes | | --- | --- | --- | | App launch | read + search | `patient/FamilyMemberHistory.rs` | | Backend services | — | — | ## Search parameters Search results are `searchset` Bundles; follow the Bundle's `next` link for cursor pagination. | Parameter | Type | Description | | --- | --- | --- | | `_id` | token | Match a specific logical resource id. | | `_lastUpdated` | date | Filter by last modification time; supports FHIR date prefixes such as ge and lt. | | `_count` | number | Page size hint for the searchset Bundle. | | `_page_token` | string | Opaque pagination cursor. Never construct one — follow the Bundle's next link. | | `patient` | reference | Family history entries for a specific Patient. | | `status` | token | partial, completed, entered-in-error, or health-unknown. | ## Read a family history entry `GET /FamilyMemberHistory/{id}` — launch context, returns `200 OK`. ```bash curl "https://sandbox.avaemr.ca/fhir/r4/{partition_id}/FamilyMemberHistory/synthetic-familyhistory-1" \ -H "Authorization: Bearer $SMART_ACCESS_TOKEN" \ -H "Accept: application/fhir+json" ``` ```json { "resourceType": "FamilyMemberHistory", "id": "synthetic-familyhistory-1", "meta": { "profile": ["http://fhir.infoway-inforoute.ca/io/CA-Core/StructureDefinition/familymemberhistory-ca-core|1.1.0"] }, "status": "completed", "patient": { "reference": "Patient/synthetic-patient-1" }, "relationship": { "coding": [{ "system": "http://terminology.hl7.org/CodeSystem/v3-RoleCode", "code": "MTH", "display": "mother" }] }, "condition": [ { "code": { "coding": [{ "system": "http://snomed.info/sct", "code": "38341003", "display": "Hypertension" }] } } ] } ``` --- # DocumentReference Write a document — a report, note, or file — onto the patient's chart. **Create-only.** `user/DocumentReference.c` lets your app attach documents to the chart; there is no read, search, update, or delete. The document content travels inline as a base64-encoded attachment. A successful create returns `201 Created` with the stored resource and its server-assigned id in the `Location` header. Advertises a validated CA Core+ profile. ## Access | Mode | Interactions | Scopes | | --- | --- | --- | | App launch | create | `user/DocumentReference.c` | | Backend services | create | `system/DocumentReference.c` | > Create-only in both authorization modes; your app cannot read documents back. ## Attach a PDF report to the chart `POST /DocumentReference` — launch context, returns `201 Created`. ```bash curl -X POST "https://sandbox.avaemr.ca/fhir/r4/{partition_id}/DocumentReference" \ -H "Authorization: Bearer $SMART_ACCESS_TOKEN" \ -H "Content-Type: application/fhir+json" \ -H "Accept: application/fhir+json" \ -d '{ "resourceType": "DocumentReference", "status": "current", "type": { "coding": [{ "system": "http://loinc.org", "code": "34133-9", "display": "Summary of episode note" }] }, "subject": { "reference": "Patient/synthetic-patient-1" }, "content": [{ "attachment": { "contentType": "application/pdf", "data": "", "title": "Visit summary" } }] }' ``` ```json { "resourceType": "DocumentReference", "id": "synthetic-docref-1", "meta": { "profile": ["http://fhir.infoway-inforoute.ca/io/CA-Core/StructureDefinition/documentreference-ca-core|1.1.0"] }, "status": "current", "type": { "coding": [{ "system": "http://loinc.org", "code": "34133-9", "display": "Summary of episode note" }] }, "subject": { "reference": "Patient/synthetic-patient-1" }, "date": "2026-08-24T10:15:00-04:00", "content": [ { "attachment": { "contentType": "application/pdf", "title": "Visit summary" } } ] } ``` --- # Media Write an image, video, or audio capture onto the patient's chart. **Create-only**, clinician context (`user/Media.c`). Media is an ordinary FHIR R4 resource — it is the one resource without a CA Core+ profile claim. Use it for photos and other captures; use DocumentReference for documents. Ordinary FHIR R4 resource (no CA Core+ profile claim). ## Access | Mode | Interactions | Scopes | | --- | --- | --- | | App launch | create | `user/Media.c` | | Backend services | — | — | > Create-only; no read, search, update, or delete, and no Backend Services access. ## Attach a wound photo `POST /Media` — launch context, returns `201 Created`. ```bash curl -X POST "https://sandbox.avaemr.ca/fhir/r4/{partition_id}/Media" \ -H "Authorization: Bearer $SMART_ACCESS_TOKEN" \ -H "Content-Type: application/fhir+json" \ -H "Accept: application/fhir+json" \ -d '{ "resourceType": "Media", "status": "completed", "type": { "coding": [{ "system": "http://terminology.hl7.org/CodeSystem/media-type", "code": "image" }] }, "subject": { "reference": "Patient/synthetic-patient-1" }, "content": { "contentType": "image/jpeg", "data": "", "title": "Left forearm, day 3" } }' ``` ```json { "resourceType": "Media", "id": "synthetic-media-1", "status": "completed", "type": { "coding": [{ "system": "http://terminology.hl7.org/CodeSystem/media-type", "code": "image" }] }, "subject": { "reference": "Patient/synthetic-patient-1" }, "createdDateTime": "2026-08-24T10:20:00-04:00", "content": { "contentType": "image/jpeg", "title": "Left forearm, day 3" } } ``` --- # Changelog Developer-visible changes to the Ava FHIR API, the sandbox, and this portal, newest first. Breaking changes additionally follow the notice windows in the [versioning policy](/docs/versioning-policy). ## September 2026 — SMART launcher locations Launch configuration now supports Chart Launch and Consult Launch, independently or together. Chart Launch remains the default. Consult Launch can create a new consult from the chart header or reuse an existing consult, with a saved app link and the consult's existing ServiceRequest reference in SMART `fhirContext`. No FHIR resource IDs, scopes, or interactions change. See the [launch guide](/docs/guides/app-launch-and-pkce). ## 2026-08 - **Documentation launched.** This documentation section: quickstart, guides, a generated API reference, and raw-markdown endpoints for agents (append `.md` to any page, or fetch [/docs/llms.txt](/docs/llms.txt)). - **Sandbox test data.** Organizations can activate a private synthetic sandbox preloaded with the `ontario_primary_care_v1` dataset, including an EMR login for driving EHR launches. ## 2026-07 - **Two-factor authentication** enforced for portal accounts. - **Initial API surface.** FHIR R4 (4.0.1) with SMART App Launch 2.2: 15 resources, public PKCE clients, granular SMART v2 scopes, and create-only DocumentReference/Media writes. See the [API reference](/docs/reference) for the current matrix. --- # Versioning policy Ava's API versioning and change-control commitment to developers. This is Ava's commitment to developers integrating with the Ava EMR FHIR R4 API: how the API is versioned, what kinds of changes we make, how you find out about them, and how long you have to adapt. ## What you can rely on - **Pinned standards.** FHIR R4 (4.0.1) with SMART App Launch and SMART Backend Services 2.2. Fourteen resources advertise validated CA Core+ profiles; Media is supported as ordinary FHIR R4. Terminology releases (SNOMED CT CA, PHCVS) and UCUM units are pinned to explicit versions and only move deliberately, with conformance re-validated before a new version is advertised. - **A machine-readable contract.** The CapabilityStatement at `{fhir_base_url}/metadata` and the SMART discovery document at `{issuer}/.well-known/smart-configuration` are generated from the same internal registry that enforces authorization. If something is advertised there, it is implemented and tested; if it is not advertised, do not depend on it. - **Version in the URL.** The FHIR major version is part of the base path (`/fhir/r4/…`). A future FHIR version would be mounted side by side under its own path. The meaning of an existing path never changes in place. ## Non-breaking changes These ship continuously, announced in the [changelog](/docs/changelog), and require no action: - New resource types, interactions, search parameters, or scopes. - New optional elements or extensions in returned resources — write clients defensively and ignore elements you do not recognize. - Newly advertised profiles, once they pass automated validation. - Stricter rejection of requests that were already invalid under the documented contract. ## Breaking changes Removing or renaming anything that has shipped, or changing the type, meaning, cardinality, or terminology binding of a published element, is a breaking change. When one is unavoidable: 1. A deprecation notice is published in the changelog and emailed to the registered owner of every application whose granted scopes touch the affected surface. 2. Affected endpoints serve standard `Deprecation` and `Sunset` HTTP headers for the entire window, so you can detect impact programmatically. 3. You get a minimum of **6 months** between notice and removal, and **12 months** when the shape or meaning of returned data changes. 4. Before removal we check for remaining traffic and contact any active integrations directly. ## Sandbox first Every API change reaches the synthetic sandbox before production — at least one release cycle earlier — so you can validate your integration against upcoming behavior before your users see it. ## Staying informed - Watch the [changelog](/docs/changelog). - Keep your application's contact information current; deprecation notices go to the registered owner. - Re-read the CapabilityStatement and SMART discovery document periodically — they are always the authoritative description of the current API surface. Questions about an announced change or a migration path: contact the Ava integration team through your organization's portal workspace.