Consolidated assessments API¶
Get an AI skin-cancer assessment with one POST (inline mode) or one POST + N uploads + one submit (slot mode). Both modes return the same aggregated response shape via GET or webhook.
Prerequisites¶
- OAuth2 client credentials with scopes
assessments:write(to create/submit) andassessments:read(to read). - The platform host root, e.g.
https://platform.example.com. All paths below are relative to that root.
If you have not set up OAuth2 credentials yet, see direct-api.md for the token-exchange walkthrough.
Two modes at a glance¶
| Mode | Calls | Use when |
|---|---|---|
| Inline | 1 — POST /v1/clinical-api/assessments with images[].data |
Images are already in-process / small enough to embed as base64 |
| Slot | POST → PUT × N → POST …/submit |
Images are large, come from a camera SDK, or you want to pipeline uploads |
Both modes accept the same request body shape. In inline mode you add base64 bytes in images[].data. In slot mode you omit data and the platform returns a presigned upload_url per image; when all uploads are done, call submit to finalise.
Authentication¶
Authorization: Bearer <access_token>
Obtain a token from POST /v1/auth/oauth/token (see direct-api.md, Step 1).
Idempotency¶
Both POST /v1/clinical-api/assessments and POST /v1/clinical-api/assessments/:id/submit accept an Idempotency-Key header. If the same key is received twice, the second call returns the existing record unchanged instead of creating a duplicate.
Idempotency-Key: <client-generated-uuid>
Minimum required fields¶
| Field | Type | Description |
|---|---|---|
patient or patient_id |
object | UUID string | Either embed patient data inline or reference an existing patient record |
patient.given_name |
string | Required if using inline patient |
patient.family_name |
string | Required if using inline patient |
patient.dob |
string (ISO 8601 date) | Required if using inline patient, e.g. "1985-06-15" |
finding.finding_type |
lesion | rash | patch | blemish | other |
Type of skin finding |
images[] |
array, min 1 item | At least one image declaration |
images[].capture_type |
dermoscopic | macroscopic | other |
How the image was captured |
images[].mime_type |
string | IANA media type, e.g. image/jpeg |
images[].data |
base64 string | Inline mode only. Omit for slot mode. |
Optional fields¶
| Field | Description |
|---|---|
patient.identifiers[] |
Array of { scheme, value } external identifiers (e.g. NHS number) |
external_reference |
Your own case reference (max 255 chars); passed through to the result |
clinical_context |
Free-form JSON object for clinical metadata (e.g. patient history notes) |
custom_fields |
Per-(org, product) key/value string pairs (configured in the admin UI) |
finding.body_site_id |
UUID of a body site record from the platform's body-site catalogue |
Mode 1 — Inline (single POST)¶
Embed base64 image bytes directly in the request. The platform stores the bytes, attaches them to the case, and begins processing immediately.
Request¶
POST /v1/clinical-api/assessments
Authorization: Bearer <access_token>
Content-Type: application/json
Idempotency-Key: f47ac10b-58cc-4372-a567-0e02b2c3d479
{
"patient": {
"given_name": "Alice",
"family_name": "Anderson",
"dob": "1985-06-15",
"identifiers": [{ "scheme": "NHS", "value": "999 999 9999" }]
},
"finding": { "finding_type": "lesion" },
"external_reference": "EHR-12345",
"images": [
{
"capture_type": "dermoscopic",
"mime_type": "image/jpeg",
"data": "/9j/4AAQSkZJRgABAQAAAQABAAD..."
}
]
}
Response — 202 Accepted¶
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"status": "processing",
"patient_id": "11112222-3333-4444-5555-666677778888",
"case_id": "aaaabbbb-cccc-dddd-eeee-ffff00001111",
"external_reference": "EHR-12345",
"images": [
{
"id": "img-0001-0002-0003-0004-000500060007",
"capture_type": "dermoscopic",
"status": "received"
}
],
"result": null,
"error": null,
"next_poll_after_ms": 5000
}
Because you provided image bytes, the assessment moves directly to processing. No further action is required — await the webhook or poll the GET endpoint.
Mode 2 — Slot (POST + uploads + submit)¶
Declare images without bytes. The platform reserves slots and returns a presigned PUT URL for each image. Upload directly to storage, then call submit.
Step 1 — Create the assessment (declare images, no bytes)¶
POST /v1/clinical-api/assessments
Authorization: Bearer <access_token>
Content-Type: application/json
Idempotency-Key: b8e1f2a3-9c4d-5e6f-7a8b-9c0d1e2f3a4b
{
"patient": {
"given_name": "Bob",
"family_name": "Baker",
"dob": "1972-11-30"
},
"finding": { "finding_type": "lesion", "body_site_id": "bsite-arm-upper-left" },
"images": [
{ "capture_type": "dermoscopic", "mime_type": "image/jpeg" },
{ "capture_type": "macroscopic", "mime_type": "image/png" }
]
}
Response — 202 Accepted¶
{
"id": "c1d2e3f4-a5b6-7890-cdef-012345678901",
"status": "awaiting_images",
"patient_id": "22223333-4444-5555-6666-777788889999",
"case_id": null,
"images": [
{
"id": "img-1111-2222-3333-4444-555566667777",
"capture_type": "dermoscopic",
"status": "awaiting_upload",
"upload_url": "https://storage.example.com/uploads/img-1111...?X-Amz-Signature=..."
},
{
"id": "img-aaaa-bbbb-cccc-dddd-eeeeffff0000",
"capture_type": "macroscopic",
"status": "awaiting_upload",
"upload_url": "https://storage.example.com/uploads/img-aaaa...?X-Amz-Signature=..."
}
],
"result": null,
"error": null
}
The upload_urls are presigned S3 PUT URLs valid for approximately 5 minutes.
Step 2 — PUT image bytes to storage (one call per image)¶
PUT https://storage.example.com/uploads/img-1111...?X-Amz-Signature=...
Content-Type: image/jpeg
<raw image bytes>
Expected response: 200 OK (no body). Repeat for every image in the images[] array.
No auth header needed on the PUT. The presigned URL encodes all required credentials. Any extra
Authorizationheader will break the request.
Step 3 — Submit¶
POST /v1/clinical-api/assessments/c1d2e3f4-a5b6-7890-cdef-012345678901/submit
Authorization: Bearer <access_token>
Idempotency-Key: d9e2f3a4-0b5c-6d7e-8f9a-0b1c2d3e4f50
No request body is required.
Response — 200 OK¶
{
"id": "c1d2e3f4-a5b6-7890-cdef-012345678901",
"status": "processing",
"patient_id": "22223333-4444-5555-6666-777788889999",
"case_id": "bbbbcccc-dddd-eeee-ffff-000011112222",
"images": [
{
"id": "img-1111-2222-3333-4444-555566667777",
"capture_type": "dermoscopic",
"status": "received"
},
{
"id": "img-aaaa-bbbb-cccc-dddd-eeeeffff0000",
"capture_type": "macroscopic",
"status": "received"
}
],
"result": null,
"error": null,
"next_poll_after_ms": 5000
}
The status transitions to processing and the case is created in the background.
Result delivery¶
Option A — Webhook (recommended)¶
Subscribe once to receive push notifications when assessments complete or fail:
POST /v1/clinical-api/webhook-subscriptions
Authorization: Bearer <access_token>
Content-Type: application/json
{
"url": "https://your-domain.example/hooks/skin-analytics",
"event_types": ["assessment.completed", "assessment.failed"]
}
The platform will POST the aggregated assessment payload (see GET response shape below) to your endpoint as the webhook body, within seconds of completion.
Webhook delivery uses exponential back-off. See scopes.md for the webhooks:write scope requirement.
Option B — Poll¶
GET /v1/clinical-api/assessments/:id
Authorization: Bearer <access_token>
Suggested strategy: on processing, wait next_poll_after_ms (returned in the previous response) before polling again. On terminal statuses (completed, failed) stop polling.
GET response shape¶
Both polling and webhook delivery use the same JSON shape:
{
"id": "<assessment-uuid>",
"status": "completed",
"patient_id": "<patient-uuid>",
"case_id": "<case-uuid>",
"external_reference": "EHR-12345",
"images": [
{
"id": "<image-uuid>",
"capture_type": "dermoscopic",
"status": "processed",
"download_url": "https://storage.example.com/images/...?X-Amz-Signature=..."
}
],
"result": {
"ai_provider": "derm",
"outcome": {
"malignancy_score": 0.82,
"recommendation": "refer"
},
"completed_at": "2026-06-02T14:37:00.000Z"
},
"error": null
}
When the assessment fails:
{
"id": "<assessment-uuid>",
"status": "failed",
"patient_id": "<patient-uuid>",
"case_id": null,
"images": [],
"result": null,
"error": {
"code": "IMAGE_QUALITY_INSUFFICIENT",
"message": "At least one image did not meet the minimum quality threshold."
}
}
Status values¶
| Status | Meaning |
|---|---|
awaiting_images |
Slot mode: waiting for all image uploads before proceeding |
processing |
Images received; AI pipeline running |
completed |
AI assessment done; result is populated |
failed |
Processing failed; error is populated |
Error handling¶
| HTTP status | Meaning | Action |
|---|---|---|
400 |
Validation failure (missing required field, bad enum value) | Fix the request body |
401 |
Token expired or invalid | Re-issue via POST /v1/auth/oauth/token |
403 |
Token lacks assessments:write or assessments:read |
Check granted scopes with your platform operator |
404 |
Assessment ID not found or belongs to a different org | Verify the id |
409 |
Idempotency-Key collision with a different payload | Use a fresh key or retry with the same payload |
4xx (application/problem+json) |
RFC-7807 error with title, detail, status |
Log detail for diagnosis |
5xx |
Transient server error | Retry with exponential back-off + jitter |
Reference¶
The Postman collection downloadable from the Integration tab (/orgs/:orgId/products/:productId/integration) includes pre-built requests for all three assessment endpoints (POST assessments, POST assessments/:id/submit, GET assessments/:id) with sample bodies matching both modes.
See README.md for the full endpoint catalogue and Postman download instructions.