Developers

Learn · RxPhoto

Recipes — RxPhoto

The reference documents one endpoint at a time. Real features chain several together. Here are end-to-end workflows for the RxPhoto API — showing how one call's output feeds the next.

📎 Field names are illustrative

Request/response fields below are examples to show the flow. Confirm the exact required fields for each resource in the RxPhoto reference before building against it.

The setup these recipes share

Every snippet below assumes you've already built an authenticated client, exactly as in the RxPhoto quickstart — so we don't repeat auth in each step. It's a client with your token set as the Authorization header on every call:

# Each curl below omits this for brevity — add it to every request:
#   -H "Authorization: Token YOUR_TOKEN"
# Base URL for the paths below:  https://api.rxphoto.com/api/v3
const base = "https://api.rxphoto.com/api/v3";
const authHeader = { Authorization: "Token YOUR_TOKEN" };
const json = { ...authHeader, "Content-Type": "application/json" };
import requests

base = "https://api.rxphoto.com/api/v3"
s = requests.Session()
s.headers.update({"Authorization": "Token YOUR_TOKEN"})   # sent on every request
using System;
using System.Net.Http;

var http = new HttpClient {
  BaseAddress = new Uri("https://api.rxphoto.com/api/v3/")
};
http.DefaultRequestHeaders.Add("Authorization", "Token YOUR_TOKEN");

1 · Find or create a patient

Most workflows start from a patient. You can search first and reuse a match, but you don't have to guard against duplicates yourself: the create endpoint returns a validation error when first_name, last_name and birth_date all match an existing record. If you intend to create the record anyway, resend the request with force_create=true.

# 1. Look for an existing patient by name
curl "https://api.rxphoto.com/api/v3/patient/?last_name__iexact=Smith&first_name__istartswith=Jane&page_size=5" \
  -H "Authorization: Token YOUR_TOKEN"
# → { "count": 1, "results": [ { "id": 12345, "first_name": "Jane", ... } ] }

# 2. Create one only if nothing matched
curl -X POST "https://api.rxphoto.com/api/v3/patient/" \
  -H "Authorization: Token YOUR_TOKEN" -H "Content-Type: application/json" \
  -d '{"first_name": "Jane", "last_name": "Smith", "birth_date": "1985-03-15", "email": "jane.smith@example.com"}'
# → { "id": 12345, ... }   ← reuse this integer id below
// 1. Look for an existing patient by name
const qs = new URLSearchParams({ last_name__iexact: "Smith", first_name__istartswith: "Jane", page_size: "5" });
const searchRes = await fetch(`${base}/patient/?${qs}`, { headers: authHeader });
const search = await searchRes.json();

// 2. Create one only if nothing matched
let patient = search.results[0];
if (!patient) {
  const createRes = await fetch(`${base}/patient/`, {
    method: "POST",
    headers: json,
    body: JSON.stringify({ first_name: "Jane", last_name: "Smith", birth_date: "1985-03-15", email: "jane.smith@example.com" }),
  });
  patient = await createRes.json();
}
const patientId = patient.id;   // integer id, reused below
# 1. Look for an existing patient by name
res = s.get(f"{base}/patient/", params={
    "last_name__iexact": "Smith",
    "first_name__istartswith": "Jane",
    "page_size": 5,
})
matches = res.json()["results"]

# 2. Create one only if nothing matched
if matches:
    patient = matches[0]
else:
    patient = s.post(f"{base}/patient/", json={
        "first_name": "Jane",
        "last_name": "Smith",
        "birth_date": "1985-03-15",
        "email": "jane.smith@example.com",
    }).json()

patient_id = patient["id"]   # integer id, reused below
using System.Net.Http.Json;

// 1. Look for an existing patient by name
var found = await http.GetFromJsonAsync<JsonElement>(
    "patient/?last_name__iexact=Smith&first_name__istartswith=Jane&page_size=5");
var results = found.GetProperty("results");

// 2. Create one only if nothing matched
JsonElement patient;
if (results.GetArrayLength() > 0) {
    patient = results[0];
} else {
    var created = await http.PostAsJsonAsync("patient/", new {
        first_name = "Jane", last_name = "Smith", birth_date = "1985-03-15", email = "jane.smith@example.com",
    });
    patient = await created.Content.ReadFromJsonAsync<JsonElement>();
}
var patientId = patient.GetProperty("id").GetInt32();   // integer id, reused below
ℹ️ The API de-duplicates for you

An explicit search isn't required just to avoid duplicates — the create endpoint rejects a patient whose first_name, last_name and birth_date exactly match an existing one, so you learn about the clash from the 400 response. Pass force_create=true to create the record despite the match. The __iexact / __istartswith filters (see Filtering) are still useful when you want to find and reuse an existing record rather than add one.

RxPhoto's signature feature is the before/after comparison. The pieces: images belong to a patient, a gallery holds a set of comparisons, and each image pair links a "before" and an "after" image inside a gallery. So you create the gallery first, then add pairs to it. The add_pair action requires three query parameters — the two image IDs (before_id, after_id) and a procedure_details label — and a JSON body (the GalleryCreateUpdate shape, whose only required field is name).

📎 Fetch a patient's media via shortcut endpoints

To read the images, videos, notes, forms and so on that belong to a patient, the suggested approach is the shortcut endpoints nested under the patient rather than filtering the top-level collections:

  • GET /api/v3/patient/{id}/images/
  • GET /api/v3/patient/{id}/videos/

Unlike the paginated top-level collections, these shortcuts return a bare JSON array (no { count, next, previous, results } envelope) — iterate the decoded array directly. The only documented query parameter is expand. See the reference for the full set of patient shortcuts.

# 1. List the patient's images to pick a before/after set.
#    The patient shortcut endpoint is the preferred way to fetch a
#    patient's media (images, videos, notes, forms, …).
curl "https://api.rxphoto.com/api/v3/patient/PATIENT_ID/images/" \
  -H "Authorization: Token YOUR_TOKEN"

# 2. Create a gallery (only "name" is required)
curl -X POST "https://api.rxphoto.com/api/v3/gallery/" \
  -H "Authorization: Token YOUR_TOKEN" -H "Content-Type: application/json" \
  -d '{"name": "Consult - session 1"}'

# 3. Add a before/after pair to the gallery. before_id, after_id and
#    procedure_details are required query params; a JSON body (name required) is
#    also required.
curl -X POST "https://api.rxphoto.com/api/v3/gallery/GALLERY_ID/add_pair/?before_id=BEFORE_ID&after_id=AFTER_ID&procedure_details=Rhinoplasty" \
  -H "Authorization: Token YOUR_TOKEN" -H "Content-Type: application/json" \
  -d '{"name": "Consult - session 1"}'
// 1. List the patient's images to pick a before/after set.
//    The patient shortcut endpoint is the preferred way to fetch a
//    patient's media (images, videos, notes, forms, …).
const imgRes = await fetch(`${base}/patient/${patientId}/images/`, { headers: authHeader });
const imgs = await imgRes.json();   // bare array, not a {results} envelope
const beforeId = imgs[0].id;
const afterId = imgs[imgs.length - 1].id;

// 2. Create a gallery (only "name" is required)
const galleryRes = await fetch(`${base}/gallery/`, {
  method: "POST",
  headers: json,
  body: JSON.stringify({ name: "Consult — session 1" }),
});
const gallery = await galleryRes.json();

// 3. Add a before/after pair to the gallery. before_id, after_id and
//    procedure_details are required query params; a JSON body (name required)
//    is also required.
const addQs = new URLSearchParams({
  before_id: beforeId,
  after_id: afterId,
  procedure_details: "Rhinoplasty",
});
await fetch(`${base}/gallery/${gallery.id}/add_pair/?${addQs}`, {
  method: "POST",
  headers: json,
  body: JSON.stringify({ name: "Consult — session 1" }),
});
# 1. List the patient's images to pick a before/after set.
#    The patient shortcut endpoint is the preferred way to fetch a
#    patient's media (images, videos, notes, forms, …).
imgs = s.get(f"{base}/patient/{patient_id}/images/").json()   # bare array, not a {results} envelope
before_id, after_id = imgs[0]["id"], imgs[-1]["id"]

# 2. Create a gallery (only "name" is required)
gallery = s.post(f"{base}/gallery/", json={
    "name": "Consult — session 1",
}).json()

# 3. Add a before/after pair to the gallery. before_id, after_id and
#    procedure_details are required query params; a JSON body (name required)
#    is also required.
s.post(
    f"{base}/gallery/{gallery['id']}/add_pair/",
    params={
        "before_id": before_id,
        "after_id": after_id,
        "procedure_details": "Rhinoplasty",
    },
    json={"name": "Consult — session 1"},
)
// 1. List the patient's images to pick a before/after set.
//    The patient shortcut endpoint is the preferred way to fetch a
//    patient's media (images, videos, notes, forms, …).
// bare array, not a {results} envelope — use the decoded array directly
var imgs = await http.GetFromJsonAsync<JsonElement>($"patient/{patientId}/images/");
var beforeId = imgs[0].GetProperty("id").GetInt32();
var afterId = imgs[imgs.GetArrayLength() - 1].GetProperty("id").GetInt32();

// 2. Create a gallery (only "name" is required)
var galRes = await http.PostAsJsonAsync("gallery/", new { name = "Consult — session 1" });
var gallery = await galRes.Content.ReadFromJsonAsync<JsonElement>();
var galleryId = gallery.GetProperty("id").GetInt32();

// 3. Add a before/after pair to the gallery. before_id, after_id and
//    procedure_details are required query params; a JSON body (name required)
//    is also required.
await http.PostAsJsonAsync(
    $"gallery/{galleryId}/add_pair/?before_id={beforeId}&after_id={afterId}&procedure_details=Rhinoplasty",
    new { name = "Consult — session 1" });
📎 Gallery sub-actions

Galleries expose action endpoints beyond plain CRUD — for example /gallery/{id}/pairs/ to list its pairs and /gallery/{id}/set_procedure/ to tag it. See the reference for the full set and each body.

3 · Share a gallery

Once a gallery is assembled you can share it or export it. RxPhoto offers both app/web sharing and file exports. The file exports require a template query parameter (the integer ID of the export template to render with).

# Share on the patient-facing web viewer (share_on_web is a GET)
curl "https://api.rxphoto.com/api/v3/gallery/GALLERY_ID/share_on_web/" \
  -H "Authorization: Token YOUR_TOKEN"

# ...or export the gallery as a file (template is a required query param)
curl "https://api.rxphoto.com/api/v3/gallery/GALLERY_ID/download_pdf/?template=TEMPLATE_ID" \
  -H "Authorization: Token YOUR_TOKEN" -o gallery.pdf   # application/pdf
curl "https://api.rxphoto.com/api/v3/gallery/GALLERY_ID/download_zip/?template=TEMPLATE_ID" \
  -H "Authorization: Token YOUR_TOKEN" -o gallery.zip   # image archive
const galleryId = gallery.id;

// Share on the patient-facing web viewer (share_on_web is a GET)
await fetch(`${base}/gallery/${galleryId}/share_on_web/`, { headers: authHeader });

// ...or export the gallery as a file (template is a required query param)
const pdf = await fetch(`${base}/gallery/${galleryId}/download_pdf/?template=TEMPLATE_ID`, { headers: authHeader });  // application/pdf
const zip = await fetch(`${base}/gallery/${galleryId}/download_zip/?template=TEMPLATE_ID`, { headers: authHeader });  // image archive
gallery_id = gallery["id"]

# Share on the patient-facing web viewer (share_on_web is a GET)
s.get(f"{base}/gallery/{gallery_id}/share_on_web/")

# ...or export the gallery as a file (template is a required query param)
pdf = s.get(f"{base}/gallery/{gallery_id}/download_pdf/", params={"template": TEMPLATE_ID})   # application/pdf
zip_ = s.get(f"{base}/gallery/{gallery_id}/download_zip/", params={"template": TEMPLATE_ID})  # image archive
// Share on the patient-facing web viewer (share_on_web is a GET)
await http.GetAsync($"gallery/{galleryId}/share_on_web/");

// ...or export the gallery as a file (template is a required query param)
var pdf = await http.GetByteArrayAsync($"gallery/{galleryId}/download_pdf/?template=TEMPLATE_ID");  // application/pdf
var zip = await http.GetByteArrayAsync($"gallery/{galleryId}/download_zip/?template=TEMPLATE_ID");  // image archive
ℹ️ Exports can be asynchronous

Bulk and archive exports may be prepared in the background. Endpoints such as /gallery/download_status/ let you poll for readiness before you fetch the file. Check each endpoint's response in the reference.