curl --request POST \
--url https://api.healos.ai/ext-api/v1/patients/{patientId}/documents \
--header 'Content-Type: multipart/form-data' \
--header 'X-API-Key: <api-key>' \
--form 'file=<unknown>' \
--form external_id=PARTNER-DOC-456import requests
url = "https://api.healos.ai/ext-api/v1/patients/{patientId}/documents"
payload = "-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"file\"\r\n\r\n<unknown>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"external_id\"\r\n\r\nPARTNER-DOC-456\r\n-----011000010111000001101001--"
headers = {
"X-API-Key": "<api-key>",
"Content-Type": "multipart/form-data"
}
response = requests.post(url, data=payload, headers=headers)
print(response.text)const form = new FormData();
form.append('file', '<unknown>');
form.append('external_id', 'PARTNER-DOC-456');
const options = {method: 'POST', headers: {'X-API-Key': '<api-key>'}};
options.body = form;
fetch('https://api.healos.ai/ext-api/v1/patients/{patientId}/documents', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.healos.ai/ext-api/v1/patients/{patientId}/documents",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => "-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"file\"\r\n\r\n<unknown>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"external_id\"\r\n\r\nPARTNER-DOC-456\r\n-----011000010111000001101001--",
CURLOPT_HTTPHEADER => [
"Content-Type: multipart/form-data",
"X-API-Key: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.healos.ai/ext-api/v1/patients/{patientId}/documents"
payload := strings.NewReader("-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"file\"\r\n\r\n<unknown>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"external_id\"\r\n\r\nPARTNER-DOC-456\r\n-----011000010111000001101001--")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("X-API-Key", "<api-key>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.healos.ai/ext-api/v1/patients/{patientId}/documents")
.header("X-API-Key", "<api-key>")
.body("-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"file\"\r\n\r\n<unknown>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"external_id\"\r\n\r\nPARTNER-DOC-456\r\n-----011000010111000001101001--")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.healos.ai/ext-api/v1/patients/{patientId}/documents")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["X-API-Key"] = '<api-key>'
request.body = "-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"file\"\r\n\r\n<unknown>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"external_id\"\r\n\r\nPARTNER-DOC-456\r\n-----011000010111000001101001--"
response = http.request(request)
puts response.read_body{
"data": {
"id": "d4e5f6a7-b8c9-4d0e-1f2a-3b4c5d6e7f8a",
"filename": "intake_form_maria_santos.pdf",
"file_type": "pdf",
"file_size": 245760,
"public_url": "<string>",
"upload_date": "2026-03-20T11:15:00.000Z",
"status": "pending",
"extracted_date": "2026-03-20T11:16:00.000Z",
"extraction_error": "No text could be extracted from the PDF",
"has_extracted_text": true,
"extracted_text_length": 2048,
"external_id": "PARTNER-DOC-456"
},
"message": "Document uploaded successfully; text extraction started"
}{
"error": "<string>"
}{
"error": "<string>"
}{
"error": "<string>"
}{
"error": "external_id already in use",
"existing": {
"id": "<string>",
"external_id": "<string>"
}
}{
"error": "Rate limit exceeded. Please retry after the window resets."
}Upload a document
Upload a document for a patient. Accepts PDF, JPEG, PNG, and DOCX files up to 10MB. Pass an optional external_id form field to attach a caller-supplied identifier (immutable once set; unique per organization).
Text extraction runs asynchronously. This endpoint returns as soon as the file is stored — it does not wait for extraction (which can take 10–30s for a PDF). The returned document is typically in pending status (extraction may occasionally finish before the response is built). Track progress by polling GET /patients/{patientId}/documents (pass ?external_id=<your id> to fetch just this document) until its status is completed (has_extracted_text becomes true) or failed. Note: these endpoints report extraction status and availability (status, has_extracted_text, extracted_text_length) — the extracted text content itself is not returned. You can (re-)trigger extraction at any time with POST /patients/{patientId}/documents/{documentId}/extract.
Rarely, if extraction could not be queued, the upload still succeeds (201) but the response message omits “text extraction started” and the document stays pending with no work scheduled. Check the response message; if extraction was not started, call the /extract endpoint to trigger it (otherwise a poller would wait indefinitely).
Prefer webhooks over polling. Register a DOCUMENT_STATUS_CHANGED outbound webhook to be notified the moment extraction reaches a terminal state — the event payload carries document_id, external_id, status (completed/failed), any error, and a document_url (a direct document lookup when external_id is set, otherwise the patient’s document collection), so no polling loop is required.
curl --request POST \
--url https://api.healos.ai/ext-api/v1/patients/{patientId}/documents \
--header 'Content-Type: multipart/form-data' \
--header 'X-API-Key: <api-key>' \
--form 'file=<unknown>' \
--form external_id=PARTNER-DOC-456import requests
url = "https://api.healos.ai/ext-api/v1/patients/{patientId}/documents"
payload = "-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"file\"\r\n\r\n<unknown>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"external_id\"\r\n\r\nPARTNER-DOC-456\r\n-----011000010111000001101001--"
headers = {
"X-API-Key": "<api-key>",
"Content-Type": "multipart/form-data"
}
response = requests.post(url, data=payload, headers=headers)
print(response.text)const form = new FormData();
form.append('file', '<unknown>');
form.append('external_id', 'PARTNER-DOC-456');
const options = {method: 'POST', headers: {'X-API-Key': '<api-key>'}};
options.body = form;
fetch('https://api.healos.ai/ext-api/v1/patients/{patientId}/documents', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.healos.ai/ext-api/v1/patients/{patientId}/documents",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => "-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"file\"\r\n\r\n<unknown>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"external_id\"\r\n\r\nPARTNER-DOC-456\r\n-----011000010111000001101001--",
CURLOPT_HTTPHEADER => [
"Content-Type: multipart/form-data",
"X-API-Key: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.healos.ai/ext-api/v1/patients/{patientId}/documents"
payload := strings.NewReader("-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"file\"\r\n\r\n<unknown>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"external_id\"\r\n\r\nPARTNER-DOC-456\r\n-----011000010111000001101001--")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("X-API-Key", "<api-key>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.healos.ai/ext-api/v1/patients/{patientId}/documents")
.header("X-API-Key", "<api-key>")
.body("-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"file\"\r\n\r\n<unknown>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"external_id\"\r\n\r\nPARTNER-DOC-456\r\n-----011000010111000001101001--")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.healos.ai/ext-api/v1/patients/{patientId}/documents")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["X-API-Key"] = '<api-key>'
request.body = "-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"file\"\r\n\r\n<unknown>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"external_id\"\r\n\r\nPARTNER-DOC-456\r\n-----011000010111000001101001--"
response = http.request(request)
puts response.read_body{
"data": {
"id": "d4e5f6a7-b8c9-4d0e-1f2a-3b4c5d6e7f8a",
"filename": "intake_form_maria_santos.pdf",
"file_type": "pdf",
"file_size": 245760,
"public_url": "<string>",
"upload_date": "2026-03-20T11:15:00.000Z",
"status": "pending",
"extracted_date": "2026-03-20T11:16:00.000Z",
"extraction_error": "No text could be extracted from the PDF",
"has_extracted_text": true,
"extracted_text_length": 2048,
"external_id": "PARTNER-DOC-456"
},
"message": "Document uploaded successfully; text extraction started"
}{
"error": "<string>"
}{
"error": "<string>"
}{
"error": "<string>"
}{
"error": "external_id already in use",
"existing": {
"id": "<string>",
"external_id": "<string>"
}
}{
"error": "Rate limit exceeded. Please retry after the window resets."
}Authorizations
API key obtained from /api/v1/ext-api-keys
Path Parameters
Patient ID
"1743552000000"
Body
Response
Document uploaded. The file is stored and, in the normal case, text extraction is queued to run asynchronously — the returned document is then typically pending. If extraction could not be queued, the file is still saved and message reflects that (trigger extraction later via the extract endpoint). Poll the GET endpoint to track extraction status.
Show child attributes
Show child attributes
Human-readable status. text extraction started when extraction was queued; Document uploaded successfully if the file was stored but extraction could not be queued (retry via the extract endpoint).
"Document uploaded successfully; text extraction started"
Was this page helpful?

