Appearance
Understanding Endpoint Capabilities
The Endpoint Capabilities endpoint returns everything a FHIR server publishes about itself: which resource types it supports, which interactions (read/write) are available per resource, and the full SMART on FHIR authorization configuration.
This guide explains how to interpret that payload so you can answer questions like:
- Can I read Observations from this endpoint?
- Can I write Appointments to this endpoint?
- What audience does this endpoint serve — patients, providers, or both?
- Which OAuth flow should my app use?
The response shape
GET /api/v1/endpoints/:id/capabilitiesReturns three things stacked together:
summary— pre-extracted scalar fields for fast consumptioncapabilityStatement— the raw FHIR CapabilityStatement from/metadatasmartConfiguration— the raw/.well-known/smart-configurationdocument
The summary is enough for most filtering and display. Reach into the raw payloads when you need fine-grained detail the summary doesn't surface (per-resource read/write, specific SMART extensions, etc).
Per-resource read/write support
The summary.supportedResources array tells you which resource types the server exposes, but not what you can do with each one. To determine read vs write capability per resource, walk the raw capabilityStatement:
capabilityStatement.rest[0].resource[]
├─ type → "Observation"
└─ interaction[]
├─ code: "read"
├─ code: "search-type"
└─ code: "create"Group the interaction codes into read-capable and write-capable buckets:
| Bucket | FHIR interaction codes |
|---|---|
| Read | read, vread, search-type, history-instance, history-type |
| Write | create, update, patch, delete |
A resource with at least one read code is readable; one with at least one write code is writable. Many production endpoints are read-only — always check before assuming you can POST.
Example: parse read/write per resource
typescript
interface ResourceAccess {
type: string;
readable: boolean;
writable: boolean;
}
const READ = new Set(['read', 'vread', 'search-type', 'history-instance', 'history-type']);
const WRITE = new Set(['create', 'update', 'patch', 'delete']);
async function getResourceAccess(endpointId: string): Promise<ResourceAccess[]> {
const res = await fetch(`https://fhir-api.luxera.io/api/v1/endpoints/${endpointId}/capabilities`);
const { data } = await res.json();
const resources = data.capabilityStatement?.rest?.[0]?.resource ?? [];
return resources.map((r: any) => {
const codes = new Set((r.interaction ?? []).map((i: any) => i.code));
return {
type: r.type,
readable: [...READ].some(c => codes.has(c)),
writable: [...WRITE].some(c => codes.has(c)),
};
});
}python
import requests
READ = {"read", "vread", "search-type", "history-instance", "history-type"}
WRITE = {"create", "update", "patch", "delete"}
def get_resource_access(endpoint_id: str):
r = requests.get(f"https://fhir-api.luxera.io/api/v1/endpoints/{endpoint_id}/capabilities")
data = r.json()["data"]
resources = (data.get("capabilityStatement") or {}).get("rest", [{}])[0].get("resource", [])
out = []
for res in resources:
codes = {i["code"] for i in res.get("interaction", [])}
out.append({
"type": res["type"],
"readable": bool(codes & READ),
"writable": bool(codes & WRITE),
})
return outUnderstanding the access field
On every endpoint, the top-level access field tells you the audience — who the endpoint is intended for:
| Value | Meaning |
|---|---|
["patient"] | Patient-facing (standalone launch, patient-level scopes) |
["provider"] | Clinician-facing (EHR launch, user-level scopes) |
["patient", "provider"] | Both audiences supported |
[] | Unknown — endpoint has not been probed or does not publish SMART config |
When the directory probes an endpoint's SMART configuration, it infers the audience from launch mode and scope patterns:
- Patient indicators:
launch-standalonecapability,context-standalone-patient, or anypatient/*scope - Provider indicators:
launch-ehrcapability,context-ehr-patient, or anyuser/*scope
Both lists can be true — many Epic and Cerner endpoints support both patient and provider audiences.
SMART capability codes
The summary.smartCapabilities array is a flat list of SMART conformance codes. They fall into a few natural groups:
Launch Modes
How your app is launched:
| Code | Meaning |
|---|---|
launch-ehr | EHR-initiated launch — your app is opened from within the EHR |
launch-standalone | Standalone launch — your app opens the EHR for auth |
Client Types
Which OAuth client authentication schemes the server accepts:
| Code | Use when |
|---|---|
client-public | Mobile/SPA apps that can't keep a secret |
client-confidential-symmetric | Server apps using a shared client secret |
client-confidential-asymmetric | Server apps using JWT client assertion (backend services) |
Single Sign-On
| Code | Meaning |
|---|---|
sso-openid-connect | Server returns an OpenID Connect id_token — use it to identify the end user |
Launch Context
What context the server will pass to your app after launch:
| Code | Means the server will pass… |
|---|---|
context-ehr-patient | The current patient when launched from the EHR |
context-ehr-encounter | The current encounter when launched from the EHR |
context-standalone-patient | A patient picker for standalone launches |
context-standalone-encounter | An encounter picker for standalone launches |
context-banner | Patient banner styling hints |
context-style | UI style hints (fonts, colors) |
Permissions / Scopes
Which scope patterns the server accepts:
| Code | Means the server supports… |
|---|---|
permission-offline | offline_access scope (refresh tokens) |
permission-patient | patient/* scopes (patient-level data access) |
permission-user | user/* scopes (user-level data access) |
permission-v1 | SMART v1 scope syntax (patient/Observation.read) |
permission-v2 | SMART v2 scope syntax (patient/Observation.rs) |
Most production endpoints accept v1 syntax. v2 is newer and adds finer-grained controls.
Authorization endpoint protocol
| Code | Meaning |
|---|---|
authorize-post | The authorization endpoint accepts POST as well as GET — useful for long request URIs |
Choosing your OAuth flow
Based on the capability flags, here's how to pick a flow:
| Your app is… | Use | Required capabilities |
|---|---|---|
| A mobile app or SPA | Standalone launch, PKCE, public client | launch-standalone, client-public, permission-patient |
| A web app launched from the EHR | EHR launch, confidential symmetric | launch-ehr, client-confidential-symmetric, permission-user |
| A backend service (no user) | Client credentials, asymmetric JWT | client-confidential-asymmetric, SMART Backend Services grant type |
The grant_types_supported field in smartConfiguration tells you which OAuth 2.0 grant types the authorization server accepts (authorization_code, client_credentials, refresh_token, etc.).
What isn't in summary
The summary fields are optimized for browsing and filtering. If you need any of the following, read from the raw capabilityStatement or smartConfiguration:
- Per-resource interactions (see above)
searchParamlists (which query parameters each resource supports)operationdefinitions (custom$operations like$everything)- Server-level extensions
- The full
scopes_supportedlist jwks_uri,introspection_endpoint, and other OAuth metadata
The raw documents are complete passthroughs of what the server published — the directory does not modify or filter them.
Probe status
The probeStatus field indicates how recently the directory contacted the endpoint:
| Status | Meaning |
|---|---|
success | Both /metadata and /.well-known/smart-configuration responded |
partial | Only one of the two responded |
failed | Neither responded. Check probeFailureReason for the cause. |
Common failure reasons:
auth_required— server is alive but requires credentials (endpoint stays aslisted, not marked unresponsive)dns_failure,timeout,network_error— endpoint is likely down (marked asunresponsive)
Data from a failed probe may be stale — treat it as a hint, not ground truth.