# fhirclient: a Python client for SMART on FHIR servers

> fhirclient is the Python client behind the SMART on FHIR ecosystem, with a FHIRServer handle, OAuth login helpers, a search API that can pull referenced resources along with the matches, and generated data model classes that validate as you set them.

**smart-on-fhir/client-py** — Python SMART on FHIR client

- Repository: https://github.com/smart-on-fhir/client-py
- Website: http://docs.smarthealthit.org
- Stars: 697 · Forks: 242
- Language: Python
- License: NOASSERTION
- Published: 2026-10-21 · Updated: 2026-10-21 · Language: en
- Canonical page: https://hysenlabs.com/projects/smart-on-fhir-client-py

## One client, but a version table you have to read carefully

fhirclient is described as a flexible Python client for FHIR servers supporting the SMART on FHIR protocol, and it comes with a warning that client versioning is not identical to FHIR versioning. The table in the project overview maps client versions to FHIR versions, and the mapping is not one to one. Client 4.2.0 sits on FHIR 4.0.1, which is R4, and 4.0.0 on FHIR 4.0.0. The 3.0.0 line is on FHIR 3.0.0, which is STU-3, and the older entries step back through the STU-3 ballots and DSTU 2. Three branches carry different states: the main branch is usually on the latest version of the client and possibly on its bugfix releases, the develop branch should be on recent freezes, and the feature/latest-ci branch is periodically updated to the latest FHIR continuous integration builds. If you pin a version, you are pinning a FHIR generation too, and that is the detail that bites.

## Installing and pointing the client at a server

Installation is a single pip install of the fhirclient package.

```bash
pip install fhirclient
```

The technical documentation lives at docs.smarthealthit.org/client-py. The package metadata is modest in a way worth noting: it requires Python 3.10 or newer and its only runtime dependency is requests, with pytest, pytest-cov and responses as optional test extras, and the build backend is flit. The client is built around two classes. FHIRClient is what you use, and it initialises and handles a FHIRServer instance, which is the actual handle to the server you want to talk to. You can work with FHIRServer directly without FHIRClient, but the project says that is not recommended.

## Reading a patient from a public FHIR server

The first example reads a patient from an open FHIR server using the data model classes.

```python
from fhirclient import client
from fhirclient.models.patient import Patient

settings = {
    'app_id': 'my_web_app',
    'api_base': 'https://r4.smarthealthit.org'
}
smart = client.FHIRClient(settings=settings)

patient = Patient.read('2cda5aad-e409-4070-9a15-e1c35c46ed5a', smart.server)
print(patient.birthDate.isostring)
# '1992-07-03'
print(smart.human_name(patient.name[0]))
# 'Mr. steve Smith'
```

The pattern is worth internalising. The settings map carries the app_id and the api_base, the client is built from it, and resources are read statically through their own class with the server passed in. The smart.human_name helper is on the client rather than the model, which tells you where the convenience methods live: presentation helpers that need the server's knowledge sit on the client, and the resource itself stays a plain object.

## Protected servers need a login first, and two ways to check

If the server is protected, you first have to send your user to the authorization endpoint, and the client exposes the URL you need through smart.authorize_url. There are two ways to find out whether that is necessary, and the project is precise about the difference. smart.prepare() returns False if the server is protected and you need to authorize. The smart.ready property has the same purpose, but it will not retrieve the server's CapabilityStatement resource and hence is only fit as a quick check whether the server instance is ready.

```python
from fhirclient import client

settings = {
    'app_id': 'my_web_app',
    'api_base': 'https://r4.smarthealthit.org'
}
smart = client.FHIRClient(settings=settings)

smart.ready
# prints `False`
smart.prepare()
# prints `True` after fetching CapabilityStatement
smart.ready
# prints `True`
smart.prepare()
# prints `True` immediately
smart.authorize_url
# is `None`
```

The difference matters in practice, because prepare() is the call that fetches the CapabilityStatement and therefore the one that does real work, while ready is a cheap check you can use in a polling loop.

## Search that can drag referenced resources along

Search returns a query object rather than a list, and that is where the useful part is. You build a search from a resource class and a struct of criteria, then iterate the results against the server.

```python
search = Encounter.where(struct={'subject': '2cda5aad-e409-4070-9a15-e1c35c46ed5a', 'status': 'finished'})
print({res.type[0].text for res in search.perform_resources_iter(smart.server)})
# {'Encounter for symptom', 'Encounter for check up (procedure)'}
```

From there the search can pull in the resources the matches refer to. Calling include('subject') brings in the resources referred to via subject, and include('encounter', Procedure, reverse=True) brings in the Procedure resources which refer to the encounter via encounter, since the reverse flag flips the direction of the reference. If you want the raw Bundles rather than parsed resources, perform_iter gives you those instead of perform_resources_iter. In a clinical app this is the difference between one round trip and three, which is why the include mechanism is the part of the search API worth learning first.

## The data model validates as you assign to it

The client ships data model classes built using fhir-parser, and they handle serialisation and let you work with FHIR data in a Pythonic way. From version 1.0.5 the validity of the data model is enforced to a certain extent, and the enforcement is type based.

```python
from fhirclient.models.patient import Patient
from fhirclient.models.humanname import HumanName

patient = Patient({'id': 'patient-1'})
print(patient.id)
# patient-1

name = HumanName()
name.given = ['Peter']
name.family = 'Parker'
patient.name = [name]
print(patient.as_json())
# {'id': 'patient-1', 'name': [{'family': 'Parker', 'given': ['Peter']}], 'resourceType': 'Patient'}

name.given = 'Peter'
print(patient.as_json())
# throws FHIRValidationError:
# {root}:
#   name.0:
#     given:
#       Expecting property "given" on <class 'fhirclient.models.humanname.HumanName'> to be list, but is <class 'str'>
```

Assigning a string where the FHIR definition wants a list of strings raises a FHIRValidationError naming the path, the property and the type it got. That is a better failure mode than producing a resource that the server rejects, and it is the reason to build resources with the model classes rather than by assembling dictionaries and casting them later.

## The Flask demo shows the login flow end to end

The project ships a Flask demo that shows the client in a small application, and it is the fastest way to see the OAuth side work. The demo needs a server capable of SMART OAuth logins for patients, so you need such a server ready first. The app starts a web server, listens on localhost:8000, and prompts you to log in to the sandbox server and select a patient; it then retrieves the selected patient's demographics and med prescriptions and lists them on a simple HTML page. The Flask demo app has separate requirements, so the setup is a clone and an environment of its own, followed by installing from the requirements file and editing flask_app.py to put your own server's URL in as api_base. The steps are git clone of the client-py repository, a change into the demos/flask directory, a virtual environment, activation, pip install -r requirements.txt, and then running flask_app.py. The repository also carries a generate_models.sh script, a fhir-parser directory and a fhir-parser-resources directory, which is where the generated model classes come from.

## Two alternatives the project names, and where each wins

The overview lists two related projects and describes the difference in each case, which is more useful than a bare name. cumulus-fhir-support is a similar network client with more authentication options and built-in retries, but no FHIR classes. fhir.resources has similar FHIR classes, but no network layer. So the choice is along two axes. If your problem is authentication complexity or a flaky network, the network client is the better fit and you can build your own resource handling. If your problem is the resource model, the class library is the better fit and you can write your own transport. fhirclient is the one that has both, which is convenient and also means it is not the sharpest tool for either job alone. The releases tell you where the project stands: 4.4.0 on 2026-02-10, 4.3.2 on 2025-08-01 and 4.3.1 on 2025-03-13, with the last push on 2026-09-24.

## Conclusion

fhirclient fits a Python team building a SMART on FHIR application, because it handles the two things that are tedious about FHIR in Python, namely the OAuth handshake and the shape of a resource, and it does so without a heavy dependency list, since the package itself requires only requests. It does not fit a project that needs a fully typed resource model with no runtime validation, since the project points at fhir.resources as the alternative with similar FHIR classes, or one that wants a client with built-in retries and more authentication options, since that is what cumulus-fhir-support is described as offering instead. Before you adopt it, check the version table, because client versions do not match FHIR versions and the main branch is usually on the latest, decide whether you need the Flask demo path or the library alone, and read the LICENSE file yourself, since the package points at a licence file rather than naming the terms in its metadata.

## FAQ

### How do I install fhirclient and read a patient?

Install it with pip install fhirclient, then create a FHIRClient with settings containing app_id and api_base, and read the resource through its own class, for example Patient.read with the identifier and smart.server. The package requires Python 3.10 or newer and depends on requests at runtime.

### How do I handle a protected SMART on FHIR server?

Send the user to the authorization endpoint, whose URL you get from smart.authorize_url. To check whether authorization is needed, smart.prepare() returns False on a protected server and fetches the CapabilityStatement, while the smart.ready property is only a quick check that does not retrieve the CapabilityStatement.

### Can fhirclient search include referenced resources in the results?

Yes. Calling include('subject') on a search brings in the resources referred to via subject, and include('encounter', Procedure, reverse=True) pulls in the Procedures that refer to the encounter. Use perform_iter instead of perform_resources_iter when you want the raw Bundles.

### How do fhirclient versions relate to FHIR versions?

They are not the same numbering. Client 4.2.0 targets FHIR 4.0.1, which is R4, and 3.0.0 targets FHIR 3.0.0, which is STU-3, with older client lines mapping back to the STU-3 ballots and DSTU 2. The main branch is usually on the latest client version, develop on recent freezes, and feature/latest-ci on FHIR continuous integration builds.

## Sources

- [Issues](https://github.com/smart-on-fhir/client-py/issues)
- [Project website](http://docs.smarthealthit.org)
- [README](https://github.com/smart-on-fhir/client-py/blob/main/README.md)
- [Releases](https://github.com/smart-on-fhir/client-py/releases)
- [smart-on-fhir/client-py on GitHub](https://github.com/smart-on-fhir/client-py)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/smart-on-fhir-client-py
