Skip to main content

Public Patient Documents API

Purpose​

This page explains how an external client can:

  1. Create a patient document placeholder in the root directory or a folder path.
  2. Upload the file content to the returned storage URL.
  3. Search for documents.
  4. Traverse a patient's documents by path.

The API specification defines the endpoint contracts, request fields, and response schemas. This guide complements it with an end-to-end integration workflow using the external /v1 routes.

Prerequisites​

Every Patient Documents API request requires:

  • An OAuth 2.0 bearer token.
  • The UUID of the customer organization in the Organization-ID header.
  • Access to the patient identified by patientId.
  • Patient consent for the organization.
  • Patient.File.Write permission for document creation.
  • Patient.File.Read permission for document search.

Create a folder and file placeholder​

This operation creates a document placeholder. File bytes are uploaded separately using the returned storageUrl.

There is no separate public Create Folder endpoint. Folders are expressed through the document's path. Supplying a nested path places the placeholder in that folder hierarchy in file storage.

Create a file at the patient root​

Request:

curl --request POST \
--url "$BASE_URL/v1/patients/$PATIENT_ID/documents" \
--header "Authorization: Bearer $ACCESS_TOKEN" \
--header "Organization-ID: $ORGANIZATION_ID" \
--header "Content-Type: application/json" \
--data '{
"fileName": "consent-form.pdf"
}'

Create a nested folder path and file​

The following request places the document under /Cases/2026/Initial-Scan:

curl --request POST \
--url "$BASE_URL/v1/patients/$PATIENT_ID/documents" \
--header "Authorization: Bearer $ACCESS_TOKEN" \
--header "Organization-ID: $ORGANIZATION_ID" \
--header "Content-Type: application/json" \
--data '{
"fileName": "upper-jaw.stl",
"path": "/Cases/2026/Initial-Scan"
}'

Successful response: 201 Created

{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"storageUrl": "https://storage.example.com/patient-documents/3fa85f64-5717-4562-b3fc-2c963f66afa6",
"path": "/Cases/2026/Initial-Scan"
}

Upload the file content​

Document creation and content upload are separate steps.

Current AXS web-client behavior​

The current AXS frontend uploads content with an authenticated HTTP POST to {storageUrl}/file. The request requires the OAuth bearer token in the Authorization header and sends multipart form data with these fields:

FieldValue
fileBinary file content
FileNameOriginal file name
TypeUppercase file type, such as PDF or STL
Idid returned by Create Document

Example matching the current frontend implementation:

DOCUMENT_ID="3fa85f64-5717-4562-b3fc-2c963f66afa6"
STORAGE_URL="<storageUrl returned by Create Document>"

curl --request POST \
--url "$STORAGE_URL/file" \
--header "Authorization: Bearer $ACCESS_TOKEN" \
--form "file=@./upper-jaw.stl" \
--form "FileName=upper-jaw.stl" \
--form "Type=STL" \
--form "Id=$DOCUMENT_ID"

Do not set the multipart Content-Type header manually; the HTTP client must add the boundary. Confirm the storage contract before using this implementation outside the AXS web client because the Developer Portal does not guarantee these multipart details.

Search documents​

The API specification contains the complete search request contract. The examples below focus on common integration workflows. Always provide offset and limit for predictable pagination.

Search all uploaded documents​

Because public searches include nested paths, searching / returns files recursively from the patient root and all descendant paths.

curl --request POST \
--url "$BASE_URL/v1/patients/$PATIENT_ID/documents/search" \
--header "Authorization: Bearer $ACCESS_TOKEN" \
--header "Organization-ID: $ORGANIZATION_ID" \
--header "Content-Type: application/json" \
--data '{
"offset": 0,
"limit": 30,
"path": "/",
"hasContent": true,
"sortBy": "lastModifiedAt",
"sortDirection": "DESC"
}'

Example response:

{
"documents": [
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"storageUrl": "https://storage.example.com/patient-documents/3fa85f64-5717-4562-b3fc-2c963f66afa6",
"fileName": "upper-jaw.stl",
"fileType": "STL",
"lastModifiedAt": "2026-08-11T09:30:00Z",
"fileSize": 204800,
"storageDownloadUri": "https://storage.example.com/download/upper-jaw.stl?signature=abc123",
"thumbnailDownloadUri": null,
"path": "/Cases/2026/Initial-Scan"
}
],
"offset": 0,
"limit": 30,
"total": 1
}

storageDownloadUri and thumbnailDownloadUri are time-limited. A thumbnail can be null when the file type does not support previews.

Search within a folder subtree​

This search returns files in /Cases/2026 and its descendant paths, including /Cases/2026/Initial-Scan:

curl --request POST \
--url "$BASE_URL/v1/patients/$PATIENT_ID/documents/search" \
--header "Authorization: Bearer $ACCESS_TOKEN" \
--header "Organization-ID: $ORGANIZATION_ID" \
--header "Content-Type: application/json" \
--data '{
"offset": 0,
"limit": 30,
"path": "/Cases/2026",
"hasContent": true,
"sortBy": "fileName",
"sortDirection": "ASC"
}'

Search by filename and type​

{
"offset": 0,
"limit": 30,
"fileName": "upper-jaw",
"fileType": "STL",
"hasContent": true
}

Find specific documents by ID​

{
"offset": 0,
"limit": 30,
"id": [
"3fa85f64-5717-4562-b3fc-2c963f66afa6",
"24a92682-69a9-4cd5-8100-cf1b25c1fd8f"
]
}

When id contains values, ID lookup takes precedence over filename, path, content-status, and file-type filters.

Traverse folders and files​

The public API returns files, not folder nodes. A result has a path, but it does not have isFolder, child collections, or a folder ID. Empty folders are therefore not discoverable through the public search API.

Use path-based traversal:

  1. Search path: "/" to obtain all files recursively.
  2. Build the visible folder tree by grouping documents by their path values.
  3. Search a selected path to load only that subtree.
  4. Continue paging until all records represented by total have been read.

For example, these results:

[
{ "fileName": "consent-form.pdf", "path": "/" },
{ "fileName": "upper-jaw.stl", "path": "/Cases/2026/Initial-Scan" },
{ "fileName": "lower-jaw.stl", "path": "/Cases/2026/Initial-Scan" },
{ "fileName": "treatment-plan.pdf", "path": "/Cases/2026/Planning" }
]

can be presented as:

/
|-- consent-form.pdf
`-- Cases
`-- 2026
|-- Initial-Scan
| |-- upper-jaw.stl
| `-- lower-jaw.stl
`-- Planning
`-- treatment-plan.pdf

Searching /Cases/2026/Initial-Scan returns both jaw scans. Searching /Cases/2026 returns files from both Initial-Scan and Planning because nested search is enabled by the public API.

Pagination example​

The response total is the total number of matching documents, not the number in the current page.

offset = 0
limit = 30

repeat:
response = search(offset, limit, path)
process response.documents
offset = offset + response.documents.length
until offset >= response.total or response.documents is empty

For stable traversal across multiple pages, specify the same sortBy and sortDirection on every request.

Verify a completed upload​

After uploading the bytes, search by the returned document ID with hasContent: true:

curl --request POST \
--url "$BASE_URL/v1/patients/$PATIENT_ID/documents/search" \
--header "Authorization: Bearer $ACCESS_TOKEN" \
--header "Organization-ID: $ORGANIZATION_ID" \
--header "Content-Type: application/json" \
--data "{
\"offset\": 0,
\"limit\": 1,
\"id\": [\"$DOCUMENT_ID\"],
\"hasContent\": true
}"

A matching result confirms that the document is searchable. Check fileSize and storageDownloadUri before treating the upload as complete.

Error handling​

Public API errors use a problem-details response containing fields such as type, title, status, detail, instance, messageKey, and occurredAt.

StatusMeaningTypical action
400 Bad RequestInvalid UUID, blank filename, invalid path, invalid pagination, or more than 30 IDs.Correct the request; do not retry unchanged.
401 UnauthorizedMissing, invalid, or expired access token.Obtain a valid token and retry.
403 ForbiddenMissing scope, organization access, patient access, or consent.Correct authorization or consent before retrying.
404 Not FoundPatient or requested document does not exist.Verify identifiers and region.
409 ConflictA duplicate document or conflicting storage operation exists.Search before retrying or choose another filename/path.
413 Payload Too LargeRequest, header, path, or storage upload exceeds a configured limit.Reduce the input or consult the storage limits.
500 Internal Server ErrorUnexpected service or storage failure.Retry with backoff and retain the problem details for support.

Example:

{
"type": "https://docs.straumann.com/errors/validation",
"title": "Bad Request",
"status": 400,
"detail": "Validation failed for field 'fileName': must not be blank",
"instance": "/public/v1/patients/123e4567-e89b-12d3-a456-426614174000/documents",
"messageKey": "VALIDATION_ERROR",
"occurredAt": "2026-08-11T12:36:06.152Z"
}

The instance field can contain a /public/v1 path even though external clients call /v1.

Integration checklist​

  • Select the correct regional pre-production or production base URL.
  • Acquire a token with the required Patient File scope.
  • Send Organization-ID on both create and search requests.
  • Create the placeholder before sending file bytes.
  • Confirm the separate storage upload contract for non-AXS clients.
  • Use hasContent: true when only completed uploads should be shown.
  • Send an explicit offset, limit, and stable sort on every search.
  • Traverse by path; do not expect folder objects from the public search response.
  • Use storageDownloadUri, not storageUrl, to download file content.