Learn · PatientNow Pro
Guides & concepts — PatientNow Pro
The conventions that hold across every PNP resource. Learn these once and the whole API becomes predictable.
PatientNow Pro has a different host, auth scheme, field casing, and ID format from PNE and VISH. If you're on PNE or VISH, see Guides & concepts — PNE & VISH → instead.
Request basics
- Protocol: REST over HTTPS.
- Base URL:
https://backend-production.patientnow.net/backend/patientnowpro - Format: JSON request and response bodies. Send
Content-Type: application/jsononPOSTandPUT. - Verbs:
GETreads,POSTcreates,PUTupdates. - Auth: a single
Authorization: YOUR_TOKENheader on every request — no gatewayapikeyparameter. See Authentication — PatientNow Pro. - Field naming: JSON properties are
camelCase(e.g.firstName,patientId,apptDate). This differs from PNE/VISH which usePascalCase. - Path scoping: every endpoint is prefixed with
/practices/{practiceId}/— see Practice scoping.
Pagination
All list endpoints use two query parameters:
| Parameter | Type | Default | Meaning |
|---|---|---|---|
Page | integer | 1 | 1-based page number. |
Limit | integer | varies | Records per page. |
Walk a collection by incrementing Page until a page returns fewer
records than Limit (or an empty data array):
def iter_patients(session, base, practice_id, limit=50, **filters):
page = 1
while True:
res = session.get(
f"{base}/practices/{practice_id}/patients",
params={"Page": page, "Limit": limit, **filters},
)
res.raise_for_status()
batch = res.json().get("data", [])
if not batch:
return
yield from batch
if len(batch) < limit:
return
page += 1async function* iterPatients(base, headers, practiceId, limit = 50, filters = {}) {
let page = 1;
for (;;) {
const qs = new URLSearchParams({ Page: page, Limit: limit, ...filters });
const res = await fetch(`${base}/practices/${practiceId}/patients?${qs}`, { headers });
const { data = [] } = await res.json();
if (!data.length) return;
yield* data;
if (data.length < limit) return;
page++;
}
}PNP list endpoints wrap results in a { "data": [...] } envelope
rather than returning a bare array. Always read from .data, and
guard for an empty or missing array when no records match.
Filtering & search
Common filter parameters available on list endpoints:
| Parameter | Applies to | Example |
|---|---|---|
LastName, FirstName, DOB, CellPhone | Patients | ?LastName=Smith |
MatchExactName | Patients | ?MatchExactName=true — exact match instead of prefix |
LastModified | Patients | ISO datetime — incremental sync |
ApptDate | Appointments | ?ApptDate=2026-08-01 |
IncludeCancelled, IncludeNoShow, IncludeDeleted | Appointments | Boolean flags to widen results |
LastModified
To keep a local copy of patients in sync, store the timestamp of your last run
and pass it as LastModified to pull only records changed since then.
Combine with pagination to page through the deltas.
Resource IDs
PNP returns plain integer IDs for all resources — patients, appointments, providers, event types, and so on. They appear directly in path parameters and response bodies:
// A patient response — IDs are integers:
{ "patientId": 12345, "firstName": "Jane", "lastName": "Smith", ... }
// Use the integer directly in the next call:
GET /practices/YOUR_PRACTICE_ID/patients/12345/appointments- Store them as integers in your system — no need to treat them as opaque strings.
- They're stable for a given record and safe to use as foreign keys.
PNE and VISH return opaque encrypted strings for IDs. If you're building an integration that touches both platforms, keep the ID types separate — don't mix integer PNP IDs with encrypted PNE/VISH IDs.
Dates & times
- Date and date-time fields are ISO 8601 strings. Filter parameters that accept
dates (e.g.
ApptDate,Start,End) acceptYYYY-MM-DDor a full datetime string. - Nullable date fields may be
nullwhen unset — guard for that in your client. - The
LastModifiedfilter accepts an ISO datetime with optional timezone offset; pass UTC for consistent results across environments.
Practice scoping
Every PNP endpoint is rooted under a practice:
https://backend-production.patientnow.net/backend/patientnowpro/practices/{practiceId}/...Your practiceId is a fixed integer assigned to your practice. All
data — patients, appointments, providers, event types — is scoped to it. There is
no cross-practice access from a single token.
| Resource | Path pattern |
|---|---|
| Patients | /practices/{practiceId}/patients |
| Appointments | /practices/{practiceId}/appointments |
| Providers | /practices/{practiceId}/providers |
| Event types | /practices/{practiceId}/event-types |
| Availability slots | /practices/{practiceId}/scheduling/availability/slots |
| Waitlist | /practices/{practiceId}/waitlist |
Errors & status codes
PNP uses standard HTTP status codes. The status line is the primary signal; a JSON problem-details body may accompany errors.
| Code | Meaning | What to do |
|---|---|---|
200 OK | Successful read or update. | Parse the body. |
201 Created | Successful create. | Read the new record's ID from the response body. |
400 Bad Request | Validation failed — missing or invalid fields. | Check the error body for field-level detail; fix the payload. |
401 Unauthorized | Missing or invalid token. | See 401/403 causes. |
403 Forbidden | Token valid but no access to this practice or resource. | Confirm your token is scoped to the correct practice ID. |
404 Not Found | No such record, or not visible to this token. | Re-check the ID and practice scope. |
500 Server Error | Unexpected server-side failure. | Retry with backoff; if it persists, contact support. |
Always branch on the status code before parsing. A non-2xx response may have an
empty or non-JSON body. Check for both 401 and 403 —
PNP distinguishes unauthenticated (401) from unauthorized (403) more consistently
than PNE/VISH does.