Patient messages
Read permitted patient messages through the Jump EHR API.
GET /messages and GET /messages/{id} return patient-directed communication
metadata. GET /messages/{id}/content retrieves a recorded source copy, requiring
both read_messages and read_message_content on a live organisation key.
Metadata grants alone never fetch bodies or subjects. Existing keys are not
upgraded; no MCP capability is enabled by the route inventory.
The backing store is communications, not staff/CRM/support mailboxes. Both
the communication and joined patient must belong to the key's practice, and
recipient_scope must be patient. A row being about a patient does not mean
it was addressed to them. Internal notifications are excluded. Channels covered
are native email, sms, push and app; notes, calls and unknown channels are
outside this initial contract. Draft and unknown status rows are excluded.
List accepts only patient_id, channel_type, direction, status, limit
(default 20, maximum 100) and offset (maximum 100000). Empty, repeated and
unsupported parameters return 422. An unavailable patient filter returns 404.
Sort is created_at DESC NULLS LAST, id DESC, with one lookahead row for
has_more and next_offset. Offset pagination is not a consistent export
snapshot during concurrent changes. Get/content accept a UUID and no query.
Metadata includes identity, patient, channel, direction, native status and delivery code, attachment-presence flag, and creation/update/occurrence/send/ failure/read/retraction timestamps. It does not include bodies, subjects, raw addresses, telephone numbers, failure detail, provider IDs, status-history JSON, verification claims or attachment paths. A presence flag does not mean attachment bytes are retrievable through this API. Delivery codes are preserved, not treated as proof of delivery, identity verification or clinical review. Retracted rows remain identifiable in metadata while their content is unavailable.
Content returns {id, patient_id, available, content?}. A successful copy has
source, format, subject and body. Outbound sent/delivered/read/opened/bounced
records prefer sent_html with format: html; when absent, a recorded sent_body
is returned as format: native. Only sent_subject is used. The current draft
body and subject are never substituted. HTML is the stored representation,
not newly sanitised HTML; clients must apply their own safe rendering policy.
Inbound received rows expose their recorded body/subject with format: native.
This does not establish an authenticated sender or imply that the source is
plain text. Missing copies, queued/sending/failure states and retractions report
available: false with not_recorded, not_sent_or_received or retracted.
Empty recorded copies remain empty. Content responses are capped at 1 MiB of
UTF-8 body bytes and 4096 subject bytes; oversized records fail closed. These
are response checks after database retrieval, not a pre-buffer network limit.
Example: GET /messages?patient_id=16261000-0000-4000-8000-000000000002&direction=outbound&channel_type=email.
List/get use the normal {data, meta} envelope; list includes pagination meta.
Content uses the same envelope around its availability result. Successful
responses use Cache-Control: no-store. First-party logs retain practice/key/
request attribution; analytics for the new resource use a fixed /api-v1/messages
label without message IDs or query filters. General analytics cleanup is #15496.
Sending, retries, drafts, marking read and attachment retrieval are separate operations. Recorded content can itself contain patient links or personal information, which is why access has its own grant. Final CI, security/clinical assessment and deployed PostgREST/staging acceptance remain pending until reviewed.
Authorization
BearerApiKey API key with the pk_live_ prefix. pk_test_ keys are refused (403 TEST_MODE_UNAVAILABLE) until a sandbox exists.
In: header
Query Parameters
1 <= value <= 100200 <= value <= 1000000uuidValue in
- "email"
- "sms"
- "push"
- "app"
Value in
- "inbound"
- "outbound"
Value in
- "queued"
- "sending"
- "in_progress"
- "sent"
- "delivered"
- "read"
- "opened"
- "bounced"
- "failed"
- "error"
- "received"
Response Body
application/json
curl -X GET "https://example.com/messages"{ "data": [ { "id": "string", "patient_id": "string", "channel_type": "email", "direction": "inbound", "status": "queued", "delivery_status": "string", "has_attachments": true, "created_at": "string", "updated_at": "string", "occurred_at": "string", "sent_at": "string", "failed_at": "string", "read_at": "string", "voided_at": "string" } ], "meta": { "limit": 20, "next_offset": 1, "has_more": true, "request_id": "req_abc123" }}Authorization
BearerApiKey API key with the pk_live_ prefix. pk_test_ keys are refused (403 TEST_MODE_UNAVAILABLE) until a sandbox exists.
In: header
Path Parameters
uuidResponse Body
application/json
curl -X GET "https://example.com/messages/497f6eca-6276-4993-bfeb-53cbbbba6f08"{ "data": { "id": "string", "patient_id": "string", "channel_type": "email", "direction": "inbound", "status": "queued", "delivery_status": "string", "has_attachments": true, "created_at": "string", "updated_at": "string", "occurred_at": "string", "sent_at": "string", "failed_at": "string", "read_at": "string", "voided_at": "string" }, "meta": { "request_id": "req_abc123" }}Authorization
BearerApiKey API key with the pk_live_ prefix. pk_test_ keys are refused (403 TEST_MODE_UNAVAILABLE) until a sandbox exists.
In: header
Path Parameters
uuidResponse Body
application/json
curl -X GET "https://example.com/messages/497f6eca-6276-4993-bfeb-53cbbbba6f08/content"{ "data": { "id": "string", "patient_id": "string", "available": true }, "meta": { "request_id": "req_abc123" }}