First-party API
Retina Capture API
The session-authenticated API behind Attunio's investigational retinal screening. It powers the in-app patient capture flow and the native (Capacitor) shell — start a session, finalize a live frame or submit a fundus image, record quality, run investigational analysis, and read history. Only true fundus images are gradeable, and every result is decision support that a licensed clinician must review before it enters the chart.
On this page
Overview
The Retina Capture API is a first-party API — it is not part of the API-key–authenticated Clinical Intelligence suite. It exists to serve the Attunio patient app and the native capture shell, so it authenticates with the patient's own session rather than an att_sk_ key.
All endpoints are rooted at /api/retina/v1 and every read or write is scoped to the authenticated patient. A patient can only ever see or modify their own sessions, captures, and screenings.
Authentication
Requests are authenticated with the patient's Better Auth session cookie — the same session used by the marketplace portal. There are no bearer tokens or API keys for this API. Requests without a valid patient session return 401.
The native (Capacitor) shell shares this contract: it reuses the authenticated web session, so the same endpoints back both the web capture flow and the native app without a second auth model.
Image types & gradeability
Accuracy depends entirely on what the image actually shows. The analysis model measures retinal vasculature and optic-disc morphology (an AutoMorph-style feature extraction feeding a gradient-boosted classifier), so it can only score images that contain the back of the eye.
image type
| Field | Type | Description |
|---|---|---|
| fundus | gradeable | A true retinal image — from a fundus camera or a phone clip-on ophthalmoscope lens. Eligible for morphology analysis and a model score. |
| anterior | not gradeable | A bare-phone photo of the front of the eye (iris/pupil/sclera). Stored for record-keeping, but the model returns no retinal score — it cannot see the vasculature. |
| unknown | not gradeable | Provenance could not be determined; treated conservatively as not gradeable. |
The image type is derived from captureSource: clip_on_lens and fundus_upload ⇒ fundus; a bare patient_capture ⇒ anterior. When an image is not gradeable, the model produces no scores and instead records honest reasons — it never fabricates a retinal finding from a front-of-eye photo.
/capture, /fundus, /analyze, or /history.Capture lifecycle
A capture moves a screening record through a small status ladder:
| Field | Type | Description |
|---|---|---|
| ordered | status | A session/screening exists but no image has been uploaded yet. |
| image_uploaded | status | The best frame was uploaded, but analysis has not completed. |
| analyzed | status | The model produced a result. Awaiting clinician review. |
| reviewed | status | A clinician accepted or rejected the result (terminal). |
The typical flow is: POST /session → POST /capture (which uploads, records quality, and runs analysis in one step) → clinician review. POST /quality and POST /analyze are supplementary — useful for the native shell or to retry analysis if the inline run failed.
Start a session — POST /session
Opens a capture session for the authenticated patient and returns its id. Pass the resolved device capabilities so the server records what the device could actually do (best-effort in the browser).
Body
| Field | Type | Description |
|---|---|---|
| targetEye | "left" | "right" | "both" | Which eye(s) the patient intends to capture. Defaults to "both". |
| deviceInfo | object? | Optional device telemetry (userAgent, platform, screen) stored for QA and debugging. |
| captureCapabilities | object? | Optional resolved CaptureBackend capabilities (torch, manualFocus, hdr, maxResolution) so the server records what the device could actually do. |
curl https://attuniohealth.com/api/retina/v1/session \ -X POST \ -H "Content-Type: application/json" \ --cookie "better-auth.session_token=<patient session>" \ -d '{ "targetEye": "both", "deviceInfo": { "userAgent": "...", "platform": "iPhone" }, "captureCapabilities": { "torch": false, "manualFocus": false, "hdr": false, "maxResolution": { "width": 1920, "height": 1080 } } }'{ "sessionId": "rsess_9f2c41d07ab34e8a91b2"}Get a session — GET /session/:id
Returns one session owned by the patient, including its linked screening (if the capture has been finalized).
curl https://attuniohealth.com/api/retina/v1/session/rsess_9f2c41d07ab34e8a91b2 \ --cookie "better-auth.session_token=<patient session>"{ "id": "rsess_9f2c41d07ab34e8a91b2", "status": "completed", "targetEye": "both", "startedAt": "2026-07-03T15:02:11.000Z", "completedAt": "2026-07-03T15:03:48.000Z", "screening": { "id": "rscr_7c1e58f2a90b4d3e62f4", "status": "analyzed", "statusLabel": "Awaiting clinician review", "imageUrl": "https://blob.vercel-storage.com/retina/...jpg", "imageEye": "both", "reviewDecision": null, "reviewedAt": null, "createdAt": "2026-07-03T15:03:48.000Z" }}Finalize a capture — POST /capture
Uploads the accepted still (as multipart/form-data), records the capture and its quality, creates the investigational screening record, and runs the pluggable analysis model inline. Returns 201 with the new screening and capture ids. If analysis fails, the capture is still saved and the screening is left in image_uploaded so it can be re-analyzed.
This endpoint is for live device capture. Set captureSource to clip_on_lens when a retinal lens is attached (gradeable fundus image); otherwise it defaults to patient_capture (anterior, not gradeable — see Image types & gradeability). To submit an already-captured fundus image by URL, use POST /fundus instead.
multipart/form-data
| Field | Type | Description |
|---|---|---|
| image | file | The accepted still frame as image/jpeg. Required. |
| sessionId | string | The session started via POST /session. Required and must belong to the caller. |
| eye | "left" | "right" | "both" | Eye captured in this frame. Defaults to the session target. |
| method | "auto" | "manual" | Whether the auto-shutter or a manual tap produced the frame. |
| captureSource | "patient_capture" | "clip_on_lens" | How the frame was produced. "patient_capture" (default) is a bare-phone anterior image; "clip_on_lens" produces a gradeable fundus image. Determines the image type and whether the morphology model can score it. |
| width | integer | Pixel width of the captured frame. |
| height | integer | Pixel height of the captured frame. |
| quality | json string? | Optional serialized QualityAssessment for the frame (see the quality object below). |
curl https://attuniohealth.com/api/retina/v1/capture \ -X POST \ --cookie "better-auth.session_token=<patient session>" \ -F "sessionId=rsess_9f2c41d07ab34e8a91b2" \ -F "eye=both" \ -F "method=auto" \ -F "width=1920" \ -F "height=1080" \ -F 'quality={"overallScore":0.86,"grade":"good","sharpness":0.9,"exposure":0.8,"contrast":0.82,"fieldCoverage":0.77,"glareScore":0.05,"motionScore":0.04,"passed":true,"issues":[]}' \ -F "image=@best-frame.jpg;type=image/jpeg"{ "screeningId": "rscr_7c1e58f2a90b4d3e62f4", "captureId": "rcap_2ab90c37e51f4d6c80a1"}Submit a fundus image — POST /fundus
Creates an investigational screening from an already-uploaded fundus image — from a fundus camera or a phone clip-on retinal lens. Because the image contains the retina, this path runs the full morphology analysis inline and produces a gradeable result (still pending clinician review). Upload the file first (e.g. via the client Blob upload), then pass its URL here. Returns 201; captureId is empty because there is no live capture session for an uploaded image.
Body
| Field | Type | Description |
|---|---|---|
| imageUrl | string (url) | Public URL of an already-uploaded fundus image (e.g. a Blob url from the client upload). |
| eye | "left" | "right" | "both" | Eye captured in the image. Defaults to "both". |
| captureSource | "fundus_upload" | "clip_on_lens" | Provenance of the fundus image. Both are gradeable; defaults to "fundus_upload". |
curl https://attuniohealth.com/api/retina/v1/fundus \ -X POST \ -H "Content-Type: application/json" \ --cookie "better-auth.session_token=<patient session>" \ -d '{ "imageUrl": "https://blob.vercel-storage.com/retina/fundus-abc.jpg", "eye": "both", "captureSource": "fundus_upload" }'{ "screeningId": "rscr_a1b2c3d4e5f60718293a", "captureId": ""}Record quality — POST /quality
Persists a standalone quality assessment for a capture. The web flow already embeds quality in the capture call; this endpoint exists for the native shell when quality is graded in a separate step. The capture must belong to the caller.
curl https://attuniohealth.com/api/retina/v1/quality \ -X POST \ -H "Content-Type: application/json" \ --cookie "better-auth.session_token=<patient session>" \ -d '{ "captureId": "rcap_2ab90c37e51f4d6c80a1", "quality": { "overallScore": 0.86, "grade": "good", "sharpness": 0.9, "exposure": 0.8, "contrast": 0.82, "fieldCoverage": 0.77, "glareScore": 0.05, "motionScore": 0.04, "passed": true, "issues": [] } }'{ "qualityAssessmentId": "rqa_5d6e7f8091a2b3c4"}Run analysis — POST /analyze
Runs (or re-runs) the pluggable analysis model on a patient-owned screening and writes the result. Useful after a /capture whose inline analysis failed. The result lands in analyzed awaiting clinician review — scores are never surfaced to the patient here.
curl https://attuniohealth.com/api/retina/v1/analyze \ -X POST \ -H "Content-Type: application/json" \ --cookie "better-auth.session_token=<patient session>" \ -d '{ "screeningId": "rscr_7c1e58f2a90b4d3e62f4" }'{ "screeningId": "rscr_7c1e58f2a90b4d3e62f4", "status": "analyzed", "ran": true}Screening history — GET /history
Lists the authenticated patient's screenings, newest first.
curl https://attuniohealth.com/api/retina/v1/history \ --cookie "better-auth.session_token=<patient session>"{ "screenings": [ { "id": "rscr_7c1e58f2a90b4d3e62f4", "status": "reviewed", "statusLabel": "Reviewed by your clinician", "imageUrl": "https://blob.vercel-storage.com/retina/...jpg", "imageEye": "both", "reviewDecision": "accepted", "reviewedAt": "2026-07-04T14:20:00.000Z", "createdAt": "2026-07-03T15:03:48.000Z" } ]}Partner ingestion — POST /api/v1/retina/ingest
The server-to-server path for clinical-grade inputs. A partner clinic — or a portable fundus device on the partner network — submits a fundus image for one of our patients. This is how we bring in real fundus-camera data, distinct from the in-app patient capture above.
Different auth. Unlike the endpoints above (patient session cookie), this endpoint lives on the developer API and authenticates with a secret Bearer key (Authorization: Bearer att_sk_...). Publishable keys are rejected. It shares the developer API's quota, billing, and usage logging.
Clinician-only output. The response returns a safe summary only — screeningId, status, and gradeability. Model scores, morphology, SHAP, and calibration are stored for the clinician portal and are never returned to the partner. Every ingested screening is pending clinician review.
Body
| Field | Type | Description |
|---|---|---|
| patientRef | string | Attunio patient user id the image belongs to. The clinic-to-patient mapping is configured per partner. |
| imageUrl | string (url)? | Partner-hosted URL of the fundus image; we fetch and copy it into our own store. Provide this or imageBase64. |
| imageBase64 | string? | Inline base64 image (a data: URL is accepted). Alternative to imageUrl. |
| eye | "left" | "right" | "both" | Eye captured. Defaults to "both". |
| source | "partner_network" | "in_clinic" | "portable_device" | Provenance of the capture. All are approved fundus sources. Defaults to "partner_network". |
| imageType | "fundus" | "anterior" | Honesty override. Defaults to fundus for a partner source; set "anterior" for a front-of-eye image (stays not gradeable). |
| deviceModel | string? | Fundus camera / device model, stored for provenance. |
| externalId | string? | Partner's own record id / MRN, stored for reconciliation. |
| indication | string? | Clinical reason for the screen. |
| patientAgeYears | integer? | Patient age, if the model conditions on it. |
curl https://attuniohealth.com/api/v1/retina/ingest \ -X POST \ -H "Authorization: Bearer att_sk_live_..." \ -H "Content-Type: application/json" \ -d '{ "patientRef": "usr_8f2a...", "imageUrl": "https://clinic.example.com/exports/fundus-9021.png", "eye": "both", "source": "in_clinic", "deviceModel": "Topcon NW400", "externalId": "MRN-55213" }'{ "ok": true, "requestId": "req_9f2b7c1d0a3e4f5g6h7i", "data": { "object": "retina.ingestion", "screeningId": "rscr_a1b2c3d4e5", "status": "analyzed", "imageType": "fundus", "gradeable": true, "gradeabilityReasons": [], "requiresClinicianReview": true, "analysisRan": true, "disclaimer": "Investigational, clinician-reviewed decision support — not a diagnosis. ..." }}Objects
The quality object attached to a capture and the screening object returned by reads:
quality
| Field | Type | Description |
|---|---|---|
| overallScore | number | Composite 0–1 quality score for the frame. |
| grade | "excellent" | "good" | "acceptable" | "poor" | Human-readable grade derived from the score. |
| sharpness | number | 0–1 focus/sharpness metric (Laplacian variance, normalized). |
| exposure | number | 0–1 exposure adequacy. |
| contrast | number | 0–1 contrast metric. |
| fieldCoverage | number | 0–1 estimate of how much of the target field is filled. |
| glareScore | number | 0–1 glare presence (lower is better). |
| motionScore | number | 0–1 motion blur estimate (lower is better). |
| passed | boolean | Whether the frame cleared the quality gate. |
| issues | string[] | Human-readable reasons a frame failed, e.g. ["too dark", "glare detected"]. |
screening
| Field | Type | Description |
|---|---|---|
| id | string | Screening id (rscr_…). The clinical record that clinicians review. |
| status | string | Lifecycle status — see the status ladder below. |
| statusLabel | string | Patient-friendly label for the status. |
| imageUrl | string | null | URL of the captured fundus image. |
| imageEye | string | null | Eye associated with the image. |
| reviewDecision | "accepted" | "rejected" | null | The clinician's decision, once reviewed. |
| reviewedAt | ISO date | null | When a clinician completed review. |
| createdAt | ISO date | When the screening record was created. |
Errors
Errors return a non-2xx status with a JSON body of the shape { "error": string }. Ownership failures return 403so patients can never probe others' records.
| Status | Meaning |
|---|---|
| 400 | Bad request — missing image, missing sessionId, or a body that failed validation. |
| 401 | Unauthorized — no valid patient session cookie. |
| 403 | Forbidden — the session/capture/screening does not belong to the caller. |
| 404 | Not found — the requested session id does not exist for this patient. |
| 500 | Server error — unexpected failure finalizing or analyzing. |
{ "error": "Forbidden"}