# Patients V1 — Developer Guide

> **Reference:** [Patients (V1) Endpoint Documentation](https://papidocs.hs1api.com/publicapi/api-patient-management/patients-(v1))


Patient records are the core identity for scheduling, clinical, and billing integrations. This guide covers the published **Patients V1** operations: bulk list, get by id, create, update, calculated fields, and total count.

For **`GET /v1/patients/async`**, see **[Async Endpoints V1 — Developer Guide](/publicapi/endpoints/async-v1)**. Async returns the first 10 results immediately and streams the full result set to the Streaming API. Results on the async endpoint are ordered by **`id` descending** (newest first), unlike the synchronous bulk GET which pages by ascending `id`.

## GET — Bulk List Patients

Returns a paginated list of patients matching the filter criteria.

> **⚠️ Rate Limited:** This endpoint is rate limited. See the [rate limiting guide](https://papidocs.hs1api.com/publicapi/api-consumer-guide#rate-limiting) for details.


### Parameters

| Parameter | In | Required | Type | Description |
|  --- | --- | --- | --- | --- |
| `Organization-ID` | header | Yes | string | The organization ID |
| `filter` | query | No | string | Filter criteria (see filter keys below) |
| `responseFields` | query | No | string | URL-encoded comma-delimited list of fields to include, or `ALL` |
| `page` | query | No | string | Page number for pagination (**deprecated**) |
| `pageSize` | query | No | string | Number of records per page (max `500`) |
| `lastId` | query | No | string | Largest `patientId` from the previous page — used for cursor-based pagination |


### Filter Keys

| Filter Key | Operators | Example |
|  --- | --- | --- |
| `id` | `->` | `?filter=id->[12000006542062,12000006542063]` |
| `firstName` | `==`, `!=`, `~=`, `<>=` | `?filter=firstName~=Smi` |
| `lastName` | `==`, `!=`, `~=`, `<>=` | `?filter=lastName==Doe` |
| `gender` | `==`, `!=` | `?filter=gender==M` |
| `preferredName` | `==`, `!=`, `~=`, `<>=` | `?filter=preferredName~=John` |
| `dateOfBirth` | `==`, `!=` | `?filter=dateOfBirth==1985-08-25` |
| `patientStatus` | `==`, `!=` | `?filter=patientStatus==ACTIVE` |
| `phones.number` | `==`, `!=`, `->` | `?filter=phones.number==5551234567` |
| `emailAddress` | `==` | `?filter=emailAddress==john.doe@example.com` |
| `chartNumber` | `==`, `.absent`, `.present` | `?filter=chartNumber==CH10023456` |
| `preferredLocation.id` | `==`, `!=`, `->` | `?filter=preferredLocation.id->[64000000000004]` |
| `primaryGuarantor.id` | `==` | `?filter=primaryGuarantor.id==12000006542062` |
| `duplicateOfPatient.id` | `==` | `?filter=duplicateOfPatient.id==12000006542062` |
| `primaryContact.id` | `==` | `?filter=primaryContact.id==12000006542062` |
| `firstVisitDate` | `==`, `>`, `>=`, `<`, `<=` | `?filter=firstVisitDate>=2024-01-01` |
| `lastModified` | `>`, `>=`, `<`, `<=` | `?filter=lastModified>=2025-01-01T00:00:00Z` |
| `referredByPatient.id` | `==`, `!=`, `->` | `?filter=referredByPatient.id==12000006542062` |
| `referredByReferral.id` | `==`, `!=`, `->` | `?filter=referredByReferral.id==500` |


#### `patientStatus` Values

`NEW`, `ACTIVE`, `NON-PATIENT`, `INACTIVE`, `DUPLICATE`, `DISMISSED`, `DECEASED`

#### `gender` Values

`M`, `F`, `O`

#### `preferredLocation.id` — All Locations

Use a specific location id, a list via `->`, or all locations with `ALL`, `*`, `->["ALL"]`, or `->["*"]`.

### Important Notes

> **⚠️ Upcoming Breaking Change:** `preferredLocation.id` (as a list of IDs or `["ALL"]`) will become a **required** filter. Omitting `preferredLocation.id` currently defaults to all locations during a transitional period.


> **⚠️ Date-limiting filters:** If you do not supply a date-limiting filter, one may be applied automatically using `lastModified`. Supply your own date bound for predictable results. The following filter keys count as date-limiting: **`lastModified`**, **`createdDate`**, and **`updatedDate`**.


> **⚠️ Narrow your query:** A very wide `lastModified` range with no other scoping can return **408** (timeout) on large tenants. Combine name, chart, contact, or location filters with a date bound, or use `lastId` pagination.


> **⚠️ Not Recommended:** Do **not** paginate large data sets using the `page` parameter. Use **`lastId`** cursor pagination instead.


> **Empty results:** **200** with `data: []` means no patients matched; it does not indicate invalid filter values.


### Example Requests

Fetch patients modified after a date at specific locations:

```
GET /api/v1/patients?pageSize=500&filter=lastModified>=2025-01-01T00:00:00Z,preferredLocation.id->[64000000000004]
```

Fetch patients for a primary guarantor:

```
GET /api/v1/patients?filter=primaryGuarantor.id==12000006542062,lastModified>=2025-01-01T00:00:00Z
```

## Paginating with `lastId`

`lastId` uses the patient's `id` as a cursor. When filtering by `lastModified`, `firstVisitDate`, or `dateOfBirth`, combine those filters with `lastId` for stable pagination. Results are ordered by `id` for paging.

### Step 1 — Initial request

```
GET /api/v1/patients?pageSize=500&filter=lastModified>=2025-01-01T00:00:00Z,preferredLocation.id->[64000000000004]
```

### Step 2 — Subsequent requests

Pass the `id` of the **last** record in `data` as `lastId`:

```
GET /api/v1/patients?lastId=22000012345678&pageSize=500&filter=lastModified>=2025-01-01T00:00:00Z,preferredLocation.id->[64000000000004]
```

### Step 3 — Repeat

Continue until `data` is empty.

For how `meta.pagination.total` behaves (including when it is `0` or omitted), see **[Bulk GET — Total Count Behavior](/publicapi/bulk-get-total-count)**.

## GET — Patient by ID

Returns a single patient by `patientId`.

### Parameters

| Parameter | In | Required | Type | Description |
|  --- | --- | --- | --- | --- |
| `patientId` | path | Yes | integer | The patient ID |
| `Organization-ID` | header | Yes | string | The organization ID |
| `responseFields` | query | No | string | Comma-delimited list of fields to include, or `ALL` |


### Example Request

```
GET /api/v1/patients/12000006542062?responseFields=firstName,lastName,chartNumber
```

## POST — Create Patient

Creates a new patient. Returns **201** with the created resource in `data`.

### Required Fields

| Field | Type | Description |
|  --- | --- | --- |
| `firstName` | string | 1–25 characters |
| `lastName` | string | 1–25 characters |
| `contactMethod` | string | `Call me`, `Text me`, or `Email me` |
| `languageType` | string | See schema enum (e.g. `English`, `Spanish`) |
| `patientStatus` | string | See [`patientStatus` values](#patientstatus-values) |
| `gender` | string | `M`, `F`, or `O` |
| `dateOfBirth` | string | ISO date `YYYY-MM-DD` |
| `preferredLocation` | object | Linked model reference to a location |
| `address1` | string | 1–50 characters |
| `city` | string | 1–30 characters |
| `state` | string | US state/territory code |
| `postalCode` | string | 5–10 characters |


### Conditional Fields

| Condition | Required |
|  --- | --- |
| `discountType: BASIC` | `discountPlan` |
| `discountType: SLIDING_FEES` | `income` and `familySize` |


### Example Request Body

```json
{
  "firstName": "Jane",
  "lastName": "Smith",
  "contactMethod": "Email me",
  "languageType": "English",
  "patientStatus": "ACTIVE",
  "gender": "F",
  "dateOfBirth": "1990-03-15",
  "preferredLocation": { "id": "64000000000004" },
  "address1": "123 Maple Street",
  "city": "Anytown",
  "state": "CA",
  "postalCode": "90210",
  "emailAddress": "jane.smith@example.com",
  "phones": [
    {
      "phoneType": "MOBILE",
      "number": "5551234567",
      "sequence": 1
    }
  ]
}
```

## PUT — Update Patient

Updates an existing patient. Only fields included in the request body are changed; omitted fields are unchanged.

### Parameters

| Parameter | In | Required | Type | Description |
|  --- | --- | --- | --- | --- |
| `patientId` | path | Yes | integer | The patient ID |
| `Organization-ID` | header | Yes | string | The organization ID |
| `responseFields` | query | No | string | Comma-delimited list of fields to include in the response, or `ALL` |


### Example Request

```
PUT /api/v1/patients/12000006542062
```

```json
{
  "patientStatus": "INACTIVE",
  "emailAddress": "jane.smith.new@example.com"
}
```

## GET — Calculated Fields

`GET /api/v1/patients/calculated` returns computed fields per patient row.

Both **`filter`** and **`responseFields`** are **required**.

### Filter Keys

| Filter Key | Operators | Example |
|  --- | --- | --- |
| `patient.id` | `==`, `->` | `?filter=patient.id==12000006542062` |


### Supported Calculated Fields

| Field | Description |
|  --- | --- |
| `lastVisitDate` | Most recent visit date derived from appointments and patient procedures |


### Example Request

```
GET /api/v1/patients/calculated?filter=patient.id->[12000006542062]&responseFields=lastVisitDate
```

## GET — Total Count

```
GET /api/v1/patients/totalcount
```

Uses the **same filter syntax and filter keys** as bulk GET. See **[Bulk GET — Total Count Behavior](/publicapi/bulk-get-total-count)** for how `pagination.total` behaves on bulk GET responses.

### Example Request

```
GET /api/v1/patients/totalcount?filter=lastModified>=2025-01-01,preferredLocation.id==64000000000004
```

## Patient Billing Types

Patient billing types are managed on a nested path under the patient:

- **`GET /api/v1/patients/{patientId}/billingtypes`** — list billing types for a patient
- **`POST /api/v1/patients/{patientId}/billingtypes`** — replace all billing types for the patient (existing types are removed and replaced by the request body)


See the official API reference under **Patient Billing Types** for schema details.

## Streaming API

For real-time patient creates and updates, use the **Streaming API** instead of polling bulk GET on a fixed schedule. Setup is covered in **[Creating and Setting Up Webhooks for the StreamAPI](/publicapi/streamapi-webhooks-setup)**.

Patients are streamable under domain type **`PatientV1`**.

## Response Structure

A successful `200`/`201` response returns:

| Field | Type | Description |
|  --- | --- | --- |
| `data` | object or array | Patient record(s) |
| `warnings` | array | Non-fatal warnings |
| `errors` | array | Field-level or structural errors |


For bulk GET, `data` is an array and includes `meta.pagination`.

### `meta.pagination`

| Field | Type | Description |
|  --- | --- | --- |
| `limit` | integer | Page size used for this request |
| `offset` | integer | Record offset applied |
| `total` | integer | Present only on plain bulk GET without `lastId` or `page` — typically `0` (see [Bulk GET — Total Count Behavior](/publicapi/bulk-get-total-count)) |


### `PatientV1` Object (summary)

| Field | Access | Description |
|  --- | --- | --- |
| `id` | read-only | Unique patient identifier |
| `firstName`, `lastName`, `preferredName` | read/write | Name fields |
| `gender`, `dateOfBirth`, `patientStatus` | read/write | Demographics and status |
| `chartNumber`, `emailAddress` | read/write | Chart and contact |
| `phones` | read/write | Array of phone records |
| `preferredLocation` | read/write | Linked location |
| `primaryGuarantor`, `secondaryGuarantor` | read/write | Linked patient guarantors |
| `primaryContact`, `secondaryContact` | read/write | Linked patient contacts |
| `address1`, `address2`, `city`, `state`, `postalCode` | read/write | Address |
| `firstVisitDate`, `lastModified` | read-only | Timestamps |
| `referredByPatient`, `referredByReferral` | read/write | Referral links |
| `referredPatients` | read-only | Patients referred by this patient |


> For the authoritative field list, enums, and validation rules, use the **`PatientV1`** and **`PatientUpdateV1`** schemas in the official API reference.


## Errors & Warnings Reference

details
summary
strong
HTTP Status Code Errors
— click to expand
These apply to all Patients V1 endpoints.

#### `400` — Bad Request

The request body or query parameters are invalid or cannot be processed. Common causes include invalid `filter` syntax, missing required fields on POST, or missing `filter` / `responseFields` on `/calculated`.

#### `401` — Unauthorized

|  |  |
|  --- | --- |
| **Message** | Authentication is required and has failed or has not been provided. |
| **Fix** | Provide a valid Bearer token in the `Authorization` header. |


#### `403` — Forbidden

|  |  |
|  --- | --- |
| **Message** | The request is understood, but it has been refused or access is not allowed. |
| **Why** | Token lacks `model:read` (GET) or `model:write` (POST/PUT) scope. |
| **Fix** | Request the appropriate scope for the operation. |


#### `404` — Not Found

|  |  |
|  --- | --- |
| **Message** | The requested resource is either missing or does not exist. |
| **Why** | No patient exists for the given `patientId` in your organization, or the id is invalid. |
| **Fix** | Confirm the `patientId` and organization context. |


#### `408` — Request Timeout

|  |  |
|  --- | --- |
| **Message** | The server timed out while processing the request. |
| **Why** | Query too broad — often a wide date range without location or name scoping. |
| **Fix** | Retry with a narrower `filter` or smaller `pageSize`. |


#### `429` — Too Many Requests

|  |  |
|  --- | --- |
| **Message** | Rate limit exceeded. |
| **Fix** | Implement exponential backoff. See the [rate limiting guide](https://papidocs.hs1api.com/publicapi/api-consumer-guide#rate-limiting). |


#### `500` — Internal Server Error

|  |  |
|  --- | --- |
| **Message** | An unexpected error occurred. |
| **Fix** | Retry. If the issue persists, contact support. |


details
summary
strong
Field-Level Validation
— click to expand
Validation errors are returned in the **`errors`** array on **`400`** responses. Common cases on create/update include:

- Missing required fields (`firstName`, `lastName`, `gender`, `dateOfBirth`, address fields, etc.)
- Invalid `patientStatus`, `gender`, `contactMethod`, or `languageType` enum values
- `discountType: BASIC` without `discountPlan`, or `discountType: SLIDING_FEES` without `income` / `familySize`
- Duplicate phone `sequence` values within the same `phoneType`
- Invalid `dateOfBirth` format (must be `YYYY-MM-DD`)