From aidbox
Guides Aidbox FHIR platform: Docker setup, init bundles, FHIR REST API, CRUD/search/transactions/bundles, access policies, subscriptions, custom resources, SQL on FHIR, terminology, and configuration.
How this skill is triggered — by the user, by Claude, or both
Slash command
/aidbox:aidboxThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Aidbox is a FHIR-native platform built on PostgreSQL. It provides FHIR API with extensions for real-world production use.
Aidbox is a FHIR-native platform built on PostgreSQL. It provides FHIR API with extensions for real-world production use.
Aidbox docs live at https://www.health-samurai.io/docs/aidbox.
When you need to look up Aidbox documentation, use the hs-search skill — it can search and fetch full pages from health-samurai.io docs. Invoke it with /hs-search or let it trigger automatically when searching for Aidbox docs.
To quickly set up a new Aidbox instance with Docker Compose:
# Download the docker-compose.yml
curl -JO https://aidbox.app/runme
# Start Aidbox
docker compose up -d
This downloads a pre-configured docker-compose.yml with recommended environment variables. Always use a fresh download — don't write docker-compose.yml manually. After starting, read the downloaded file to find the port (AIDBOX_PORT) and root client secret (BOX_ROOT_CLIENT_SECRET).
Open the Aidbox UI in the browser (check the port in docker-compose.yml, typically http://localhost:8080). On first launch, click "Continue with Aidbox account" to create a free account at https://aidbox.app/ and activate the instance.
For the full getting started guide, fetch:
curl -s 'https://www.health-samurai.io/docs/aidbox/getting-started/run-aidbox-locally.md'
Init Bundle is the primary way to configure Aidbox. It executes a FHIR Bundle at startup, before the HTTP server starts, to load configuration resources (AccessPolicy, SearchParameter, SubscriptionTopic, custom StructureDefinitions, seed data, etc.).
Set BOX_INIT_BUNDLE in docker-compose.yml to point to your bundle file:
environment:
BOX_INIT_BUNDLE: file:///app/init-bundle.json
Supported URL schemes: file:///path/to/bundle.json, https://storage.googleapis.com/...
Only JSON format is supported. Use transaction (all-or-nothing — startup fails if any entry fails) or batch (partial failures produce warnings but don't block startup):
{
"resourceType": "Bundle",
"type": "transaction",
"entry": [
{
"resource": {
"resourceType": "AccessPolicy",
"id": "as-my-app-crud-patients",
"engine": "matcho",
"link": [{"reference": "Client/my-app"}],
"matcho": {
"params": {"resource/type": "Patient"},
"request-method": {"$enum": ["get", "post", "put", "patch"]}
}
},
"request": { "method": "PUT", "url": "AccessPolicy/as-my-app-crud-patients" }
}
]
}
/fhir manually first. Post it multiple times to confirm it is idempotent.ifNoneExist in requests to avoid duplicate key errors on restart.envsubst.Init Bundle can load FHIR IGs automatically at startup. Search the docs for details:
curl -s 'https://www.health-samurai.io/api/llm/search?q=load+FHIR+IG+init+bundle&limit=5'
For full documentation:
curl -s 'https://www.health-samurai.io/docs/aidbox/configuration/init-bundle.md'
Always use the /fhir/ prefix for all Aidbox API interactions (e.g., /fhir/Patient, /fhir/Observation). Never use URLs without the /fhir/ prefix — the non-prefixed format (/Patient) is deprecated.
For applications, create a dedicated Client resource with appropriate AccessPolicies (see Access Policies section). Load it via Init Bundle:
{
"resourceType": "Client",
"id": "my-app",
"secret": "<my-app-secret>",
"grant_types": ["basic"]
}
Then authenticate with Basic Auth:
curl -s -u "my-app:my-app-secret" "http://localhost:<port>/fhir/Patient"
For quick manual testing only, use the root client (root / password from BOX_ROOT_CLIENT_SECRET in docker-compose.yml). The root client bypasses all access policies — never use it in application code.
# Root client — for debugging/testing only
curl -s -u "root:<BOX_ROOT_CLIENT_SECRET>" "http://localhost:<port>/fhir/Patient"
# Read a specific resource
curl -s -u "<client>:<secret>" "http://localhost:<port>/fhir/Patient/<id>" | python3 -m json.tool
# Search resources
curl -s -u "<client>:<secret>" "http://localhost:<port>/fhir/Patient?name=John&_count=10" | python3 -m json.tool
# Create (POST)
curl -s -u "<client>:<secret>" -X POST "http://localhost:<port>/fhir/Patient" \
-H "Content-Type: application/fhir+json" \
-d '{"resourceType":"Patient","name":[{"given":["Test"],"family":"User"}]}' | python3 -m json.tool
# Update (PUT) — requires id in both URL and body
curl -s -u "<client>:<secret>" -X PUT "http://localhost:<port>/fhir/Patient/<id>" \
-H "Content-Type: application/fhir+json" \
-d '{"resourceType":"Patient","id":"<id>","name":[{"given":["Updated"],"family":"User"}]}' | python3 -m json.tool
# Partial update (PATCH)
curl -s -u "<client>:<secret>" -X PATCH "http://localhost:<port>/fhir/Patient/<id>" \
-H "Content-Type: application/merge-patch+json" \
-d '{"birthDate":"1990-01-01"}' | python3 -m json.tool
# Delete
curl -s -u "<client>:<secret>" -X DELETE "http://localhost:<port>/fhir/Patient/<id>"
# History
curl -s -u "<client>:<secret>" "http://localhost:<port>/fhir/Patient/<id>/_history" | python3 -m json.tool
GET /fhir/Patient?name=John&birthdate=gt1990-01-01
GET /fhir/Patient?_include=Patient:organization
GET /fhir/Patient?_revinclude=Observation:subject
GET /fhir/Patient?_has:Observation:subject:code=http://loinc.org|1234
GET /fhir/Patient?_sort=-birthdate&_count=10&_page=2
Search modifiers: :exact, :contains, :missing, :not, :text, :of-type
Prefixes for date/number: eq, ne, gt, lt, ge, le, sa, eb, ap
curl -s -u "<client>:<secret>" -X POST "http://localhost:<port>/fhir" \
-H "Content-Type: application/fhir+json" \
-d '{
"resourceType": "Bundle",
"type": "transaction",
"entry": [
{
"fullUrl": "urn:uuid:patient-1",
"resource": {"resourceType": "Patient", "name": [{"family": "Smith"}]},
"request": {"method": "POST", "url": "Patient"}
},
{
"resource": {"resourceType": "Observation", "subject": {"reference": "urn:uuid:patient-1"}},
"request": {"method": "POST", "url": "Observation"}
}
]
}' | python3 -m json.tool
Transaction = all-or-nothing. Batch = independent operations, partial success OK.
PUT /fhir/Patient?identifier=http://mrn|12345 # Conditional create/update
DELETE /fhir/Patient?identifier=http://mrn|12345 # Conditional delete
POST /fhir/Patient/$validate
Content-Type: application/fhir+json
{"resourceType": "Patient", "birthDate": "invalid"}
Define custom resource types using StructureDefinition (the recommended approach). The id, name, and type must match; set derivation: specialization to create a new resource type with its own database table.
For a full tutorial with examples, use the hs-search skill or fetch:
curl -s 'https://www.health-samurai.io/docs/aidbox/tutorials/artifact-registry-tutorials/custom-resources/custom-resources-using-structuredefinition.md'
Use the matcho engine (recommended). Link policies to Client, User, or Operation — unlinked policies are global and evaluate on every request, hurting performance.
id: as-my-app-crud-patients-and-practitioners
resourceType: AccessPolicy
engine: matcho
link:
- reference: Client/my-app
matcho:
params:
resource/type:
$enum:
- Patient
- Practitioner
request-method:
$enum:
- get
- post
- patch
- put
id: as-my-app-allowed-to-use-fhir-transactions
resourceType: AccessPolicy
engine: matcho
link:
- reference: Client/my-app
matcho:
operation:
id: FhirTransaction
Note: this only allows access to the /fhir transaction endpoint. Individual resource operations within the bundle need their own policies.
id: as-patient-get-own-data
resourceType: AccessPolicy
engine: matcho
link:
- reference: Operation/FhirRead
matcho:
params:
resource/id: .user.fhirUser.id
resource/type: Patient
as- prefix: describe the audience and action (e.g., as-practitioner-read-observations)Client, User, or Operation — avoid global policies. paths for present?: if both sides of a .user.data.field comparison are missing, matcho may evaluate to true. Add explicit present? checks:
matcho:
body:
subject: .user.data.patient
user:
data:
patient: present?
matcho:
params:
_include: nil?
_revinclude: nil?
_with: nil?
_assoc: nil?
complex with or — easier to maintain and Aidbox logs which policy granted access$one-of workFor more examples and best practices:
curl -s 'https://www.health-samurai.io/docs/aidbox/tutorials/security-access-control-tutorials/accesspolicy-examples.md'
curl -s 'https://www.health-samurai.io/docs/aidbox/tutorials/security-access-control-tutorials/accesspolicy-best-practices.md'
To inspect current policies:
curl -s -u "<client>:<secret>" "http://localhost:<port>/fhir/AccessPolicy?_count=50" | python3 -m json.tool
POST /$sql
Content-Type: text/yaml
- select * from patient where resource->>'birthDate' > '1990-01-01' limit 10
Or via /$psql for interactive queries. Tables follow naming: resource type in lowercase (e.g., patient, observation). Resource stored in resource JSONB column.
{
"resourceType": "AidboxTopicDestination",
"topic": "http://example.com/patient-changes",
"kind": "webhook",
"url": "https://my-service.com/webhook",
"filter": [{"resourceType": "Patient"}]
}
GET /fhir/$export # System-level export
GET /fhir/Group/<id>/$export # Group export
POST /fhir/$import # Bulk import (ndjson)
GET /fhir/ValueSet/$expand?url=<vs-url>&filter=<text>
POST /fhir/CodeSystem/$lookup
POST /fhir/ConceptMap/$translate
Install (ESM-only package):
npm install @health-samurai/aidbox-client@^0.0.0-alpha.5
import { AidboxClient, BasicAuthProvider } from "@health-samurai/aidbox-client";
// Use a dedicated Client resource (see Authentication section)
const baseUrl = "http://localhost:8888";
const client = new AidboxClient(baseUrl, new BasicAuthProvider(baseUrl, "my-app", "my-app-secret"));
Auth providers:
BasicAuthProvider(baseUrl, username, password) — HTTP Basic Auth (server-side)BrowserAuthProvider(baseUrl) — cookie-based sessions (browser)SmartBackendServicesAuthProvider(...) — OAuth 2.0 SMART Backend ServicesAll methods return Result<ResourceResponse<T>, ResourceResponse<OperationOutcome>>:
const result = await client.read<Patient>({ type: "Patient", id: "pt-1" });
if (result.isOk()) {
const patient = result.value.resource;
} else {
const error = result.value.resource; // OperationOutcome
}
// Read
const result = await client.read<Patient>({ type: "Patient", id: "pt-1" });
// Create
const result = await client.create<Patient>({
type: "Patient",
resource: { resourceType: "Patient", gender: "female" },
});
// Update (PUT)
const result = await client.update<Patient>({
type: "Patient",
id: "pt-1",
resource: { resourceType: "Patient", name: [{ family: "Smith" }] },
});
// Patch (JSON Patch)
const result = await client.patch<Patient>({
type: "Patient",
id: "pt-1",
patch: [{ op: "replace", path: "/name/0/family", value: "NewName" }],
});
// Delete
const result = await client.delete<Patient>({ type: "Patient", id: "pt-1" });
Conditional variants also available: conditionalCreate, conditionalUpdate, conditionalPatch, conditionalDelete — pass searchParameters: [["key", "value"]] instead of id.
Search parameters are [string, string][] tuples (allows duplicate keys like multiple _include):
// Type-level search
const result = await client.searchType({
type: "Patient",
query: [["family", "Smith"], ["_count", "10"]],
});
if (result.isOk()) {
const bundle = result.value.resource; // Bundle with entries
}
// System-level search
const result = await client.searchSystem({
query: [["_type", "Patient"], ["family", "Test"]],
});
// Compartment search
const result = await client.searchCompartment({
compartment: "Patient",
compartmentId: "pt-1",
type: "Observation",
query: [["status", "final"]],
});
const result = await client.transaction({
format: "application/json",
bundle: {
resourceType: "Bundle",
type: "transaction",
entry: [
{
request: { method: "POST", url: "Patient" },
resource: { resourceType: "Patient", name: [{ family: "Doe" }] },
},
{
request: { method: "DELETE", url: "Patient/some-id" },
},
],
},
});
// Batch (partial success OK)
const result = await client.batch({
format: "application/json",
bundle: { resourceType: "Bundle", type: "batch", entry: [...] },
});
// $validate
const result = await client.validate<Patient>({
type: "Patient",
resource: patientResource,
});
// Custom operation
const result = await client.operation<InputType, OutputType>({
type: "Patient",
id: "pt-1",
operation: "$everything",
resource: { /* parameters */ },
});
Use rawRequest for Aidbox-specific endpoints (no built-in $sql method):
// SQL query
const result = await client.rawRequest({
method: "POST",
url: "/$sql",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(["SELECT * FROM patient WHERE id = ?", "pt-1"]),
});
const rows = await result.response.json();
// Any Aidbox endpoint — rawRequest throws on errors, request returns Result<T>
const result = await client.request<MyType>({
method: "POST",
url: "/custom-endpoint",
body: JSON.stringify(data),
});
For full FHIR type coverage, use the atomic-generate-types skill to generate TypeScript types from FHIR packages.
curl -s -u "<root>:<secret>" "http://localhost:<port>/fhir/AccessPolicy?_count=50"POST /$access-policy-checkcurl -s "http://localhost:<port>/health" | python3 -m json.tool
curl -s -u "<client>:<secret>" "http://localhost:<port>/fhir/<ResourceType>/<id>" | python3 -m json.tool
To apply init bundle changes without restarting:
curl -s -u "<root>:<secret>" -X POST "http://localhost:<port>/fhir" \
-H "Content-Type: application/fhir+json" \
-d @init-bundle.json | python3 -m json.tool
npx claudepluginhub healthsamurai/samurai-skills --plugin aidboxGuides building FHIR R4 REST endpoints for Patient, Observation, Encounter, Condition, MedicationRequest including resource validation, HTTP status codes, value sets, coding systems (LOINC, SNOMED, RxNorm, ICD-10), and OperationOutcome error handling.
Provides expert guidance for Azure Health Data Services development including FHIR/DICOM APIs, bulk export/import, de-identification, events, and Synapse/ADF integrations. Covers troubleshooting, best practices, architecture, security, and deployment.
Provides FHIR development guidance including package management, resource modeling, server implementation, and terminology handling for R4, R4B, R5.