Elixir client for Medplum's FHIR API.

Built for apps that want:

  • direct CRUD on FHIR resources
  • resource-specific helpers under Medplum.Resources.*
  • workflow helpers under Medplum.Workflows.*
  • token reuse across requests
  • stable {:error, %Medplum.Error{}} returns
  • lower-level escape hatches for operations, batch, transaction, binary, upsert, GraphQL, and raw requests

Installation

Add package to mix.exs:

def deps do
  [
    {:medplum_elixir, "~> 0.2.0"}
  ]
end

Then fetch deps:

mix deps.get

Quick start

Medplum uses OAuth client credentials. Set env vars first:

export MEDPLUM_BASE_URL="https://api.medplum.com"
export MEDPLUM_CLIENT_ID="..."
export MEDPLUM_CLIENT_SECRET="..."

Create reusable client:

client =
  Medplum.new(
    base_url: System.fetch_env!("MEDPLUM_BASE_URL"),
    client_id: System.fetch_env!("MEDPLUM_CLIENT_ID"),
    client_secret: System.fetch_env!("MEDPLUM_CLIENT_SECRET")
  )

Read resource:

{:ok, patient} = Medplum.read(client, "Patient", "123")

Search resource:

{:ok, bundle} = Medplum.search(client, "Patient", %{"family" => "Smith"})

Patch resource:

ops = [%{"op" => "replace", "path" => "/active", "value" => true}]
{:ok, patient} = Medplum.patch(client, "Patient", "123", ops)

Stream paged search results:

entries =
  client
  |> Medplum.stream_search("Patient", %{"family" => "Smith"})
  |> Enum.to_list()

Client options

Medplum.new/1 accepts:

  • base_url - Medplum server URL, trailing slash removed automatically
  • client_id - OAuth client id
  • client_secret - OAuth client secret
  • fhir_version - defaults to "R4"
  • default_headers - merged into every request
  • req_options - forwarded to Req
  • auth_req_options - forwarded only to token requests
  • retry - false, :safe_transient, or :transient
  • max_retries - defaults to 2
  • token_refresh_skew - defaults to 60
  • cache_tokens - defaults to true

Example with request tuning:

client =
  Medplum.new(
    base_url: System.fetch_env!("MEDPLUM_BASE_URL"),
    client_id: System.fetch_env!("MEDPLUM_CLIENT_ID"),
    client_secret: System.fetch_env!("MEDPLUM_CLIENT_SECRET"),
    default_headers: [{"x-app-name", "my_app"}],
    req_options: [receive_timeout: 15_000],
    max_retries: 2,
    token_refresh_skew: 60
  )

Core API

Main module covers generic FHIR and Medplum operations:

  • read/3
  • search/3
  • create/3
  • update/4
  • delete/3
  • patch/4
  • request/4
  • operation/5
  • poll_async/3
  • batch/2
  • transaction/2
  • create_binary/3
  • get_binary/2
  • upsert/3
  • graphql/3
  • stream_search/3

Example:

{:ok, bundle} =
  Medplum.batch(client, [
    %{"request" => %{"method" => "GET", "url" => "Patient/123"}}
  ])

{:ok, graphql_result} =
  Medplum.graphql(client, "query Demo { PatientList { id } }")

Resource helpers

Thin wrappers live under Medplum.Resources.*.

Common modules:

  • Patient
  • Appointment
  • Encounter
  • Task
  • Observation
  • Condition
  • DocumentReference
  • ServiceRequest
  • DiagnosticReport
  • Claim
  • Coverage
  • Organization
  • Practitioner
  • PractitionerRole
  • Schedule
  • Slot
  • Location
  • Bot
  • Subscription

Example:

alias Medplum.Resources.Patient
alias Medplum.Resources.Task

{:ok, patient} = Patient.get(client, "123")

{:ok, matches} =
  Patient.search_by_identifier(client, "http://hospital.example/mrn", "123")

{:ok, tasks} =
  Task.search_by_owner(client, "Practitioner/dr-1", %{"status" => "requested"})

Many resource modules also expose identifier-based upsert helpers:

{:ok, patient} =
  Patient.upsert(client, %{
    "identifier" => [%{"system" => "http://hospital.example/mrn", "value" => "123"}],
    "name" => [%{"family" => "Smith"}]
  })

Workflow helpers

Higher-level modules live under Medplum.Workflows.*.

Available workflows:

  • Scheduling
  • Billing
  • Automation
  • Medications
  • CareCoordination
  • CarePlanning
  • Tasks
  • Intake
  • Orders
  • Charting

Example:

alias Medplum.Workflows.Automation
alias Medplum.Workflows.Billing
alias Medplum.Workflows.CarePlanning
alias Medplum.Workflows.CareCoordination
alias Medplum.Workflows.Charting
alias Medplum.Workflows.Intake
alias Medplum.Workflows.Medications
alias Medplum.Workflows.Orders
alias Medplum.Workflows.Scheduling
alias Medplum.Workflows.Tasks

{:ok, open_tasks} =
  Tasks.list_for_owner(client, "Practitioner/dr-1", %{"status" => "requested"})

{:ok, appointments} =
  Scheduling.find_appointments(client, %{
    "resourceType" => "Parameters",
    "parameter" => [%{"name" => "start", "valueDateTime" => "2026-07-04T09:00:00Z"}]
  })

{:ok, service_requests} =
  Orders.service_requests_for_subject(client, "Patient/123")

{:ok, claim_pdf} =
  Billing.export_claim(client, "claim-123")

{:ok, bot_result} =
  Automation.execute_bot(client, "bot-123", %{
    "resourceType" => "Patient",
    "id" => "123"
  })

{:ok, active_meds} =
  Medications.active_medication_requests_for_subject(client, "Patient/123")

{:ok, care_goals} =
  CarePlanning.active_goals_for_subject(client, "Patient/123")

{:ok, family_contacts} =
  CareCoordination.active_family_for_patient(client, "Patient/123")

{:ok, observations} =
  Charting.observations_for_patient(client, "Patient/123", %{"category" => "vital-signs"})

{:ok, responses} =
  Intake.questionnaire_responses_for_subject(client, "Patient/123")

Error handling

All public functions return either success tuple or %Medplum.Error{}:

case Medplum.read(client, "Patient", "missing-id") do
  {:ok, patient} ->
    patient

  {:error, %Medplum.Error{type: :api_error, status: 404}} ->
    nil

  {:error, %Medplum.Error{} = error} ->
    raise error
end

Error types:

  • :config_error
  • :auth_failed
  • :request_failed
  • :api_error
  • :invalid_response

Phoenix config

# config/runtime.exs
config :my_app, :medplum,
  base_url: System.fetch_env!("MEDPLUM_BASE_URL"),
  client_id: System.fetch_env!("MEDPLUM_CLIENT_ID"),
  client_secret: System.fetch_env!("MEDPLUM_CLIENT_SECRET")
client =
  :my_app
  |> Application.fetch_env!(:medplum)
  |> Medplum.new()

Docs

  • API docs generated with mix docs
  • ExDoc extras:
    • README.md
    • guides/getting-started.md
    • guides/resource-and-workflow-helpers.md

Development

Run tests:

mix test

Build docs:

mix docs