Developers

Get started · RxPhoto

RxPhoto quickstart

Make your first authenticated RxPhoto API call in under ten minutes — no SDK required.

ℹ️ What is the RxPhoto API?

The RxPhoto (RxP) API lets you build on RxPhoto's clinical photography platform — working with patients, images and before/after image pairs, bundles, galleries, forms, and consultations. Most endpoints sit under the /api/v3/ path and exchange JSON. Authentication is a single token in the Authorization header — no gateway apikey parameter. If you're integrating with PatientNow Essentials, see the PNE quickstart → instead.

Before you start

You need one thing to begin:

⚠️ Treat credentials as secrets

Anyone with your token has the same API access you do. Keep it out of source control, browser code shipped to end users, and logs. Use a dedicated service account rather than a person's credentials where possible.

⚠️ Writes hit live data

The examples below use the base URL https://api.rxphoto.com. Write operations affect real records immediately, so test with records you can clean up.

1 · Get a token

Exchange your username and password for a token by posting to /api/v3/authenticate/user/. The response includes a token field — send it as your Authorization header, prefixed with Token (Authorization: Token YOUR_TOKEN), and reuse it on every call.

# Sign in, then copy the "token" from the response:
curl -X POST "https://api.rxphoto.com/api/v3/authenticate/user/" \
  -H "Content-Type: application/json" \
  -d '{"username": "user@example.com", "password": "YOUR_PASSWORD"}'

# Reuse it on every request (note the "Token " prefix):
#   -H "Authorization: Token YOUR_TOKEN"
const base = "https://api.rxphoto.com/api/v3";

const auth = await fetch(`${base}/authenticate/user/`, {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ username: "user@example.com", password: "YOUR_PASSWORD" }),
});
const { token } = await auth.json();
const authHeader = { Authorization: `Token ${token}` };
import requests

base = "https://api.rxphoto.com/api/v3"
auth = requests.post(f"{base}/authenticate/user/", json={
    "username": "user@example.com",
    "password": "YOUR_PASSWORD",
})
token = auth.json()["token"]

# A session with the token as a default header keeps every call clean:
s = requests.Session()
s.headers.update({"Authorization": f"Token {token}"})
using System;
using System.Net.Http;
using System.Net.Http.Json;

var http = new HttpClient { BaseAddress = new Uri("https://api.rxphoto.com/api/v3/") };
var auth = await http.PostAsJsonAsync("authenticate/user/", new {
    username = "user@example.com",
    password = "YOUR_PASSWORD",
});
var body = await auth.Content.ReadFromJsonAsync<Dictionary<string, object>>();
http.DefaultRequestHeaders.Add("Authorization", $"Token {body["token"]}");

2 · Confirm connectivity

The cleanest first call is GET /api/v3/doctor/me/ — it returns the signed-in doctor, requires valid auth, and confirms your token is correct. It's also cheaper than listing patients, which can be slow without pagination. A 200 OK means you're in. (There's also GET /api/v3/ping/, which does nothing but extend your token's expiry.)

curl "https://api.rxphoto.com/api/v3/doctor/me/" \
  -H "Authorization: Token YOUR_TOKEN"
const res = await fetch(`${base}/doctor/me/`, { headers: authHeader });
if (!res.ok) throw new Error(`HTTP ${res.status}`);
console.log(await res.json());
res = s.get(f"{base}/doctor/me/")
res.raise_for_status()
print(res.json())
var res = await http.GetAsync("doctor/me/");
res.EnsureSuccessStatusCode();
Console.WriteLine(await res.Content.ReadAsStringAsync());

A 401 Unauthorized means your token is missing or invalid. A 403 Forbidden means your account isn't permitted for that resource.

3 · Read real data

List patients. Results are paginated with page and page_size parameters, and each list response wraps the rows in a {count, next, previous, results} envelope. You can narrow results with filters like search or field lookups such as last_name__istartswith:

curl "https://api.rxphoto.com/api/v3/patient/?page=1&page_size=5&last_name__istartswith=sm" \
  -H "Authorization: Token YOUR_TOKEN"
const params = new URLSearchParams({ page: "1", page_size: "5", last_name__istartswith: "sm" });
const res = await fetch(`${base}/patient/?${params}`, { headers: authHeader });
const { count, results } = await res.json();
console.log(`${count} total; showing ${results.length}`);
# s (from step 1) already sends the Authorization header on every call:
res = s.get(f"{base}/patient/", params={
    "page": 1, "page_size": 5, "last_name__istartswith": "sm",
})
body = res.json()
print(body["count"], "total;", len(body["results"]), "shown")
📎 Trailing slashes & integer IDs

RxPhoto endpoints end in a trailing slash (/api/v3/patient/, /api/v3/patient/{id}/). Record IDs are integers — use them as returned, not as opaque strings like in PNE/VISH.

4 · Create a record

Write operations POST a JSON body. On success the API returns the new object, including its assigned integer id that you use for subsequent reads and updates. Check the reference for the exact required fields of the resource you're creating.

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"
      }'
const res = await fetch(`${base}/patient/`, {
  method: "POST",
  headers: { ...authHeader, "Content-Type": "application/json" },
  body: JSON.stringify({
    first_name: "Jane",
    last_name: "Smith",
    birth_date: "1985-03-15",
    email: "jane.smith@example.com",
  }),
});
console.log(res.status, await res.json());
res = s.post(  # s carries the Authorization header from step 1
    f"{base}/patient/",
    json={
        "first_name": "Jane",
        "last_name": "Smith",
        "birth_date": "1985-03-15",
        "email": "jane.smith@example.com",
    },
)
print(res.status_code, res.json())
📎 Field names are illustrative

The exact fields a resource accepts come from its schema in the reference. The names above are examples — confirm required vs. optional fields there before you build against them.

What's next