Downloading Interaction Files
Request short-lived, single-use download URLs for an interaction's file attachments.
Clinical interactions in Hint can carry file attachments: lab report PDFs, uploaded documents, scanned forms, and files pushed by other partners. This guide shows how to turn those attachments into downloadable files.
The files array names the attachments
files array names the attachmentsEvery interaction response includes a files array. Each entry identifies one attachment:
{
"id": "int-abc123",
"type": "document",
"files": [
{ "id": "Fah-3TWDJuQ1MSe-lBTiPh", "filename": "referral.pdf", "type": null },
{ "id": "Znd8CviNr5RERx6L-v5auf", "filename": "scan.tiff", "type": null }
]
}This tells you what is attached, but it is not itself downloadable. The underlying storage keys are private, so you request a short-lived signed URL for a file through one of two endpoints. The file id in the files array is the same id you pass to the single-file endpoint below.
Each entry carries a type
typeEvery entry in the files array carries a type, and its value is null. The key is always present. Read null as untyped.
One interaction type is the exception. A Lab Interaction records which document ordered the lab and which reported it, and its files carry that type. Lab Requisitions and Results covers both values.
Both endpoints below resolve every file id, whatever its type.
Send a file
files takes the documents you attach, and every interaction type accepts the same entry. Each entry is an object:
{
"files": [
{ "url": "https://files.example.com/referral-000123.pdf", "filename": "referral.pdf" },
{ "url": "data:application/pdf;base64,JVBERi0xLjQKJVBFWEFNUExF", "filename": "scan.pdf" }
]
}url is required, and it carries the document in one of two forms:
- A
data:URI carries the document inline. - An
httpsURL points Hint at the document. Hint fetches it while it handles your call, so the URL has to resolve at that moment.
filename is the name the file is stored under. It is optional, and sending it is the reliable choice. When you leave it out Hint takes the name from the response's Content-Disposition, then from the URL's path, and derives a missing extension from the file's own bytes. A file Hint cannot name is rejected with a 422. For a data: URI the name comes from that last step every time, since the URI carries the bytes alone.
Hint accepts a URL string or an object carrying a url. It rejects anything else:
{
"status": 422,
"code": "unprocessable_entity",
"message": "Files must be an array of URL strings or objects holding a url",
"errors": { "files": ["must be an array of URL strings or objects holding a url"] }
}A bare URL string also works, and Hint derives the whole name.
A Lab Interaction takes two further file fields that record what each document is. See Lab Requisitions and Results.
Clearing files with files: [] is legacy
files: [] is legacyfiles is the canonical file field on an interaction create or update, and it replaces the interaction's file list:
- Leave
filesout and the interaction keeps its current files. - Send entries in
filesand they replace the interaction's whole untyped file list. - Send an empty
filesarray and Hint removes every untyped file on the interaction, including the files a provider attached in the Hint UI and the files another partner pushed.
{
"files": []
}A file typed as a lab requisition or a lab result belongs to a separate list. It stays attached through files: [] and keeps appearing in the interaction's files array. files: [] is the whole of the removal the API offers today.
Do not build a sync on the empty-array delete
files: []is the legacy way to remove a file, and a dedicated endpoint for deleting a single interaction file is planned to replace it. Until that endpoint ships,files: []is the only removal the API offers, and it takes every untyped file with it.Send it only when you mean to remove every untyped file on the interaction. Send
fileswhen you mean to attach one.
Download every file on an interaction
List Interaction File Download URLs returns a signed URL for every file on the interaction, as a bare array:
GET /api/provider/interactions/{interaction_id}/files/download_urls
[
{
"id": "Fah-3TWDJuQ1MSe-lBTiPh",
"filename": "referral.pdf",
"type": null,
"url": "https://practice-bucket.s3.amazonaws.com/public/pat-123456/referral.pdf?X-Amz-Signature=EXAMPLE",
"expires_at": "2026-07-01T12:05:00.000Z"
},
{
"id": "Znd8CviNr5RERx6L-v5auf",
"filename": "scan.tiff",
"type": null,
"url": null,
"expires_at": null
}
]Each element carries the same type the interaction reports, so a sync can group the signed URLs without reading the interaction again.
This is the call to make when you are syncing an interaction and want every attachment in one round trip.
Download a single file
Get Interaction File Download URL returns one element of that same shape. Use it when you already hold a file id (from an earlier sync, a queued job, or a retry) and want a fresh URL for just that file without re-signing the rest:
GET /api/provider/interactions/{interaction_id}/files/{id}/download_url
{
"id": "Fah-3TWDJuQ1MSe-lBTiPh",
"filename": "referral.pdf",
"type": null,
"url": "https://practice-bucket.s3.amazonaws.com/public/pat-123456/referral.pdf?X-Amz-Signature=EXAMPLE",
"expires_at": "2026-07-01T12:05:00.000Z"
}Downloads are PDF-only
Only files that resolve to application/pdf receive a signed url. The two endpoints handle a non-PDF differently:
- On
download_urls, a non-PDF stays in the array withurlandexpires_atset tonull. It is not omitted, so you can tell "this attachment is not downloadable" apart from "this attachment does not exist". - On
download_url, a non-PDF is a422 Unprocessable Entity. When you ask for a specific file, a refusal is the honest answer.
An id that does not match any file on the interaction returns 404 Not Found on the single-file endpoint.
URLs are short-lived: sign at download time
The signed url expires roughly 5 minutes after it is issued (expires_at carries the exact time), and it is meant to be used once. Request it at the moment the user acts, then redirect them to (or fetch) the returned url right away.
Do not cache the URL, store it in your database, or precompute it during a scheduled sync. A URL signed during an overnight backfill will have expired long before anyone clicks it. Store the file id instead, and request a fresh URL for it whenever you need one - the same id returns a new URL every time you call.
sequenceDiagram
participant App as Your app
participant Hint as Hint Provider API
App->>Hint: GET /interactions/{id}
Hint-->>App: files [ { id, filename } ]
App->>Hint: GET /interactions/{id}/files/{file_id}/download_url
Hint-->>App: { url, expires_at }
App->>App: Redirect user to url within 5 minutes
The same pattern holds wherever you put a file in front of a user: keep the file's id from the interaction's files array, and request its download_url at the moment they act.
Access and scope
These endpoints are practice-scoped like the rest of the Provider API, authorized with the practice's access token. A partner can download files on any interaction it can already read; the endpoints do not restrict downloads to files the calling partner uploaded. Files a Hint provider attached in the UI are in scope alongside files uploaded by partners.
Availability
Both endpoints are available from API version 2026-07-01 onward. Clients pinned to an earlier version receive a 404 on both routes. See Initial Data Sync for the broader pattern of reconciling interaction data when you first connect a practice.
Updated 1 day ago

