Health Bank One Partner API Documentation
Integration guide for the Health Bank One Partner APIs: real-time event notifications, patient data, and health record access. This is the canonical source; Confluence links here rather than duplicating content.
A machine-readable OpenAPI 3.1 spec for the request/response endpoints is in openapi.yaml.
Contents
- Getting Started
- Configuration
- Authentication
- Security and mTLS
- Events API
- Event Types
- Patient API
- Patient Records API
- Errors
- Versioning and Deprecation
- Onboarding and Support
- Changelog
Getting Started
All Partner APIs are served from a single base URL:
https://partner.healthbankone.com
The integration follows one flow: you register a tenant with us (Configuration), we notify your endpoint when events occur (Events API), and you call back to fetch the full data (Patient API and Patient Records API). Every request you make to us carries a signed JWT (Authentication); production tenants must also use mTLS and call from allowlisted IPs (Security and mTLS).
Sandbox vs production
Production and sandbox are distinguished by your client ID, not by a separate host, both use the same base URL above.
- A
sandbox-*client is for testing: it is unrestricted (no mTLS or source-IP requirements) and may connect with or without a client certificate. - A
prod-*client is your live tenant: mTLS public-key pinning and the source-IP allowlist are enforced.
Build against your sandbox client first, then switch to your production client for go-live.
How a connection is created
Everything you receive is scoped to a connection: a link between one Health Bank One user and you (the relying party), authorized by that user. A connection is created in one of two ways:
- The user connects to you. The user finds you in Health Bank One and authorizes the connection.
- You invite the user. You send an invitation and the user accepts it.
The user’s consent at connection time determines which events we send you and which scopes you hold (for example whether record.added events and the Patient Records API are available, see records:read). A user can withdraw consent at any time, which disconnects the connection and stops further events.
Quickstart
The shortest path to a working end-to-end integration:
- Configure your sandbox client. Submit your configuration (endpoint URL, mTLS/JWT public keys). We return your
sandbox-*client ID. - Stand up your notification endpoint. Accept
POST, verify the notification JWT, durably store the notification, and only then return200within 5 seconds. - Connect a test user to your sandbox client (see Testing in sandbox) so events are generated.
- Receive a notification and, asynchronously, call
GET /events/{id}with a Client Access Token to fetch the full event. - Follow the event. For a
connection.connectedwithrecords:read, list the patient’s records withGET /patients/{id}/recordsand download one viaGET /patients/{id}/records/{recordId}. - Go live. Repeat against your
prod-*client, this time over mTLS from an allowlisted IP (Security and mTLS).
Testing in sandbox
- Patient and Records APIs return mock data for any ID in sandbox, so you can develop against them without a real connection.
- Events are real for sandbox: to exercise the notification flow, connect a test user to your sandbox client (the same way a connection is created in production, see How a connection is created). Connecting, updating, or disconnecting that user, and records arriving for them, generate the corresponding events. Only events can return
404in sandbox, if you request an event ID that was never issued.
Sandbox exposes no real data. Everything you receive in sandbox is synthetic. You will never see real PII, and no identifier in a sandbox payload maps back to a real patient. Real events are anonymized before they reach a sandbox client: the demographic fields are replaced with fixed placeholder values, and every real patient and record ID (including inside portal_link) is swapped for a synthetic one. The synthetic IDs are deterministic and easy to recognize:
| Identifier | Sandbox value |
|---|---|
| Patient ID | 00000000-0000-8000-face-000000000001 |
| Record ID | 00000000-0000-8000-f11e-0000000000NN (NN = 1-based record index) |
Because they are fixed, do not treat a sandbox ID as unique per user; use sandbox only to validate your integration mechanics, not to model real data volumes.
Configuration
To start using the Partner APIs, provide your configuration details and cryptographic keys in a structured JSON format.
Required Parameters
General
| Name | Type | Format | Required | Description |
|---|---|---|---|---|
client_id |
string | Max length: 256 chars | Always | Unique identifier for your tenant. Must start with prod- (production) or sandbox- (non-production). |
Outbound, We Call You (Events API only)
| Name | Type | Format | Required | Description |
|---|---|---|---|---|
outbound.endpoints |
array | — | Always | Publicly accessible HTTPS endpoints where we send event notifications. |
outbound.endpoints[].url |
string | uri, max length: 2048 | Always | The HTTPS URL for event notifications. |
Inbound, You Call Us
| Name | Type | Format | Required | Description |
|---|---|---|---|---|
inbound.jwt_sig_keys |
object | JWKS | Always | Your JWT public keys in JWKS format. We use these to verify API requests you sign. RSA keys must be at least 3072 bits long. |
inbound.allowed_cidrs |
array | IPv4/IPv6 CIDRs | Production only | The CIDR ranges you send API requests from (a single host is /32 for IPv4, /128 for IPv6). We only accept requests from these ranges. |
inbound.mtls_client_keys |
array | base64 strings | Production only | Base64-encoded DER public key from your mTLS client certificate. Used for mutual TLS authentication. RSA keys must be at least 3072 bits long. |
Public Keys
You need two distinct public keys:
| Key | Required | Used For |
|---|---|---|
| mTLS Client Key | Production tenants | Authenticating your mTLS connections when you call our API. |
| JWT Signing Key | Always | Verifying JWTs you sign when making API requests. |
Caution: Do NOT include private keys. Each key must be unique.
Generating Keys
Create a Private Key
openssl genpkey -algorithm RSA -out private.pem -pkeyopt rsa_keygen_bits:3072
Extract Your mTLS Client Certificate
openssl x509 -in your_mtls_client_cert.pem -pubkey -noout | openssl rsa -pubin -outform DER | base64
Use this in inbound.mtls_client_keys.
Convert JWT Private Key to JWKS
import { createPublicKey } from "crypto";
import fs from "fs";
const pemKey = fs.readFileSync("privateKey.pem", "utf8");
const publicKey = createPublicKey(pemKey);
const jwk = publicKey.export({ format: "jwk" });
jwk.kid = '<clientid>-sig-rs256-20250820';
jwk.alg = 'RS256';
jwk.use = 'sig';
console.log(JSON.stringify({ keys: [jwk] }, null, 2));
Use this output in inbound.jwt_sig_keys.
Full Configuration Example
Once you have extracted all public keys, structure them as follows and send this payload to us:
With Events API
{
"client_id": "prod-acme",
"outbound": {
"endpoints": [
{
"url": "https://api.acme.example/webhook"
}
]
},
"inbound": {
"allowed_cidrs": ["203.0.113.0/24", "198.51.100.42/32"],
"mtls_client_keys": [
"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0fNmR7hK8a...HrNs6"
],
"jwt_sig_keys": {
"keys": [
{
"kty": "RSA",
"alg": "RS256",
"use": "sig",
"kid": "prod-acme-sig-rs256-20250212",
"n": "sXW2Lk5z3m9fCzNJ1X8vJX9d1F1oUtIpt9A5rzQmL4hPl6X4cWbNh...",
"e": "AQAB"
}
]
}
}
}
Without Events API
{
"client_id": "prod-acme",
"inbound": {
"allowed_cidrs": ["203.0.113.0/24", "198.51.100.42/32"],
"mtls_client_keys": [
"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0fNmR7hK8a...HrNs6"
],
"jwt_sig_keys": {
"keys": [
{
"kty": "RSA",
"alg": "RS256",
"use": "sig",
"kid": "prod-acme-sig-rs256-20250212",
"n": "sXW2Lk5z3m9fCzNJ1X8vJX9d1F1oUtIpt9A5rzQmL4hPl6X4cWbNh...",
"e": "AQAB"
}
]
}
}
}
Checklist Before Submission
- All keys are unique and correct:
- mTLS Client Key ->
inbound.mtls_client_keys - JWT Signing Key (JWKS) ->
inbound.jwt_sig_keys
- mTLS Client Key ->
- All fields adhere to max length constraints:
client_id-> 256 charsoutbound.endpoints[].url-> 2048 chars
- Public keys are in the correct format:
- Base64 DER for the mTLS client key
- JWKS for the JWT key
Send the JSON file via Slack or email to our integration team. We’ll verify and confirm or provide guidance.
Authentication
All API requests from partners to Health Bank One must be authenticated using a client-signed JWT bearer access token (Client Access Token), provided in the Authorization header.
Authorization: Bearer <Client Access Token>
Client Access Token Format
JWT Payload
A Client Access Token must include the following claims:
{
"htu": "5N38sVL2OOBRENFGU0hmti-m-H_Uxo8Brf1WYULMh3M",
"htm": "FOMM0WPHMpEuBIxMg34VxOkMBi67eVq5R9V3BuLRDdg",
"iss": "prod-acme",
"aud": "partner.healthbankone.com",
"iat": 1738262025,
"nbf": 1738262025,
"exp": 1738262325,
"jti": "155e9353-42f1-4813-a23a-267f43b18c4c"
}
| Field | Required | Type | Format | Description |
|---|---|---|---|---|
htu |
Yes | string | base64url (SHA-256) |
Base64url-encoded SHA-256 hash of the full request URI you are about to call. Ensures the JWT is valid only for that specific URI. |
htm |
Yes | string | base64url (SHA-256) |
Base64url-encoded SHA-256 hash of the HTTP method in uppercase (e.g., GET, POST). Ensures the JWT is valid only for that request method. |
iss |
Yes | string | Max length: 256 chars | Issuer: must be the client ID of the requesting partner (e.g., prod-acme). |
aud |
Yes | string | Fixed: "partner.healthbankone.com" |
Audience: must always be "partner.healthbankone.com". |
iat |
Yes | integer | int64 (Unix timestamp) | “Issued at” timestamp in Unix time (seconds since epoch). |
nbf |
Yes | integer | int64 (Unix timestamp) | “Not before” timestamp. Minimum value: 5 min before iat. Ensure your system clock is synchronized. |
exp |
Yes | integer | int64 (Unix timestamp) | Expiration timestamp. Maximum value: 5 min after iat. Ensure your system clock is synchronized. |
jti |
Yes | string | uuid | Unique identifier for the JWT to prevent replay attacks. |
Computing htu and htm
import crypto from "crypto";
const sha256Base64Url = (input) => {
return crypto.createHash("sha256")
.update(input, "utf8")
.digest("base64url");
};
// Request URL (as-is, including query parameters)
const requestUrl = "https://partner.healthbankone.com/events/550e8400-e29b-41d4-a716-446655440000";
// HTTP Method (uppercase)
const httpMethod = "GET";
const htu = sha256Base64Url(requestUrl);
const htm = sha256Base64Url(httpMethod);
console.log("htu:", htu);
console.log("htm:", htm);
JWT Signature
The Client Access Token must be signed using RS256 (unless otherwise agreed). Health Bank One will verify the signature using the partner’s public key identified by the kid value in the token’s header.
JWT Header
{
"typ": "JWT",
"alg": "RS256",
"kid": "prod-acme-sig-rs256-20250130"
}
| Field | Type | Format | Description |
|---|---|---|---|
typ |
string | Fixed: "JWT" |
Token type. Always "JWT". |
alg |
string | RFC 7518 | Signing algorithm. Default "RS256" unless agreed otherwise. |
kid |
string | Max length: 255 chars | ID of the key used for signature verification. Must match a key in your JWKS. |
Security and mTLS
Production tenants require additional security measures beyond JWT authentication. These requirements apply to requests you make to our APIs. Receiving event notifications from us carries no additional requirements (see the last section).
mTLS (Mutual TLS) with Public-Key Pinning
When making requests to our APIs, you must authenticate using mutual TLS (mTLS). Both the client and server present a certificate during the TLS handshake. Your client certificate does not need to be issued by a public or trusted Certificate Authority. A self-signed certificate is fine. What matters is the public key it carries: we accept your requests only if the certificate presents the public key you registered with us. See public-key pinning below.
We enforce public-key pinning as part of mTLS, we will only accept requests whose client certificate contains a public key associated with your client ID. We pin the public key itself, not the certificate, so a renewed certificate carrying the same key is accepted without any coordination.
Public-Key Pinning Mechanics (your mTLS client certificate)
- You can renew or replace your client certificate at any time without coordination, as long as its public key remains the same.
- To rotate your key (change the public key itself), you must send us the new public key first so we can pin it alongside the existing one for a seamless transition, after which the old key is removed.
Static IPs
We only accept requests from your predefined static IP addresses. Any request from an unknown IP will be rejected. Send us the IP address ranges your requests will originate from before your production tenant is enabled.
Event Notification Delivery (outbound)
When we deliver event notifications to your endpoint there are no additional requirements on your side:
- We connect over standard TLS to the endpoint hostname you provide: your endpoint’s certificate is validated against trusted CAs and its hostname is verified. We do not pin your endpoint’s certificate key and do not require a static IP on your endpoint: managed certificates (for example AWS ACM or Let’s Encrypt) and dynamic-IP hosting (for example AWS API Gateway) are fine.
- Each notification is a JWT signed by Health Bank One containing only an event ID and event type: no patient data. Verify its signature against our published key to confirm it genuinely came from us, then fetch the event through the Events API.
Events API
The Events API delivers real-time event notifications by sending HTTP POST requests to your endpoints when designated events occur, connections, updates, disconnections, and new health records.
Notification Flow
- Notification received: Your public endpoint receives an HTTP POST containing only the event type and event ID.
- Verify the notification: The notification is a signed JWT. Verify its signature and claims. A notification that fails verification is not from Health Bank One, reject it and do not process it.
- Persist the notification to durable storage: Write the notification (its event type and event ID) to a queue or other persistent store. This is the step that makes the event recoverable, so it must succeed before you acknowledge. If it fails, do not acknowledge (see step 4).
- Acknowledge with
200 OKwithin 5 seconds: Return200 OKonly once the notification is safely stored. The200confirms receipt, not that you have finished processing, and it stops our retries: if you acknowledge before the notification is durably stored and then lose it, we will not redeliver it. If you cannot store it, do not return200, let the delivery fail so we retry (see Retry Strategy). - De-duplicate by event ID: We may deliver the same event more than once. Skip event IDs you have already stored or processed.
- Request full event data (asynchronously): Working off your stored queue, send an authenticated
GET /events/{id}request with the event ID. Health Bank One responds with the full event payload as JSON. - Process the event: Process according to your business logic. If your fetch or processing fails, retry on your side from the stored notification; the event is safe because you persisted it before acknowledging.
The order is persist, then acknowledge, then fetch and process asynchronously. Never fetch or process before you acknowledge, and never acknowledge before the notification is durably stored. The 5-second window is for the acknowledgement of a stored notification, not for processing.
sequenceDiagram
participant HB1 as Health Bank One
participant P as Your endpoint
HB1->>P: POST notification (signed JWT, event type + id)
Note over P: Verify JWT (RS256)
Note over P: Persist notification to a durable queue
P->>HB1: 200 OK within 5s (only after the durable write)
Note over HB1: Marked acknowledged, not re-delivered
Note over P: Then, asynchronously, off the queue
P->>HB1: GET /events/{id} with Client Access Token
HB1->>P: 200, full event data (JSON)
Note over P: De-duplicate, then process
alt not stored, or no 200 OK within 5s
HB1->>P: Retry after 30s, then exponential backoff (5 attempts total)
end
Receiving Event Notifications
Your endpoint receives an HTTP POST with a signed JWT body:
POST /your-notification-endpoint HTTP/1.1
Host: your-server.com
Content-Type: application/jwt
eyJhbGciOiJSUzI1NiIsImtpZCI6I...
| Header | Value | Description |
|---|---|---|
Content-Type |
application/jwt |
The request body is a JWT. |
Notification JWT Format
{
"type": "event_notification",
"event": {
"type": "connection.connected",
"id": "b7b96067-691c-4e3a-b50f-762edd11f1b2"
},
"iss": "partner.healthbankone.com",
"aud": "prod-acme",
"iat": 1700000000,
"nbf": 1700000000,
"exp": 1700000300,
"jti": "93c8100c-f77e-436c-a003-60bcbc1b6ed8"
}
Note: Event notifications contain only the event
typeandid. Full event data must be retrieved by callingGET /events/{id}.
| Field | Type | Format | Description |
|---|---|---|---|
type |
string | Fixed: "event_notification" |
Always "event_notification". |
event.type |
string | Max length: 255 chars | Event type, e.g., "connection.connected". |
event.id |
string | uuid | Unique identifier of the event. Use for de-duplication. |
iss |
string | Fixed: "partner.healthbankone.com" |
Issuer. Always "partner.healthbankone.com". |
aud |
string | Max length: 255 chars | Intended recipient (your client ID). |
iat |
integer | int64 (Unix timestamp) | “Issued at” timestamp. |
nbf |
integer | int64 (Unix timestamp) | “Not before” timestamp. |
exp |
integer | int64 (Unix timestamp) | Expiration timestamp (5 minutes after iat). |
jti |
string | uuid | Unique JWT identifier for replay prevention. |
Notification JWT Header
{
"typ": "JWT",
"alg": "RS256",
"kid": "healthbankone-sig-rs256-20250130"
}
The JWT is signed using RS256 and can be verified using Health Bank One’s public key identified by kid.
Validating Event Notifications
- Validate the JWT header:
algequals"RS256"(or a pre-agreed algorithm).kidis the identifier of Health Bank One’s signing key.
- Verify the JWT signature using Health Bank One’s key identified by
kid. - Validate the JWT metadata:
issis exactly"partner.healthbankone.com".audmatches your client ID.nbfis in the past (allow margin for clock skew).expis in the future (allow margin for clock skew).
Fetch our public signing keys from our JWKS endpoint and select the one whose kid matches the token header:
https://partner.healthbankone.com/.well-known/jwks.json
This endpoint is public: it is not subject to mTLS or source-IP restrictions, so your webhook receiver can fetch it from any host, including sandbox.
We recommend the jose library’s createRemoteJWKSet, which fetches and caches the key set across warm invocations and refreshes automatically when we rotate keys, so you should not store or cache our keys yourself:
import { createRemoteJWKSet, jwtVerify } from "jose";
// Create once, at module scope (do not recreate per request).
const JWKS = createRemoteJWKSet(
new URL("https://partner.healthbankone.com/.well-known/jwks.json")
);
const { payload } = await jwtVerify(notificationJwt, JWKS, {
issuer: "partner.healthbankone.com",
audience: "<your client ID>",
algorithms: ["RS256"],
});
If verification fails, do not process the notification: a notification that does not verify was not sent by Health Bank One. Do not fetch the event and do not acknowledge it as a genuine event.
Requesting Full Event Data
GET /events/{event.id}
Authorization: Bearer <Client Access Token>
See Authentication for how to build the Client Access Token.
Example Response
{
"type": "connection.connected",
"timestamp": "2025-01-24T10:30:00Z",
"id": "b7b96067-691c-4e3a-b50f-762edd11f1b2",
"patient_id": "dd27837a-ba7f-4e28-b042-ba0dc00addac",
"patient": {
"id": "dd27837a-ba7f-4e28-b042-ba0dc00addac",
"first_name": "Floyd",
"last_name": "Perkins",
"gender": "Male",
"date_of_birth": "1976-05-12",
"email": "pfloyd@email.com",
"mobile_phone": "+15551234567",
"address": {
"street1": "123 Elm St.",
"street2": "Unit 2A",
"city": "Springfield",
"state": "IL",
"zip": "12345"
}
},
"permissions": ["records:read", "records:add"],
"portal_link": "https://adminportal.app.healthbankone.com/portal-patients/connections/9a8b419b-816e-4ae6-82f4-802f7c206fdb/electronic-record/allergies",
"promo_code": "PROMO2025"
}
Error Responses
| Status | Description |
|---|---|
401 Unauthorized |
Invalid or missing Client Access Token (see Errors). |
404 Not Found |
No event with that ID for your client (in sandbox, also if the ID was never issued). |
500 Internal Server Error |
Unexpected server error. |
See Errors for the response bodies.
Acknowledging Event Notifications
Respond with HTTP 200 OK within 5 seconds, but only after you have durably stored the notification. The acknowledgement confirms receipt only, it does not mean you have finished processing the event, and because it stops our retries the durable write is its precondition: verify the notification, write it (its event type and ID) to a queue or other persistent store, and only then return 200. Fetch the full event and process it asynchronously off that stored record (see Notification Flow). If you cannot store the notification, do not return 200, let the delivery fail so we retry.
The following scenarios constitute a delivery failure:
- We cannot negotiate or validate your server’s SSL certificate.
- Your domain resolves to a disallowed IP address range.
- We receive any response other than HTTP 200 OK.
- We wait longer than 5 seconds for a response.
Tip: The order is durable-store, then
200, then fetch and process on a background worker. Never fetchGET /events/{id}or run business logic before acknowledging, and never acknowledge before the notification is safely stored, so neither processing latency nor a crash mid-processing can cost you an event.
Retry Strategy
If a delivery fails, we retry up to 5 times total, with the delays growing between attempts: 30 seconds, then 1, 2, 4, and 8 minutes (about 15.5 minutes across all attempts). We may add jitter to prevent retry storms and throttle notifications during high failure rates.
Upon consistent failures, we may disable event delivery and will notify you ahead of time.
Event Ordering
We try to deliver related events in order, connection.connected before record.added for the same patient. Occasionally events may arrive out of order.
To maintain order on your side, persist events in order-preserving storage as you acknowledge them, and have your background worker process them in received order rather than concurrently. Because you acknowledge before processing, ordering is your responsibility once the event is persisted.
See Event Types for detailed payloads of each event.
Replay and Backfill
If you miss events (for example an outage on your side that outlasts our retry window), we can replay or backfill them. This is currently a manual process coordinated with our operations team: contact us with the affected connection and time range and we will re-deliver. Automated self-service replay is on the roadmap.
Because notifications may be redelivered, your handler must be idempotent: de-duplicate by event ID (see Notification Flow).
Event Types
Data Model
A Connection links you (the partner) to a Patient and carries the permissions that gate access; a Patient belongs to a User (one user can hold several patients). Connection events describe changes to the connection; record.added signals new Records.
Connection Events
| Event | Description |
|---|---|
connection.connected |
A new connection between you and a patient is established. |
connection.updated |
Connection attributes (e.g., permissions) are updated. |
connection.disconnected |
The connection is fully terminated. |
Record Events
| Event | Description |
|---|---|
record.added |
New medical records are added for a connected patient. |
connection.connected
Triggered when a new connection between you and a patient is established.
If records:read is not included in permissions, you will not have access to the patient’s records or receive record-related event notifications.
With records:read
{
"type": "connection.connected",
"timestamp": "2025-01-24T10:30:00Z",
"id": "b7b96067-691c-4e3a-b50f-762edd11f1b2",
"patient_id": "dd27837a-ba7f-4e28-b042-ba0dc00addac",
"patient": {
"id": "dd27837a-ba7f-4e28-b042-ba0dc00addac",
"first_name": "Floyd",
"last_name": "Perkins",
"gender": "Male",
"date_of_birth": "1976-05-12",
"email": "pfloyd@email.com",
"mobile_phone": "+15551234567",
"address": {
"street1": "123 Elm St.",
"street2": "Unit 2A",
"city": "Springfield",
"state": "IL",
"zip": "12345"
}
},
"permissions": ["records:read", "records:add"],
"portal_link": "https://adminportal.app.healthbankone.com/portal-patients/connections/9a8b419b-816e-4ae6-82f4-802f7c206fdb/electronic-record/allergies",
"promo_code": "PROMO2025"
}
Without records:read
{
"type": "connection.connected",
"timestamp": "2025-01-24T10:30:00Z",
"id": "b7b96067-691c-4e3a-b50f-762edd11f1b2",
"patient_id": "dd27837a-ba7f-4e28-b042-ba0dc00addac",
"permissions": ["records:add"],
"portal_link": "https://adminportal.app.healthbankone.com/portal-patients/connections/9a8b419b-816e-4ae6-82f4-802f7c206fdb/electronic-record/allergies",
"promo_code": "PROMO2025"
}
Fields
| Field | Type | Format | Description |
|---|---|---|---|
type |
string | Max length: 255 chars | connection.connected, a new connection has been established. |
timestamp |
string | date-time (ISO 8601, UTC) | When the event occurred. Example: "2025-01-24T10:30:00Z". |
id |
string | uuid | Unique identifier of the event. |
patient_id |
string | uuid | Unique identifier of the patient. Use it with the Patient API. |
patient.id |
string | uuid | Unique identifier of the patient (only present when records:read is granted). |
patient.first_name |
string | Max length: 100 chars | Patient’s first name. |
patient.last_name |
string | Max length: 100 chars | Patient’s last name. |
patient.gender |
string | enum: ("Male", "Female", "Other") |
Patient’s gender. |
patient.date_of_birth |
string | date (ISO 8601), pattern: "^\d{4}-\d{2}-\d{2}$" |
Patient’s birthdate (YYYY-MM-DD). |
patient.email |
string | email, max length: 200 chars | Patient’s email address. |
patient.mobile_phone |
string | E.164, pattern: ^\+?[1-9]\d{1,14}$, max length: 16 |
Patient’s mobile phone number in E.164 format. |
patient.address |
object | — | Patient’s address containing street1, street2, city, state, and zip. |
patient.address.street1 |
string | Max length: 200 chars | First line of the patient’s address. |
patient.address.street2 |
string | Max length: 100 chars | Second line of the patient’s address (optional). |
patient.address.city |
string | Max length: 100 chars | City of residence. |
patient.address.state |
string | pattern: "^[A-Z]{2}$", max length: 2 |
U.S. state abbreviation (e.g., CA, NY). |
patient.address.zip |
string | pattern: "^\d{5}$", max length: 5 |
ZIP code (U.S. format). |
permissions |
array | Items: string (enum: records:read, records:add) |
Permissions the user granted. records:read allows retrieving records via the Patient Records API. records:add is a consent scope for records being added to the patient’s account on their behalf; it is not backed by a partner write endpoint (records are added by third parties through AllClear, see Patient Records API). If records:read is not included, the partner will not have access to records or receive related notifications. |
portal_link |
string | uri, max length: 2048 | Direct URL to view this connection in the partner portal (within healthbankone.com). |
promo_code |
string | Max length: 32 chars | Reference data from connection creation (optional, may be absent). Contains the promo code if one was used. |
connection.updated
Same structure as connection.connected (with and without records:read), but type is "connection.updated".
This fires whenever connection attributes change, including permissions being added or removed. If records:read is removed you lose access to the patient’s records and stop receiving record-related event notifications; if it is added, access begins.
connection.disconnected
Triggered when a connection is fully terminated (by either the patient or the partner). You lose access to the patient’s records and stop receiving any event notifications about this patient.
{
"type": "connection.disconnected",
"timestamp": "2025-01-24T10:30:00Z",
"id": "b7b96067-691c-4e3a-b50f-762edd11f1b2",
"patient_id": "dd27837a-ba7f-4e28-b042-ba0dc00addac",
"portal_link": "https://adminportal.app.healthbankone.com/portal-patients/connections/9a8b419b-816e-4ae6-82f4-802f7c206fdb/electronic-record/allergies",
"promo_code": "PROMO2025"
}
| Field | Type | Format | Description |
|---|---|---|---|
type |
string | Max length: 255 chars | connection.disconnected, the connection has been terminated. |
timestamp |
string | date-time (ISO 8601, UTC) | When the event occurred. Example: "2025-01-24T10:30:00Z". |
id |
string | uuid | Unique identifier of the event. |
patient_id |
string | uuid | Unique identifier of the patient. |
portal_link |
string | uri, max length: 2048 | Direct URL to view this connection in the partner portal. |
promo_code |
string | Max length: 32 chars | Reference data from connection creation (optional, may be absent). |
record.added
Triggered when new medical records are added and available for a connected patient.
{
"type": "record.added",
"timestamp": "2025-01-24T10:30:00Z",
"id": "fa032c9c-6e4b-4090-a719-cb49de0a5ebc",
"patient_id": "9a8b419b-816e-4ae6-82f4-802f7c206fdb",
"records": [
{
"id": "fa032c9c-6e4b-4090-a719-cb49de0a5ebc",
"portal_link": "https://adminportal.app.healthbankone.com/portal-patients/connections/dd27837a-ba7f-4e28-b042-ba0dc00addac/medical-documents/fa032c9c-6e4b-4090-a719-cb49de0a5ebc"
}
]
}
| Field | Type | Format | Description |
|---|---|---|---|
type |
string | Max length: 255 chars | record.added, a new record has been added to the patient’s file. |
timestamp |
string | date-time (ISO 8601, UTC) | When the event occurred. Example: "2025-01-24T10:30:00Z". |
id |
string | uuid | Unique identifier of the event. |
patient_id |
string | uuid | Unique identifier of the patient. Use it with the Patient API. |
records |
array | — | The new records. |
records[].id |
string | uuid | Record identifier. Use it with the Patient Records API to download the file. |
records[].portal_link |
string | uri, max length: 2048 | Direct URL to view the record in the partner portal. |
Patient API
The Patient API allows you to fetch demographic and insurance information about connected patients.
Request
GET /patients/{id}
Authorization: Bearer <Client Access Token>
See Authentication for how to build the Client Access Token.
Response
{
"id": "60389406-7863-428d-9831-cd5fa1e79e64",
"first_name": "Floyd",
"last_name": "Perkins",
"gender": "Male",
"date_of_birth": "1976-05-12",
"email": "pfloyd@email.com",
"mobile_phone": "+15551234567",
"address": {
"street1": "123 Elm St.",
"street2": "Unit 2A",
"city": "Springfield",
"state": "IL",
"zip": "12345"
},
"primary_insurance": {
"health_insurance_provider": "BlueCross BlueShield of Texas",
"member_id_number": "TC001001",
"group_number": "123456789",
"member_name": "Floyd Perkins",
"insurance_plans": ["Blue Edge PPO"]
},
"secondary_insurance": {
"health_insurance_provider": "BlueCross BlueShield of Texas",
"member_id_number": "TC001002",
"group_number": "123456789",
"member_name": "Floyd Perkins",
"insurance_plans": ["Blue Edge PPO"]
}
}
Field Descriptions
Patient
| Field | Type | Format | Description |
|---|---|---|---|
id |
string | uuid | Unique identifier of the patient. |
first_name |
string | Max length: 100 chars | Patient’s first name. |
last_name |
string | Max length: 100 chars | Patient’s last name. |
gender |
string | enum: "Male", "Female", "Other" |
Patient’s gender. |
date_of_birth |
string | date (ISO 8601), pattern: "^\d{4}-\d{2}-\d{2}$" |
Patient’s birthdate in ISO 8601 format (YYYY-MM-DD). |
email |
string | email, max length: 200 chars | Patient’s email address. |
mobile_phone |
string | E.164, pattern: ^\+?[1-9]\d{1,14}$, max length: 16 |
Patient’s mobile phone number in E.164 format. Example: "+15551234567". |
Address
| Field | Type | Format | Description |
|---|---|---|---|
address.street1 |
string | Max length: 200 chars | First line of the address. |
address.street2 |
string | Max length: 100 chars | Second line of the address (optional). |
address.city |
string | Max length: 100 chars | City of residence. |
address.state |
string | pattern: "^[A-Z]{2}$", max length: 2 |
U.S. state abbreviation (e.g., CA, NY). |
address.zip |
string | pattern: "^\d{5}$", max length: 5 |
ZIP code (U.S. format). |
Insurance
Both primary_insurance and secondary_insurance are optional: they may be absent from the response.
| Field | Type | Format | Description |
|---|---|---|---|
health_insurance_provider |
string | Max length: 200 chars | Name of the insurance provider. |
member_id_number |
string | Max length: 100 chars | Member ID number. |
group_number |
string | Max length: 100 chars | Group number (optional, may be absent). |
member_name |
string | Max length: 200 chars | Name of the member on the plan. |
insurance_plans |
array | Items: string | List of insurance plan names. |
Patient Records API
The Patient Records API allows you to list and download health records for connected patients.
This API is read-only for partners: there is no endpoint to add or upload records. Records are added to a patient’s account when third parties provide them to AllClear; when a new record becomes available for a connected patient (and you hold records:read), you are notified with a record.added event.
List All Records
GET /patients/{patient.id}/records
Authorization: Bearer <Client Access Token>
See Authentication for how to build the Client Access Token.
Returns all of the patient’s records as a flat JSON array in a single response. There is no pagination and there are no query parameters. A patient with no records returns 200 with an empty array ([]).
Response
[
{
"id": "b44a4e8e-dc25-4f1c-9bfc-c2c8f68cc34f",
"file_name": "AUSTIN REGIONAL CLINIC PA_09-08-2025_4.pdf",
"file_type": "pdf"
},
{
"id": "a12b3c4d-5678-9abc-def0-1234567890ab",
"file_name": "LAB_RESULTS_09-10-2025.pdf",
"file_type": "pdf"
}
]
| Field | Type | Format | Description |
|---|---|---|---|
id |
string | uuid | Unique identifier of the record. Use it to fetch the full record with download URL. |
file_name |
string | Max length: 100 chars | Name of the file. |
file_type |
string | enum | One of: ccda, image, pdf, dicom, zip, text, bin |
Get a Specific Record
GET /patients/{patient.id}/records/{record.id}
Authorization: Bearer <Client Access Token>
Response
{
"id": "b44a4e8e-dc25-4f1c-9bfc-c2c8f68cc34f",
"file_name": "AUSTIN REGIONAL CLINIC PA_09-08-2025_4.pdf",
"file_type": "pdf",
"download": {
"url": "https://storage.googleapis.com/<bucket>/partner-access/acme/b44a4e8e-dc25-4f1c-9bfc-c2c8f68cc34f.pdf?X-Goog-Algorithm=GOOG4-RSA-SHA256&X-Goog-Expires=300&X-Goog-Signature=...",
"expires_at": "2025-09-08T13:38:25.419Z"
}
}
| Field | Type | Format | Description |
|---|---|---|---|
id |
string | uuid | Unique identifier of the record. |
file_name |
string | Max length: 100 chars | Name of the file. |
file_type |
string | enum | One of: ccda, image, pdf, dicom, zip, text, bin |
download |
object | — | Metadata for downloading the record. |
download.url |
string | Max length: 2048 chars | Pre-signed download URL. Already includes authorization: no extra auth needed, and no mTLS or source-IP restriction. Valid until download.expires_at (5 minutes). |
download.expires_at |
string | ISO 8601 datetime | When the download URL expires. Example: "2025-09-08T13:38:25.419Z". |
Errors
Every error carries an HTTP status and a JSON body. There are two body shapes: authentication failures use a flat { status, message }, and all other errors use the standard error envelope below.
Authentication failures (401)
A request that fails authentication is rejected with HTTP 401 and a flat body:
{
"status": "UNAUTHENTICATED",
"message": "JWT expired"
}
The message states the specific reason. Common causes:
| Cause | What to check |
|---|---|
| JWT invalid or expired | nbf/exp are within 5 minutes of iat; htu/htm match the exact request URL and method; signed RS256 by a key in your JWKS (the header kid matches). |
| mTLS key not recognized (production) | Your client certificate must present the public key you registered in inbound.mtls_client_keys. A self-signed certificate is fine; we pin the key, not the certificate. |
| Source IP not allowlisted (production) | The request must originate from a CIDR listed in inbound.allowed_cidrs. |
Sandbox clients skip the mTLS and source-IP checks.
Error envelope (403, 404, and other errors)
All other errors return the standard envelope:
{
"error": {
"code": "NOT_FOUND",
"message": "Record not found",
"requestId": "b1f2c3d4-5678-90ab-cdef-1234567890ab",
"correlationId": "9a8b7c6d-..."
}
}
| Field | Description |
|---|---|
error.code |
Machine-readable code, for example FORBIDDEN, NOT_FOUND. |
error.message |
Human-readable description. |
error.requestId |
Identifier for this request. Include it when contacting support. |
error.correlationId |
Present when the request crossed internal services; include it too if present. |
error.details |
Optional array with field-level specifics when applicable. |
| Status | code |
When |
|---|---|---|
403 |
FORBIDDEN |
You are authenticated, but not permitted to access this resource (for example a patient or record outside your consent). message is "Access Denied". |
404 |
NOT_FOUND |
The event, patient, or record does not exist (or, in sandbox, the event ID was never issued). |
There are currently no request rate limits on the Partner APIs; you will not receive 429. This may change in future, in which case it will be announced ahead of time and documented here.
Versioning and Deprecation
- Additive changes are backward-compatible and ship without a version bump. We may add new fields to responses and new event
typevalues at any time. Build tolerantly: ignore fields you do not recognize, and ignore event types you do not handle rather than failing on them. - Breaking changes are introduced as a new version and announced in advance. The previous version continues to work for a deprecation window of 6 months from the announcement, so you have time to migrate.
- All changes are recorded in the Changelog and announced over Slack or email.
Onboarding and Support
We assign your client IDs. You do not choose them. On onboarding we issue a sandbox-* client ID and, for go-live, a prod-* client ID.
Configuration lifecycle:
- You submit your configuration JSON (endpoint URL, public keys) to the integration team.
- We validate it and provision your sandbox client.
- You build and test against sandbox.
- You submit your production configuration; we provision your prod client with mTLS and source-IP enforcement.
- You go live.
To change configuration later (rotate keys, add or remove allowed_cidrs, change your endpoint URL), submit an updated JSON to the integration team, the same way. We do not expose a self-service configuration endpoint today.
Proposed SLA. Support-response and delivery-availability targets are being finalized and are not yet contractual. Until then, treat delivery as best-effort with the retry and replay guarantees described above.
Need Help?
If you run into any issues, we’ll treat it as our top priority. Message us on Slack or email, we’ll jump in quickly to keep you moving.
We love hearing from partners, and we treat every request as a chance to make your integration smoother.
Changelog
| Release Date | Changes |
|---|---|
| 2026-09-16 | Clarified the notification flow: acknowledge receipt with 200 within 5 seconds, then fetch and process the event asynchronously. Reworked the errors section (401 {status,message}, error envelope for 403/404) and removed rate-limit references (none are configured). Documented that /.well-known/jwks.json is public. Added Quickstart, connection creation, sandbox testing, event replay/backfill, versioning/deprecation, and onboarding/support sections. Added a machine-readable openapi.yaml. |
| 2026-09-15 | Renamed inbound.allowed_ips to inbound.allowed_cidrs. Added the 3072-bit RSA minimum to the mTLS client-key field (the JWT signing-key field already stated it). Clarified that mTLS uses public-key pinning: a self-signed client certificate is accepted, and the certificate does not need to be issued by a trusted CA. |
| 2025-09-17 | Updated event data format (no longer wrapped into JWE+JWS). Clarified payload structure and order guarantees. Added Patient API and Patient Records API. |
| 2025-04-29 | Defined encrypted (JWE) and signed (JWS) event wrapping. Added a sequence diagram and restructured to follow flow steps. Added htu/htm example code. |
| 2025-02-10 | Changed phone number format to E.164. Removed timestamp from event notifications. Specified delivery semantics, order, and retry nuances. |
| 2025-01-31 | Initial version. |