Start building

Health Bank One Partner API Documentation

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

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:

  1. Configure your sandbox client. Submit your configuration (endpoint URL, mTLS/JWT public keys). We return your sandbox-* client ID.
  2. Stand up your notification endpoint. Accept POST, verify the notification JWT, durably store the notification, and only then return 200 within 5 seconds.
  3. Connect a test user to your sandbox client (see Testing in sandbox) so events are generated.
  4. Receive a notification and, asynchronously, call GET /events/{id} with a Client Access Token to fetch the full event.
  5. Follow the event. For a connection.connected with records:read, list the patient’s records with GET /patients/{id}/records and download one via GET /patients/{id}/records/{recordId}.
  6. 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 404 in 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
  • All fields adhere to max length constraints:
    • client_id -> 256 chars
    • outbound.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

  1. Notification received: Your public endpoint receives an HTTP POST containing only the event type and event ID.
  2. 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.
  3. 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).
  4. Acknowledge with 200 OK within 5 seconds: Return 200 OK only once the notification is safely stored. The 200 confirms 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 return 200, let the delivery fail so we retry (see Retry Strategy).
  5. De-duplicate by event ID: We may deliver the same event more than once. Skip event IDs you have already stored or processed.
  6. 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.
  7. 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 type and id. Full event data must be retrieved by calling GET /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

  1. Validate the JWT header:
    • alg equals "RS256" (or a pre-agreed algorithm).
    • kid is the identifier of Health Bank One’s signing key.
  2. Verify the JWT signature using Health Bank One’s key identified by kid.
  3. Validate the JWT metadata:
    • iss is exactly "partner.healthbankone.com".
    • aud matches your client ID.
    • nbf is in the past (allow margin for clock skew).
    • exp is 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 fetch GET /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 type values 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:

  1. You submit your configuration JSON (endpoint URL, public keys) to the integration team.
  2. We validate it and provision your sandbox client.
  3. You build and test against sandbox.
  4. You submit your production configuration; we provision your prod client with mTLS and source-IP enforcement.
  5. 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.