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/questionnaire/consultations/${HD_PATH_ID:?Set the path fixture}/images/${HD_QUERY_STRING:+?${HD_QUERY_STRING}}"
printf 'X-Brand-Slug: hi-doctor\nAccept: application/json\n' > "${HD_REQUEST_HEADERS_FILE:?Private header file}"
printf 'Authorization: Bearer %s\n' "${HD_ACCESS_TOKEN:?Set backend JWT}" >> "$HD_REQUEST_HEADERS_FILE"
curl --silent --show-error --max-time 30 --request GET \
--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' \
"$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/questionnaire/consultations/{id}/images/'
url = url.replace('{id}', quote(os.environ['HD_PATH_ID'], safe=""))
if os.environ.get("HD_QUERY_STRING"): url += "?" + os.environ["HD_QUERY_STRING"]
headers = {"X-Brand-Slug": "hi-doctor", "Accept": 'application/json'}
headers["Authorization"] = "Bearer " + os.environ["HD_ACCESS_TOKEN"]
kwargs = {}
response = requests.request('GET', 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/questionnaire/consultations/{id}/images/";
url = url.replace("{id}", encodeURIComponent(env.HD_PATH_ID));
if (env.HD_QUERY_STRING) url += "?" + env.HD_QUERY_STRING;
const headers = { "X-Brand-Slug": "hi-doctor", "Accept": "application/json" };
headers.Authorization = "Bearer " + env.HD_ACCESS_TOKEN;
const options = { method: "GET", headers, redirect: "manual", signal: AbortSignal.timeout(30000) };
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/questionnaire/consultations/{id}/images/",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://api.hi-doctor.ai/v1/questionnaire/consultations/{id}/images/"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("Authorization", "Bearer <token>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://api.hi-doctor.ai/v1/questionnaire/consultations/{id}/images/")
.header("Authorization", "Bearer <token>")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.hi-doctor.ai/v1/questionnaire/consultations/{id}/images/")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["Authorization"] = 'Bearer <token>'
response = http.request(request)
puts response.read_body[
{
"id": "edd88e14-d21c-5c30-91ef-ed80e255aea1",
"url": "https://api.hi-doctor.ai/v1/questionnaire/consultations/b0e81246-c46e-5ac2-9675-06bf07784bf4/images/edd88e14-d21c-5c30-91ef-ed80e255aea1/content/",
"filename": "synthetic-test.png",
"uploaded_at": "2026-10-05T18:25:24.513093Z",
"consultation_id": "b0e81246-c46e-5ac2-9675-06bf07784bf4",
"available": true
}
]{
"detail": "Authentication credentials were not provided."
}{
"detail": "You do not have permission to perform this action."
}{
"detail": "No Consultation matches the given query."
}{
"detail": "Request was throttled."
}List consultation photos
Every photograph attached to one of your consultations, oldest first.
The dermatology pathways ask for photographs of the affected skin, and this is where they end up once the consultation exists. Each entry carries a url pointing at the content route below rather than a storage link, so the bytes stay behind the same access check as the consultation itself.
Access, effects and retries
Ownership: Patient owns the consultation.
Prerequisites: Authentication and permission to access the consultation are required.
Effects: Read merged questionnaire and post-submit images.
Retries: Safe read for a resource owned by the patient.
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/questionnaire/consultations/${HD_PATH_ID:?Set the path fixture}/images/${HD_QUERY_STRING:+?${HD_QUERY_STRING}}"
printf 'X-Brand-Slug: hi-doctor\nAccept: application/json\n' > "${HD_REQUEST_HEADERS_FILE:?Private header file}"
printf 'Authorization: Bearer %s\n' "${HD_ACCESS_TOKEN:?Set backend JWT}" >> "$HD_REQUEST_HEADERS_FILE"
curl --silent --show-error --max-time 30 --request GET \
--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' \
"$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/questionnaire/consultations/{id}/images/'
url = url.replace('{id}', quote(os.environ['HD_PATH_ID'], safe=""))
if os.environ.get("HD_QUERY_STRING"): url += "?" + os.environ["HD_QUERY_STRING"]
headers = {"X-Brand-Slug": "hi-doctor", "Accept": 'application/json'}
headers["Authorization"] = "Bearer " + os.environ["HD_ACCESS_TOKEN"]
kwargs = {}
response = requests.request('GET', 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/questionnaire/consultations/{id}/images/";
url = url.replace("{id}", encodeURIComponent(env.HD_PATH_ID));
if (env.HD_QUERY_STRING) url += "?" + env.HD_QUERY_STRING;
const headers = { "X-Brand-Slug": "hi-doctor", "Accept": "application/json" };
headers.Authorization = "Bearer " + env.HD_ACCESS_TOKEN;
const options = { method: "GET", headers, redirect: "manual", signal: AbortSignal.timeout(30000) };
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/questionnaire/consultations/{id}/images/",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://api.hi-doctor.ai/v1/questionnaire/consultations/{id}/images/"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("Authorization", "Bearer <token>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://api.hi-doctor.ai/v1/questionnaire/consultations/{id}/images/")
.header("Authorization", "Bearer <token>")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.hi-doctor.ai/v1/questionnaire/consultations/{id}/images/")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["Authorization"] = 'Bearer <token>'
response = http.request(request)
puts response.read_body[
{
"id": "edd88e14-d21c-5c30-91ef-ed80e255aea1",
"url": "https://api.hi-doctor.ai/v1/questionnaire/consultations/b0e81246-c46e-5ac2-9675-06bf07784bf4/images/edd88e14-d21c-5c30-91ef-ed80e255aea1/content/",
"filename": "synthetic-test.png",
"uploaded_at": "2026-10-05T18:25:24.513093Z",
"consultation_id": "b0e81246-c46e-5ac2-9675-06bf07784bf4",
"available": true
}
]{
"detail": "Authentication credentials were not provided."
}{
"detail": "You do not have permission to perform this action."
}{
"detail": "No Consultation matches the given query."
}{
"detail": "Request was throttled."
}Authorizations
Bearer authentication header of the form Bearer <token>, where <token> is your auth token.
Headers
Send X-Brand-Slug: hi-doctor. Public auth/questionnaire/attribution routes resolve the brand header; authenticated scope also uses the live user brand. Do not claim the header is universally server-required: default-brand fallback exists.
hi-doctor Path Parameters
Identifier returned by the corresponding authorized collection; use only a record visible to the signed-in patient.
Response
Authorized consultation image metadata.
Image identifier.
Authorized image content URL.
Display filename.
Upload timestamp.
Owning consultation identifier.
Whether this image remains available.
Present for an image originally supplied through the questionnaire.
Question key for a questionnaire image; null if unavailable.