set -eu
umask 077
# Path values must be URL-encoded; query string excludes the leading question mark.
url="${HD_BASE_URL:?Set API origin}/v1/partner/files/${HD_QUERY_STRING:+?${HD_QUERY_STRING}}"
printf 'Accept: application/json\n' > "${HD_REQUEST_HEADERS_FILE:?Private header file}"
printf 'Authorization: Bearer %s\n' "${HD_PARTNER_API_KEY:?Set partner API key}" >> "$HD_REQUEST_HEADERS_FILE"
form_args=()
while IFS= read -r -d "" form_value; do
form_args+=(--form-string "$form_value")
done < <(python3 -c 'import json,sys; d=json.load(open(sys.argv[1])); [sys.stdout.write(str(k)+"="+str(v)+"\0") for k,v in d.items() if v is not None]' "${HD_REQUEST_FILE:?JSON form fields}")
curl --silent --show-error --max-time 30 --request POST \
--header "@$HD_REQUEST_HEADERS_FILE" \
--dump-header "${HD_HEADERS_FILE:?Private response header file}" \
--output "${HD_RESPONSE_FILE:?Private response file}" \
--write-out '%{http_code}\n%{content_type}\n' \
--form 'file=@'"${HD_UPLOAD_FILE:?Synthetic upload};type=${HD_UPLOAD_CONTENT_TYPE:?Upload MIME}" \
${form_args[@]+"${form_args[@]}"} \
"$url"import os, json
from pathlib import Path
from urllib.parse import quote
import requests
os.umask(0o077)
url = os.environ["HD_BASE_URL"].rstrip("/") + '/v1/partner/files/'
if os.environ.get("HD_QUERY_STRING"): url += "?" + os.environ["HD_QUERY_STRING"]
headers = {"Accept": 'application/json'}
headers["Authorization"] = "Bearer " + os.environ["HD_PARTNER_API_KEY"]
kwargs = {}
kwargs["files"] = {'file': ("synthetic-upload", Path(os.environ["HD_UPLOAD_FILE"]).read_bytes(), os.environ["HD_UPLOAD_CONTENT_TYPE"])}
kwargs["data"] = json.loads(Path(os.environ["HD_REQUEST_FILE"]).read_text())
response = requests.request('POST', url, headers=headers, timeout=30, allow_redirects=False, **kwargs)
Path(os.environ["HD_RESPONSE_FILE"]).write_bytes(response.content)
Path(os.environ["HD_HEADERS_FILE"]).write_text(json.dumps(dict(response.headers)))
print(response.status_code)
print(response.headers.get("Content-Type", ""))
import { readFileSync, writeFileSync } from "node:fs";
const env = process.env;
let url = env.HD_BASE_URL.replace(/\/$/, "") + "/v1/partner/files/";
if (env.HD_QUERY_STRING) url += "?" + env.HD_QUERY_STRING;
const headers = { "Accept": "application/json" };
headers.Authorization = "Bearer " + env.HD_PARTNER_API_KEY;
const options = { method: "POST", headers, redirect: "manual", signal: AbortSignal.timeout(30000) };
options.body = new FormData();
options.body.append("file", new Blob([readFileSync(env.HD_UPLOAD_FILE)], {type: env.HD_UPLOAD_CONTENT_TYPE}), "synthetic-upload");
for (const [key, value] of Object.entries(JSON.parse(readFileSync(env.HD_REQUEST_FILE, "utf8")))) { if (value != null) options.body.append(key, String(value)); }
const response = await fetch(url, options);
writeFileSync(env.HD_RESPONSE_FILE, Buffer.from(await response.arrayBuffer()), {mode: 0o600});
writeFileSync(env.HD_HEADERS_FILE, JSON.stringify(Object.fromEntries(response.headers)), {mode: 0o600});
console.log(response.status);
console.log(response.headers.get("content-type") ?? "");
<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.hi-doctor.ai/v1/partner/files/",
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\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<JPEG, PNG or PDF bytes>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"purpose\"\r\n\r\nphoto\r\n-----011000010111000001101001--",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: multipart/form-data; boundary=---011000010111000001101001"
],
]);
$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.hi-doctor.ai/v1/partner/files/"
payload := strings.NewReader("-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"file\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<JPEG, PNG or PDF bytes>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"purpose\"\r\n\r\nphoto\r\n-----011000010111000001101001--")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "multipart/form-data; boundary=---011000010111000001101001")
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.hi-doctor.ai/v1/partner/files/")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "multipart/form-data; boundary=---011000010111000001101001")
.body("-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"file\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<JPEG, PNG or PDF bytes>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"purpose\"\r\n\r\nphoto\r\n-----011000010111000001101001--")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.hi-doctor.ai/v1/partner/files/")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'multipart/form-data; boundary=---011000010111000001101001'
request.body = "-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"file\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<JPEG, PNG or PDF bytes>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"purpose\"\r\n\r\nphoto\r\n-----011000010111000001101001--"
response = http.request(request)
puts response.read_body{
"upload_id": "3d4e5f60-7182-4934-a5b6-c7d8e9f0a1b2",
"object": "upload",
"purpose": "photo",
"content_type": "image/jpeg",
"bytes": 482113,
"sha256": "<HASH>",
"width": 1600,
"height": 1200,
"status": "pending",
"expires_at": "2026-11-04T10:15:00Z"
}Upload a photo or document
Uploads one file as multipart/form-data and returns an upload_id. Use the id as the answer to an upload question or in an information request answer. Pending uploads expire after 24 hours. Pending uploads count towards your storage quota (refused with 422 storage_quota_exceeded when full); an expired upload stops counting at expires_at, can no longer be used (422 upload_unusable) and is deleted, file included, by a periodic cleanup. Attached uploads are kept with the consultation. Requires the consultations:write scope.
Access, effects and retries
Ownership: The API key’s own partner and mode (test or live).
Prerequisites: A partner API key with the consultations:write scope.
Effects: Stores one pending upload that counts towards the storage quota until it is attached or expires.
Retries: Not replayed by Idempotency-Key: a retry stores a second upload. Unused uploads expire and are cleaned up.
set -eu
umask 077
# Path values must be URL-encoded; query string excludes the leading question mark.
url="${HD_BASE_URL:?Set API origin}/v1/partner/files/${HD_QUERY_STRING:+?${HD_QUERY_STRING}}"
printf 'Accept: application/json\n' > "${HD_REQUEST_HEADERS_FILE:?Private header file}"
printf 'Authorization: Bearer %s\n' "${HD_PARTNER_API_KEY:?Set partner API key}" >> "$HD_REQUEST_HEADERS_FILE"
form_args=()
while IFS= read -r -d "" form_value; do
form_args+=(--form-string "$form_value")
done < <(python3 -c 'import json,sys; d=json.load(open(sys.argv[1])); [sys.stdout.write(str(k)+"="+str(v)+"\0") for k,v in d.items() if v is not None]' "${HD_REQUEST_FILE:?JSON form fields}")
curl --silent --show-error --max-time 30 --request POST \
--header "@$HD_REQUEST_HEADERS_FILE" \
--dump-header "${HD_HEADERS_FILE:?Private response header file}" \
--output "${HD_RESPONSE_FILE:?Private response file}" \
--write-out '%{http_code}\n%{content_type}\n' \
--form 'file=@'"${HD_UPLOAD_FILE:?Synthetic upload};type=${HD_UPLOAD_CONTENT_TYPE:?Upload MIME}" \
${form_args[@]+"${form_args[@]}"} \
"$url"import os, json
from pathlib import Path
from urllib.parse import quote
import requests
os.umask(0o077)
url = os.environ["HD_BASE_URL"].rstrip("/") + '/v1/partner/files/'
if os.environ.get("HD_QUERY_STRING"): url += "?" + os.environ["HD_QUERY_STRING"]
headers = {"Accept": 'application/json'}
headers["Authorization"] = "Bearer " + os.environ["HD_PARTNER_API_KEY"]
kwargs = {}
kwargs["files"] = {'file': ("synthetic-upload", Path(os.environ["HD_UPLOAD_FILE"]).read_bytes(), os.environ["HD_UPLOAD_CONTENT_TYPE"])}
kwargs["data"] = json.loads(Path(os.environ["HD_REQUEST_FILE"]).read_text())
response = requests.request('POST', url, headers=headers, timeout=30, allow_redirects=False, **kwargs)
Path(os.environ["HD_RESPONSE_FILE"]).write_bytes(response.content)
Path(os.environ["HD_HEADERS_FILE"]).write_text(json.dumps(dict(response.headers)))
print(response.status_code)
print(response.headers.get("Content-Type", ""))
import { readFileSync, writeFileSync } from "node:fs";
const env = process.env;
let url = env.HD_BASE_URL.replace(/\/$/, "") + "/v1/partner/files/";
if (env.HD_QUERY_STRING) url += "?" + env.HD_QUERY_STRING;
const headers = { "Accept": "application/json" };
headers.Authorization = "Bearer " + env.HD_PARTNER_API_KEY;
const options = { method: "POST", headers, redirect: "manual", signal: AbortSignal.timeout(30000) };
options.body = new FormData();
options.body.append("file", new Blob([readFileSync(env.HD_UPLOAD_FILE)], {type: env.HD_UPLOAD_CONTENT_TYPE}), "synthetic-upload");
for (const [key, value] of Object.entries(JSON.parse(readFileSync(env.HD_REQUEST_FILE, "utf8")))) { if (value != null) options.body.append(key, String(value)); }
const response = await fetch(url, options);
writeFileSync(env.HD_RESPONSE_FILE, Buffer.from(await response.arrayBuffer()), {mode: 0o600});
writeFileSync(env.HD_HEADERS_FILE, JSON.stringify(Object.fromEntries(response.headers)), {mode: 0o600});
console.log(response.status);
console.log(response.headers.get("content-type") ?? "");
<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.hi-doctor.ai/v1/partner/files/",
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\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<JPEG, PNG or PDF bytes>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"purpose\"\r\n\r\nphoto\r\n-----011000010111000001101001--",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: multipart/form-data; boundary=---011000010111000001101001"
],
]);
$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.hi-doctor.ai/v1/partner/files/"
payload := strings.NewReader("-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"file\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<JPEG, PNG or PDF bytes>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"purpose\"\r\n\r\nphoto\r\n-----011000010111000001101001--")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "multipart/form-data; boundary=---011000010111000001101001")
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.hi-doctor.ai/v1/partner/files/")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "multipart/form-data; boundary=---011000010111000001101001")
.body("-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"file\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<JPEG, PNG or PDF bytes>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"purpose\"\r\n\r\nphoto\r\n-----011000010111000001101001--")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.hi-doctor.ai/v1/partner/files/")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'multipart/form-data; boundary=---011000010111000001101001'
request.body = "-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"file\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<JPEG, PNG or PDF bytes>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"purpose\"\r\n\r\nphoto\r\n-----011000010111000001101001--"
response = http.request(request)
puts response.read_body{
"upload_id": "3d4e5f60-7182-4934-a5b6-c7d8e9f0a1b2",
"object": "upload",
"purpose": "photo",
"content_type": "image/jpeg",
"bytes": 482113,
"sha256": "<HASH>",
"width": 1600,
"height": 1200,
"status": "pending",
"expires_at": "2026-11-04T10:15:00Z"
}Authorizations
Partner API key sent as Authorization: Bearer . Test keys and live keys are separate.
Headers
Accepted but not used for replay on this operation. Only POST /v1/partner/consultations stores and replays responses by Idempotency-Key.
^[A-Za-z0-9_-]{8,128}$Body
Response
The stored upload.
The upload id; use it as the answer to an upload question.
Always upload.
upload photo or document.
photo, document Detected content type of the file.
File size in bytes.
SHA-256 hex digest of the file.
Image width in pixels; null for documents.
Image height in pixels; null for documents.
pending until a consultation or information request answer uses it (attached). An upload never used before expires_at is expired: it no longer counts towards the storage quota, cannot be used, and is deleted with its file by a periodic cleanup.
pending, attached, expired When the upload expires if it is still unused.