openapi: 3.2.0 info: title: JDP Patients API description: 'Open API specification for the JDP API ## Rate Limits All JDP API endpoints have a rate limit of **100 requests per minute** per endpoint per clinic. Rate limit information is returned in response headers: - `X-Throttle-Match`: Rate limit identifier - `Retry-After`: Number of seconds to wait before retrying (when rate limited) When rate limits are exceeded, the API returns a 429 status code with a `Retry-After` header. ### Affected Endpoints: - `/api/2026-01-01/treatments` - `/api/2026-01-01/appointments` - `/api/2026-01-01/locations` - `/api/2026-01-01/disciplines` - `/api/2026-01-01/patients` - `/api/2026-01-01/staff_members` - `/api/2026-01-01/company` - `/api/2026-01-01/medical-record/medications`' version: '2026-01-01' servers: - url: https://jdpdocsdemo.jane.qa description: Partner playground clinic security: - OAuth2: [] tags: - name: Patients paths: /api/2026-01-01/patients/{patient_id}: get: operationId: getPatient summary: Get a Patient description: Retrieve detailed patient information using their unique identifier tags: - Patients security: - OAuth2: - patients:read parameters: - name: patient_id in: path required: true description: The ID of the patient schema: type: string format: uuid responses: '200': description: The patient content: application/json: schema: $ref: '#/components/schemas/patient-2' '400': description: The request was invalid content: application/json: schema: $ref: '#/components/schemas/error' '401': description: The request is not authorized content: application/json: schema: $ref: '#/components/schemas/unauthorized_error' '404': description: The patient was not found content: application/json: schema: $ref: '#/components/schemas/not_found_error' '429': description: Rate limit exceeded - 100 requests per minute per endpoint per clinic headers: X-Throttle-Match: description: Rate limit identifier schema: type: string example: marketing_api/ip/clinic Retry-After: description: Number of seconds to wait before retrying schema: type: integer example: 60 content: application/json: schema: $ref: '#/components/schemas/errors' /api/2026-01-01/patients: get: operationId: getPatients summary: Get a list of patients description: 'Retrieve a list of patients. Filtering: Filter records by field values using the format: field[operator]=value Available fields: - `public_id`: string - `created_at`: ISO 8601 datetime (e.g. 2025-01-01T12:00:00Z) - `updated_at`: ISO 8601 datetime Available operators: - `eq`: Equals - `gt`: Greater than - `gte`: Greater than or equal - `lt`: Less than - `lte`: Less than or equal Date-time filters (ex. `created_at`, `updated_at`) must: - Use [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601) date-time format - Be **precise to the second** (e.g. `2025-01-01T12:00:00Z`) Free-text search across name, email, phone, and patient number is available via `POST /patients/search` (PII is not supported in query strings). Examples: ``` /patients?public_id[eq]=abc123 /patients?created_at[gte]=2025-01-01T00:00:00Z&created_at[lt]=2025-01-02T00:00:00Z ``` ' tags: - Patients security: - OAuth2: - patients:read parameters: - $ref: '#/components/parameters/page_cursor' - $ref: '#/components/parameters/page_limit' - name: sort in: query required: false description: 'Comma-separated list of fields to sort by. Prefix a field with ''-'' for descending order. Examples: - `sort=updated_at,-public_id` sorts by `updated_at` ascending, then `public_id` descending. - `sort=-created_at` sorts by `created_at` descending. Available fields: - `public_id` - `created_at` - `updated_at` ' schema: type: string - name: public_id in: query style: deepObject explode: true required: false description: 'Filter by `public_id`. Example: `public_id[eq]=123`' schema: $ref: '#/components/schemas/StringFilterSchema' - name: created_at in: query style: deepObject explode: true required: false description: 'Filter by `created_at` timestamp (ISO 8601). Example: `created_at[gte]=2025-01-01T00:00:00Z`' schema: $ref: '#/components/schemas/DateTimeFilterSchema' - name: updated_at in: query style: deepObject explode: true required: false description: 'Filter by `updated_at` timestamp (ISO 8601). Example: `updated_at[lt]=2025-01-02T00:00:00Z`' schema: $ref: '#/components/schemas/DateTimeFilterSchema' responses: '200': description: The paginated list of patients content: application/json: schema: $ref: '#/components/schemas/patients' '400': description: The request was invalid content: application/json: schema: $ref: '#/components/schemas/error' '401': description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/error' '429': description: Rate limit exceeded - 100 requests per minute per endpoint per clinic headers: X-Throttle-Match: description: Rate limit identifier schema: type: string example: marketing_api/ip/clinic Retry-After: description: Number of seconds to wait before retrying schema: type: integer example: 60 content: application/json: schema: $ref: '#/components/schemas/errors' /api/2026-01-01/patients/search: post: operationId: searchPatients summary: Search patients by free text description: "Free-text patient search across name, email, phone number, and patient number.\n\nThis is a POST-body endpoint specifically so PII (patient names, email, phone)\ndoes not leak into URL query strings, access logs, browser history, or referrer headers.\n\nScope is intentionally search-only: the body accepts `search`, `sort`, and `page`.\nStructured filters (`public_id`, `created_at`, `updated_at`) are not supported here —\nuse `GET /patients` for those. Combined search + structured filtering is a deliberate\nfollow-up if a consumer need materializes.\n\nFilter operators:\n- `search`: `co` (contains) only\n\nPagination: the response returns a `cursor` and `hasNextPage`. To fetch\nsubsequent pages, re-POST the same body with `page.cursor` set to the\nprevious response's `cursor`. The `links` object is omitted from this\nendpoint's response (GET-style pagination URLs would re-expose filter PII\nin query strings).\n\nExample body:\n```json\n{\n \"search\": { \"co\": \"Jane Smith\" },\n \"sort\": \"-created_at\",\n \"page\": { \"limit\": 50 }\n}\n```\n" tags: - Patients security: - OAuth2: - patients:read requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/patients_search_request' responses: '200': description: The paginated list of matching patients content: application/json: schema: $ref: '#/components/schemas/patients_search_response' '400': description: The request was invalid content: application/json: schema: $ref: '#/components/schemas/error' '401': description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/error' '429': description: Rate limit exceeded - 100 requests per minute per endpoint per clinic headers: X-Throttle-Match: description: Rate limit identifier schema: type: string example: marketing_api/ip/clinic Retry-After: description: Number of seconds to wait before retrying schema: type: integer example: 60 content: application/json: schema: $ref: '#/components/schemas/errors' components: schemas: patients_search_response: $schema: https://json-schema.org/draft/2020-12/schema $id: /domains/patient/schemas/patients_search_response.json title: PatientsSearchResponse description: 'Paginated list of matching patients from POST /patients/search. Omits the `links` object that `GET /patients` returns: GET-style pagination URLs would re-serialize search terms into query strings and re-expose PII, which is the whole reason search moved to a POST body. Clients paginate by re-POSTing the same body with `page.cursor` set to the previous response''s `cursor`.' type: object properties: items: type: array items: $ref: '#/components/schemas/patient-2' cursor: description: The Base64 encoded cursor for the next page. null if this is the last page. type: - string - 'null' example: eyJwYWdlIjozLCJzaXplIjo1fQ== limit: type: integer hasNextPage: description: Whether there is a next page of patients type: boolean DateTimeFilterSchema: type: object properties: eq: type: string format: date-time description: 'equals. Multiple equal values are comma-separated: ?field[eq]=value1,value2' ne: type: string format: date-time description: not equals gt: type: string format: date-time description: greater than gte: type: string format: date-time description: greater than or equal to lt: type: string format: date-time description: less than lte: type: string format: date-time description: less than or equal to patients_search_request: $schema: https://json-schema.org/draft/2020-12/schema $id: /domains/patient/schemas/patients_search_request.json title: PatientsSearchRequest description: Request body for POST /patients/search. Free-text search across patient name, email, phone, and patient number, carried in the body so PII does not leak into URLs. Only the `search` field is accepted at this endpoint — structured filtering (public_id, created_at, updated_at) belongs on GET /patients. Combined search + structured filtering is a deliberate follow-up if a consumer need materializes. type: object additionalProperties: false required: - search properties: search: type: object description: Free-text search across patient name, email, phone number, and patient number. Results follow the requested `sort` order (or default ordering when no `sort` is specified). additionalProperties: false properties: co: type: string maxLength: 255 description: Contains-match search term. Maximum 255 characters. sort: type: string description: 'Comma-separated list of fields to sort by. Prefix a field with ''-'' for descending order. Example: `-created_at,updated_at`.' page: type: object description: Cursor-based pagination controls. additionalProperties: false properties: cursor: type: string limit: type: integer minimum: 1 maximum: 1000 patients: $schema: https://json-schema.org/draft/2020-12/schema $id: /domains/patient/schemas/patients.json title: PatientsList description: A list of patients. type: object properties: items: type: array items: $ref: '#/components/schemas/patient-2' cursor: description: The Base64 encoded cursor for the next page. null if this is the last page. type: - string - 'null' example: eyJwYWdlIjozLCJzaXplIjo1fQ== limit: type: integer links: type: object properties: after: type: - string - 'null' description: URL for the next page of results. null if this is the last page. example: /api/2026-01-01/patients?page[cursor]=eyJwYWdlIjozLCJzaXplIjo1fQ==&page[limit]=5 hasNextPage: description: Whether there is a next page of patients type: boolean StringFilterSchema: type: object properties: eq: type: string description: 'equals. Multiple equal values are comma-separated: ?field[eq]=value1,value2 (maximum 100 values)' ne: type: string description: not equals co: type: string description: 'contains. Performs partial match (case-insensitive for string fields). Example: ?field[co]=search_term' gt: type: string description: greater than gte: type: string description: greater than or equal to lt: type: string description: less than lte: type: string description: less than or equal to sw: type: string description: 'starts with. Performs case-insensitive partial match for words in a string that begin with search term. Example: ?field[sw]=search_term' errors: $schema: https://json-schema.org/draft/2020-12/schema $id: /components/responses/schemas/errors.json type: object required: - object - message title: Error description: Error response generated when an operations fails properties: object: type: string description: The string `error` code: type: string description: Indicates the granularity of the error message: type: string description: Indicates what the error is about not_found_error: $schema: https://json-schema.org/draft/2020-12/schema $id: /components/responses/schemas/not_found_error.json type: object required: - error title: NotFoundError description: Error response when the requested resource is not found. properties: error: type: string description: Error message. patient-2: $schema: https://json-schema.org/draft/2020-12/schema $id: /domains/patient/schemas/patient-2.json title: PatientRecord description: A patient record. type: object properties: id: description: Unique identifier for the patient type: string format: uuid first_name: description: Patient's first name type: string middle_name: description: Patient's middle name type: string last_name: description: Patient's last name type: string prefix: description: Patient's name prefix/title type: string preferred_name: description: Patient's preferred name or nickname type: string sex: description: Patient's sex which matches what is on their insurance policies type: string enum: - male - female - x gender: description: Patient's current gender, which may differ from gender indicated on their insurance policies type: string pronouns: description: Patient's preferred pronouns type: string date_of_birth: description: 'Patient''s date of birth. Format: YYYY-MM-DD' type: string format: date guardian_name: description: Patient's guardian name type: string patient_since: description: Patient's date of registration type: string format: date patient_number: description: Patient's number type: string email: description: Patient's email address type: string format: email send_marketing_emails: description: Patient's marketing email preferences type: boolean do_not_email: description: Patient's general email opt-out preference type: boolean status: description: Patient's current lifecycle state type: string enum: - active - discharged - deceased - archived mobile_country_code: description: Deprecated. Use the phone_numbers array instead. This field will be removed in a future version. type: string deprecated: true mobile_phone_number: description: Deprecated. Use the phone_numbers array instead. This field will be removed in a future version. type: string deprecated: true phone_numbers: description: All phone numbers associated with the patient. Only non-blank numbers are included. type: array items: type: object properties: type: description: The phone number type type: string enum: - home - mobile - work - fax number: description: The phone number type: string is_primary: description: Whether this is the patient's primary phone number type: boolean required: - type - number - is_primary additionalProperties: false street_address: description: Patient's street address type: string street_address_2: description: Additional street address information type: string city: description: Patient's city of residence type: string province: description: Patient's state/province type: string postal: description: Postal/ZIP code type: string country: description: Country code type: string pattern: ^[A-Z]{2}$ deep_links: description: Platform-specific links for accessing this patient in different contexts (e.g., admin web app, mobile app) type: object properties: profile: type: object description: Links to the patient profile resource properties: web: description: Direct link to the patient's profile in the Jane admin web UI type: string format: uri example: https://account.janeapp.com/admin#patients/0199173f-240f-7f6a-9bce-a9836f536f03 additionalProperties: false additionalProperties: false required: - id error: $schema: https://json-schema.org/draft/2020-12/schema $id: error.json title: Error type: object properties: {} unauthorized_error: $schema: https://json-schema.org/draft/2020-12/schema $id: /components/responses/schemas/unauthorized_error.json type: object required: - error title: UnauthorizedError description: Error response generated when the request is not authorized properties: error: type: string description: Error message. securitySchemes: OAuth2: type: oauth2 description: 'OAuth2 authorization code flow with PKCE (Proof Key for Code Exchange) support. **PKCE Flow Required**: All integrations must use PKCE flow for authentication. **PKCE Benefits:** - Enhanced security against authorization code interception attacks - S256 code challenge method supported - Industry best practice for OAuth2 implementations **Implementation**: Use the authorization code flow with PKCE extension as defined in RFC 7636. ' flows: authorizationCode: authorizationUrl: https://login.id.janeapp.com/realms/jane_partner_sandbox/protocol/openid-connect/auth?response_type=code&resource=https://jdpdocsdemo.jane.qa&prompt=consent tokenUrl: https://login.id.janeapp.com/realms/jane_partner_sandbox/protocol/openid-connect/token scopes: observations:read: Read observations observations:create: Create observations observations:update: Update observations care_plans:read: Read care plans care_plans:create: Create care plans care_plans:update: Update care plans patients:read: Read patient information locations:read: Read location information staff_members:read: Read staff member information appointments:read: Read appointment information companies:read: Read company information document_uploads:read: Read document uploads document_uploads:create: Create document uploads disciplines:read: Read discipline information treatments:read: Read treatment information conversations:read: Read conversation information conversations:write: Create and update conversations messages:read: Read message information messages:write: Create and update messages partner_extensions:create: Create partner extensions partner_extensions:delete: Delete partner extensions extensions:install: Install extensions extensions:uninstall: Uninstall extensions webhooks:read: List webhook subscriptions webhooks:create: Register a webhook subscription webhooks:update: Update a webhook subscription webhooks:delete: Deregister a webhook subscription BearerAuth: type: http scheme: bearer bearerFormat: JWT