Skip to main content
Two resources round out a patient’s record beyond their clinical and financial data: files — documents, images and reports attached to them — and patient_logs — the chronological activity timeline the Activity Log tab shows. Both are written through the exact services the app’s own Documents, Images and Activity Log tabs use, so a file or entry created through the API behaves identically to one a team member added by hand.

Files

Uploading

POST /files is multipart/form-data, not JSON — the one write on the API that isn’t:

Types

Updating

PATCH /files/{id} changes description, patient_viewable and file_type — nothing else; the file’s content, its patient and its patient_log link are fixed at upload and refused (not silently ignored) if you send them again.
file_type can move between generic, custom_content, diagnostic_image and patient_profile — never to or from patient_log. A patient_log attachment refuses any re-type with 422 patient_log_attachment_locked: an attachment’s identity is the log entry it documents, and that can’t change after the fact.

Reading and downloading

GET /files and GET /files/{id} return the file’s metadata — file_name, mime_type, size, file_type, description, patient_viewable, and which patient it belongs to. The current resource set does not return a URL to fetch the binary itself — there is no download endpoint yet. If your integration needs the file content today, that’s a gap to plan around rather than something to poll for; check the changelog before building around an assumption here.

Deletion

DELETE /files/{id} soft-deletes the record. The underlying stored object is only removed from storage once no other live file row still points at the same storage key — a safety check rather than something an integration needs to think about under normal use, where each upload gets its own key.

Attachments on log entries

To attach a file to an Activity Log entry, upload it as an ordinary file with file_type=patient_log and patient_log_id set to the entry:
The log entry must already exist and belong to the same patient (422 otherwise). Deleting the log entry takes its patient_log attachments with it — they’re soft-deleted and unlinked in the same transaction, the same as the Activity Log tab’s delete does. Deleting the file on its own just removes that one attachment; the entry stays.

Patient logs

patient_logs is the patient’s activity timeline — the same feed the Activity Log tab renders, filterable by type:

Types

An entry is stamped with who wrote it automatically: entries created through the API carry source: api and are attributed to your integration in the app’s views, exactly like every other write — you never set source yourself (sending it is refused).

Updating and deleting

The activity log is also PracticeHub’s audit trail, so the API can change or delete only the entries it created (source: api). Entries written by the team or by PracticeHub itself — appointment, payment and communication history — answer 422 log_entry_locked. PATCH /patient_logs/{id} changes type, data, sub_type and source_entity. An entry can’t move to another patient (patient_id is refused on update), and its source is permanent. DELETE /patient_logs/{id} soft-deletes the entry and takes its patient_log-type file attachments with it, in the same transaction — nothing is left dangling.

Tags

patient_log_tags label individual entries — a lightweight way to mark entries for later filtering beyond what type and sub_type give you:
patient_log_id must be a real entry on the account (422 patient_log_not_found otherwise); tag_name is required, ≤ 64 chars. PATCH can rename a tag (tag_name) or move it to a different entry (patient_log_id, re-checked the same way); DELETE removes it. There’s no uniqueness constraint enforced on the pair — tagging the same entry with the same name twice creates two rows. See Writing for the full field-by-field contract and error codes, and Data migration for bulk-importing documents and history alongside billing and appointment records.