← Back to Developersv3.3.0-ensemble

V3 API Reference

Automated veterinary cardiac measurement from lateral thoracic radiographs.

V3 is targeted for production on May 1, 2026. Until then, endpoints, response formats, and billing details on this page are subject to change.

What's New

  • Billing (metered, pay-for-value) — All prediction endpoints now return a billing object in the response. You are only charged when the algorithm returns a valid VHS measurement. Failed predictions, rejected images, and rechecks are always free.
  • API Key Security — API keys are now stored as SHA-256 hashes. Raw keys are never stored server-side. Treat your API key like a password — it cannot be recovered if lost.

Authentication

All endpoints require the x-api-key header. Include your API key with every request:

x-api-key: YOUR_API_KEY

API keys are provisioned per customer. Request an API key.

Endpoints Overview

MethodPathDescriptionBillable
GET/v3/healthService health checkNo
POST/v3/predictSingle or batch image predictionYes
POST/v3/predict/streamStreaming batch prediction (SSE)Yes
POST/v3/pop-session/startStart a PoP capture sessionNo
POST/v3/pop-sessionSend a frame to a PoP sessionYes
POST/v3/cornersDetect screen cornersNo
POST/v3/rectifyPerspective correctionNo
POST/v3/preprocessCorner detection + rectificationNo

GET /v3/health

Returns the current health status of the API service and loaded models.

Response

{
  "status": "healthy",
  "models_loaded": 5,
  "pop_models_loaded": 5,
  "pop_pipeline_enabled": true,
  "model_version": "3.3.0-ensemble"
}

Response Fields

FieldTypeDescription
statusstringService health status ("healthy" or "degraded")
models_loadedintegerNumber of prediction models loaded (expected: 5)
pop_models_loadedintegerNumber of PoP pipeline models loaded (expected: 5)
pop_pipeline_enabledbooleanWhether the Picture-of-Picture pipeline is available
model_versionstringCurrent model version string

POST /v3/predict

Submit one or more base64-encoded radiograph images for cardiac measurement. Returns VHS, VLAS, landmark coordinates, confidence metrics, and an annotated image.

Request Headers

HeaderRequiredValue
x-api-keyYesYour API key
Content-TypeYesapplication/json

Single Image Request

{
  "image": "<base64-encoded-image>",
  "uuid": "patient-123",
  "render": true
}

Batch Request (up to 10 images)

{
  "images": ["<base64>", "<base64>"],
  "uuid": "patient-123",
  "render": true
}

Request Fields

FieldTypeRequiredDescription
imagestringYesBase64-encoded JPEG or PNG image
uuidstringNoPatient identifier for historical tracking. Also used for 24h recheck billing grace.
renderbooleanNoIf true, returns a rendered_image with overlaid landmarks and scores. Default: false.
imagesstring[]NoArray of base64-encoded images for batch mode (max 10). Use instead of image for batch requests.

Single Image Response

{
  "vhs": 10.4,
  "vlas": 2.8,
  "prediction": [
    { "x": 0.42, "y": 0.15, "class": "heart_top" },
    { "x": 0.41, "y": 0.44, "class": "heart_bottom" },
    { "x": 0.25, "y": 0.30, "class": "heart_right" },
    { "x": 0.57, "y": 0.29, "class": "heart_left" },
    { "x": 0.49, "y": 0.40, "class": "VLAS" },
    { "x": 0.36, "y": 0.12, "class": "Vertebra A" },
    { "x": 0.55, "y": 0.12, "class": "Vertebra B" }
  ],
  "result_code": 1,
  "result_description": "VHS and VLAS predicted successfully with high confidence",
  "model_confidence": "optimal",
  "spread": 0.0032,
  "pop_detected": false,
  "model_version": "3.3.0-ensemble",
  "rendered_image": "<base64-encoded-annotated-image>",
  "historic_vhs": [["2026-03-27", 11.38]],
  "historic_vlas": [["2026-03-27", 1.88]],
  "billing": {
    "billable": true,
    "reason": "valid_vhs",
    "call_type": "single",
    "monthly_usage": 47,
    "tier": "paygo",
    "rate": 3.00
  }
}

Response Fields

FieldTypeDescription
vhsnumberVertebral Heart Score
vlasnumberVertebral Lung-to-Apex Scale
predictionobject[]Array of 7 landmark coordinates (x, y, class). Coordinates are normalized 0.0–1.0.
result_codeintegerNumeric result code (see Result Codes)
result_descriptionstringHuman-readable result description
model_confidencestringConfidence level: "optimal", "good", or "review"
spreadnumberEnsemble spread metric (lower is better)
pop_detectedbooleanWhether a picture-of-picture was detected and corrected
model_versionstringModel version used for prediction
rendered_imagestringBase64-encoded annotated image with landmarks and measurements overlaid. Only present when render: true is set in the request.
historic_vhsnull | [date, number][]Previous measurements for this uuid as [[date, value]] pairs. Populated when a uuid is provided. Empty array if no history exists.
historic_vlasnull | [date, number][]Previous measurements for this uuid as [[date, value]] pairs. Populated when a uuid is provided. Empty array if no history exists.
billingobjectBilling metadata for this request. See Billing section.

Rejection Response

When an image is rejected (result_code 6), the response includes additional fields: classifier_confidence, rejected, and rejection_reason. VHS and VLAS will be null.

{
  "classifier_confidence": 0.7937,
  "model_version": "3.3.0-ensemble",
  "rejected": true,
  "rejection_reason": "not_a_valid_radiograph",
  "result_code": 6,
  "result_description": "Image rejected — not a valid radiograph.",
  "vhs": null,
  "vlas": null,
  "billing": {
    "billable": false,
    "reason": "no_valid_vhs",
    "monthly_usage": 47,
    "tier": "paygo"
  }
}

When an image is rejected, the following standard fields are absent from the response: prediction, model_confidence, spread, pop_detected, rendered_image, historic_vhs, historic_vlas.

Batch Response

{
  "vhs": 11.32,
  "vlas": 1.87,
  "best_photo_index": 0,
  "best_vhs": 11.32,
  "best_vlas": 1.87,
  "median_vhs": 11.32,
  "median_vlas": 1.87,
  "vhs_std": null,
  "vlas_std": null,
  "vhs_confidence": "optimal",
  "vlas_confidence": "optimal",
  "n_photos_processed": 2,
  "n_photos_with_landmarks": 1,
  "processing_started": true,
  "per_photo": [
    {
      "vhs": 11.32,
      "vlas": 1.87,
      "spread": 0.0023,
      "model_confidence": "optimal",
      "landmarks_detected": true,
      "axes_perpendicular": true,
      "pop_detected": false
    },
    {
      "vhs": null,
      "vlas": null,
      "spread": null,
      "model_confidence": "review",
      "landmarks_detected": false,
      "axes_perpendicular": false,
      "rejected": true,
      "rejection_reason": "not_a_valid_radiograph",
      "classifier_confidence": 0.7937
    }
  ],
  "historic_vhs": [["2024-01-26", 9.23]],
  "historic_vlas": [["2024-01-26", 2.12]],
  "model_version": "3.3.0-ensemble",
  "billing": {
    "billable": true,
    "reason": "valid_vhs",
    "call_type": "session",
    "monthly_usage": 48,
    "tier": "paygo",
    "rate": 4.00
  }
}

Batch Response Fields

FieldTypeDescription
vhsfloat | nullBest VHS measurement from the batch
vlasfloat | nullBest VLAS measurement from the batch
best_photo_indexintegerIndex of the best image in the batch
best_vhsfloatBest VHS across frames with detected landmarks
best_vlasfloatBest VLAS across frames with detected landmarks
median_vhsfloatMedian VHS across frames with detected landmarks
median_vlasfloatMedian VLAS across frames with detected landmarks
vhs_stdfloat | nullStandard deviation of VHS across frames
vlas_stdfloat | nullStandard deviation of VLAS across frames
vhs_confidencestringBatch-level VHS confidence: optimal, good, or review
vlas_confidencestringBatch-level VLAS confidence: optimal, good, or review
n_photos_processedintegerTotal number of images processed
n_photos_with_landmarksintegerNumber of images where landmarks were detected
processing_startedbooleanWhether processing began
per_photoobject[]Per-image results array
per_photo[].vhsfloat | nullVHS for this image
per_photo[].vlasfloat | nullVLAS for this image
per_photo[].spreadfloat | nullEnsemble spread for this image
per_photo[].model_confidencestringConfidence tier for this image
per_photo[].landmarks_detectedbooleanWhether landmarks were found
per_photo[].axes_perpendicularbooleanWhether cardiac axes are perpendicular
per_photo[].pop_detectedbooleanWhether PoP was detected (only on successful frames)
historic_vhsarrayHistorical VHS as [[date, value]] pairs (top-level, not per-photo)
historic_vlasarrayHistorical VLAS as [[date, value]] pairs (top-level, not per-photo)
billingobjectBilling metadata for this request. See Billing section.

Error Responses

HTTP StatusMeaningExample Body
400Bad Request — missing or invalid fields{"error": "Missing required field: image"}
403Forbidden — invalid or missing API key{"error": "Invalid API key"}
413Payload Too Large — image exceeds 10 MB{"error": "Image exceeds maximum size of 10MB"}
422Unprocessable — corner detection failed (corners/preprocess only){"error": "Corner detection failed"}
500Internal Server Error{"error": "Internal server error"}
503Service Unavailable — PoP pipeline not loaded{"error": "PoP pipeline not available"}

POST /v3/predict/stream

Submit a batch of images and receive results as Server-Sent Events (SSE). The request body is the same as the batch /v3/predict endpoint.

SSE Events

processing_started

event: processing_started
data: {"n_images": 2, "model_version": "3.3.0-ensemble"}

photo (emitted per image)

event: photo
data: {
  "index": 0,
  "vhs": 11.32,
  "vlas": 1.87,
  "spread": 0.0023,
  "model_confidence": "optimal",
  "landmarks_detected": true,
  "axes_perpendicular": true,
  "n_total": 2,
  "model_version": "3.3.0-ensemble"
}

summary

event: summary
data: {
  "best_vhs": 11.32,
  "best_vlas": 1.87,
  "median_vhs": 11.32,
  "median_vlas": 1.87,
  "vhs_std": null,
  "vlas_std": null,
  "vhs_confidence": "optimal",
  "vlas_confidence": "optimal",
  "best_photo_index": 0,
  "n_photos_processed": 2,
  "n_photos_with_landmarks": 1
}

Billing: Stream predictions are billed at the session rate ($4.00 paygo) as a single event, regardless of how many images are in the batch.

error

event: error
data: {"index": 1, "uuid": "patient-456", "error": "Invalid image data"}

POST /v3/pop-session/start

Initialize a new Picture-of-Picture (PoP) capture session. The session guides the client through a multi-frame capture flow to obtain the best possible radiograph image from a screen photo.

Request

Send an empty JSON body:

POST /v3/pop-session/start
Content-Type: application/json
x-api-key: YOUR_API_KEY

{}

Response

{
  "session_id": "0960abaa8179476d",
  "session_status": "ready",
  "pop_pipeline_enabled": true,
  "models_loaded": 5,
  "pop_models_loaded": 5,
  "model_version": "3.3.0-ensemble",
  "session_config": {
    "max_frames": 20,
    "max_consecutive_failures": 5,
    "session_ttl_seconds": 300
  }
}

Response Fields

FieldTypeDescription
session_idstringUnique session identifier to include in subsequent requests
session_statusstringCurrent session state: "ready"
pop_pipeline_enabledbooleanWhether the PoP pipeline is available
models_loadedintegerNumber of prediction models loaded
pop_models_loadedintegerNumber of PoP pipeline models loaded
model_versionstringModel version identifier
session_configobjectSession configuration including max frames, max consecutive failures, and session TTL

POST /v3/pop-session

Send a captured frame to an active PoP session. The server evaluates the frame quality and either requests another frame or completes the session with a prediction.

Request

{
  "image": "<base64-encoded-frame>",
  "session_id": "sess_abc123def456",
  "uuid": "patient-123"
}

Response: Continue (need more frames)

{
  "session_id": "0960abaa8179476d",
  "session_status": "continue",
  "reason": "0/3 good frames so far, need more",
  "frame_index": 0,
  "frames_processed": 1,
  "good_frames": 0,
  "result_code": 5,
  "result_description": "Neither VHS nor VLAS could be calculated — check image quality.",
  "frame_result": {
    "vhs": null,
    "vlas": null,
    "spread": null,
    "model_confidence": "review",
    "landmarks_detected": false,
    "pop_detected": true
  },
  "model_version": "3.3.0-ensemble"
}

No billing field is present during continue — billing is only evaluated on session completion or failure.

Response: Complete (session succeeded)

{
  "session_id": "sess_abc123def456",
  "session_status": "complete",
  "frame_index": 3,
  "rectified_image": "<base64-encoded-rectified-image>",
  "prediction": {
    "vhs": 10.4,
    "vlas": 2.8,
    "prediction": [
      { "x": 0.42, "y": 0.15, "class": "heart_top" },
      { "x": 0.41, "y": 0.44, "class": "heart_bottom" },
      { "x": 0.25, "y": 0.30, "class": "heart_right" },
      { "x": 0.57, "y": 0.29, "class": "heart_left" },
      { "x": 0.49, "y": 0.40, "class": "VLAS" },
      { "x": 0.36, "y": 0.12, "class": "Vertebra A" },
      { "x": 0.55, "y": 0.12, "class": "Vertebra B" }
    ],
    "result_code": 0,
    "result_description": "Success",
    "model_confidence": "optimal",
    "spread": 0.0028,
    "model_version": "3.3.0-ensemble",
    "rendered_image": "<base64>"
  },
  "billing": {
    "billable": true,
    "reason": "valid_vhs",
    "call_type": "session",
    "monthly_usage": 49,
    "tier": "paygo",
    "rate": 4.00
  }
}

Response: Failed (session could not complete)

{
  "session_id": "sess_abc123def456",
  "session_status": "failed",
  "frame_index": 20,
  "error": "max_frames_exceeded",
  "billing": {
    "billable": false,
    "reason": "no_valid_vhs",
    "monthly_usage": 49,
    "tier": "paygo"
  }
}

A PoP session is billed as a single event at the session rate when it completes with a valid VHS. Failed sessions are never billed.

Session Completion Logic

ConditionResult
Frame quality is sufficientSession completes with prediction
Frame count reaches max_framesSession fails with max_frames_exceeded
Session exceeds session_ttl_secondsSession fails with session_timeout
Frame quality insufficientReturns continue with frame result details

POST /v3/corners

Detect the four corners of a radiograph screen in a photograph. Returns corner coordinates for use with the /v3/rectify endpoint.

Request

{
  "image": "<base64-encoded-image>"
}

Response

{
  "corners": [[137.48, 100.30], [490.50, 81.32], [495.35, 421.38], [154.89, 421.57]],
  "order": "TL,TR,BR,BL",
  "image_width": 640,
  "image_height": 482,
  "model_version": "3.3.0-ensemble"
}

Response Fields

FieldTypeDescription
cornersfloat[][]Four corner coordinates as [x, y] arrays in pixel space
orderstringCorner ordering: "TL,TR,BR,BL" (top-left, top-right, bottom-right, bottom-left)
image_widthintegerWidth of the input image in pixels
image_heightintegerHeight of the input image in pixels
model_versionstringModel version identifier

POST /v3/rectify

Apply perspective correction to an image using the provided corner coordinates. Returns a rectified (de-warped) image.

Request

{
  "image": "<base64-encoded JPEG>",
  "corners": [[137.48, 100.30], [490.50, 81.32], [495.35, 421.38], [154.89, 421.57]]
}

Response

{
  "rectified_image": "<base64-encoded JPEG>",
  "width": 346,
  "height": 330,
  "model_version": "3.3.0-ensemble"
}

POST /v3/preprocess

Combined corner detection and perspective rectification in a single call. Equivalent to calling /v3/corners followed by /v3/rectify.

Request

{
  "image": "<base64-encoded-image>"
}

Response

{
  "rectified_image": "<base64-encoded JPEG>",
  "corners": [[137.48, 100.30], [490.50, 81.32], [495.35, 421.38], [154.89, 421.57]],
  "order": "TL,TR,BR,BL",
  "width": 346,
  "height": 330,
  "model_version": "3.3.0-ensemble"
}

Landmarks

Each prediction returns 7 anatomical landmarks identified on the radiograph. Coordinates are normalized (0.0–1.0) relative to image dimensions.

ClassDescription
heart_topCranial-most point of the cardiac silhouette
heart_bottomCaudal-ventral apex of the cardiac silhouette
heart_rightRight-most (cranial) extent of the heart border
heart_leftLeft-most (caudal) extent of the heart border
VLASCaudal-dorsal point where the left atrium meets the vertebral column
Vertebra ACranial edge of T4 vertebra (start of vertebral measurement)
Vertebra BCaudal edge of the vertebra where the long-axis measurement ends

Result Codes

CodeDescriptionBillable
0Abnormal result — unexpected prediction stateNo
1VHS and VLAS predicted successfully with high confidenceYes
2VHS and VLAS predicted successfully with moderate confidenceYes
3VHS and VLAS predicted but confidence is low — review recommendedYes
4VHS predicted successfully, VLAS could not be calculatedYes
5Neither VHS nor VLAS could be calculated — check image qualityNo
6Image rejected — not a valid radiographNo
7PoP image detected but perspective correction failedNo

Measurements

VHS (Vertebral Heart Score)

The long axis (heart_top to heart_bottom) and short axis (heart_right to heart_left) are measured and mapped onto the thoracic vertebral column starting at T4 (Vertebra A).

VHS = (long_axis_vertebrae + short_axis_vertebrae)

Normal range (canine): 8.5 – 10.5 vertebrae

VLAS (Vertebral Lung-to-Apex Scale)

Distance from heart_top to the VLAS landmark, measured in vertebral units.

VLAS = distance(heart_top, VLAS_landmark) in vertebral units

Normal range (canine): < 2.5 vertebrae. Values ≥ 2.5 suggest left atrial enlargement.

Confidence Scoring

The 5-fold ensemble model produces a spread value representing the variance between individual model predictions. Lower spread indicates higher agreement and confidence.

ConfidenceSpread ThresholdRecommendation
Optimal< 0.005Results are reliable
Good< 0.008Results are usable; visual verification suggested
Review≥ 0.008Manual review recommended; consider re-submitting a higher-quality image

Rate Limits

ParameterLimit
Max batch size10 images per request
Max image size10 MB per image
Accepted formatsJPEG, PNG
PoP session TTL300 seconds (5 minutes)
Max PoP frames per session20 frames
Request timeout300 seconds
Cold start latency~6–7 seconds
Warm inference~1.5 seconds per image

Billing

Overview

The V3 API uses pay-for-value billing. You are only charged when the algorithm returns a valid VHS measurement (result codes 1–4). Failed predictions, rejected images, and utility endpoints are always free.

Billing Rules

RuleDescription
Valid VHS = billedResult codes 1–4 incur a charge
No VHS = freeResult codes 0, 5, 6, 7 are never charged
24h recheck graceSame uuid within 24 hours is free. Pass a patient identifier in the uuid field to enable this.
Session = one chargeBatch, stream, and PoP session calls are billed as a single event at the session rate
Utility endpoints = free/health, /corners, /rectify, /preprocess, /pop-session/start are never billed

Pricing Tiers

TierMonthly VolumeSingle ImageSession / Stream / PoP
Pay-as-you-go0–100 calls$3.00$4.00
Starter101–500 calls$2.50$3.50
Professional501–2,000 calls$2.00$3.00
Enterprise2,001+ callsCustomCustom

New API keys default to the pay-as-you-go tier. Contact us for volume pricing or enterprise agreements.

Call Types

EndpointCall TypeRate
POST /v3/predict (single image)singleSingle rate
POST /v3/predict (batch images)sessionSession rate
POST /v3/predict/streamsessionSession rate
POST /v3/pop-session (on complete)sessionSession rate

Billing Response Object

Three example shapes depending on the billing outcome:

Billable call

{
  "billable": true,
  "reason": "valid_vhs",
  "call_type": "single",
  "monthly_usage": 47,
  "tier": "paygo",
  "rate": 3.00
}

Non-billable call

{
  "billable": false,
  "reason": "no_valid_vhs",
  "monthly_usage": 47,
  "tier": "paygo"
}

Non-billable recheck

{
  "billable": false,
  "reason": "recheck_24h",
  "monthly_usage": 47,
  "tier": "paygo"
}

Billing Response Fields

FieldTypeDescription
billablebooleanWhether this call was counted as a billable event
reasonstringWhy: "valid_vhs", "no_valid_vhs", "recheck_24h", or "billing_error"
call_typestring"single" or "session". Only present on billable calls.
monthly_usageintegerTotal billable calls this billing period
tierstringCurrent pricing tier: "paygo", "starter", "professional", or "enterprise"
ratenumberDollar amount charged. Only present on billable calls.

Tips for Minimizing Costs

  1. Always pass uuid — enables 24h recheck grace so repeat scans of the same patient within a day are free.
  2. Use batch or stream for multiple images of the same patient — charged once at the session rate instead of per-image.
  3. Pre-validate images before sending — ensure images are valid radiographs to avoid wasted requests.

Code Examples

Python — Single Image

import requests
import base64

# Read and encode the radiograph
with open("radiograph.jpg", "rb") as f:
    image_data = base64.b64encode(f.read()).decode()

# Send prediction request
response = requests.post(
    "https://api.radanalyzer.com/v3/predict",
    headers={
        "x-api-key": "YOUR_API_KEY",
        "Content-Type": "application/json"
    },
    json={
        "image": image_data,
        "uuid": "patient-123",
        "render": True
    }
)

data = response.json()
print(f"VHS: {data['vhs']}")
print(f"VLAS: {data['vlas']}")
print(f"Confidence: {data['model_confidence']}")
print(f"Spread: {data['spread']}")
print(f"Result: {data['result_description']}")

JavaScript — PoP Session Flow

const API_BASE = "https://api.radanalyzer.com";
const headers = {
  "x-api-key": "YOUR_API_KEY",
  "Content-Type": "application/json"
};

// Step 1: Start a PoP session
const startRes = await fetch(`${API_BASE}/v3/pop-session/start`, {
  method: "POST",
  headers,
  body: JSON.stringify({})
});
const { session_id } = await startRes.json();

// Step 2: Send frames until session completes
let sessionComplete = false;
while (!sessionComplete) {
  const frameBase64 = await captureFrame(); // your capture function

  const frameRes = await fetch(`${API_BASE}/v3/pop-session`, {
    method: "POST",
    headers,
    body: JSON.stringify({
      image: frameBase64,
      session_id: session_id,
      uuid: "patient-123"
    })
  });

  const result = await frameRes.json();

  if (result.session_status === "complete") {
    console.log("VHS:", result.prediction.vhs);
    console.log("VLAS:", result.prediction.vlas);
    sessionComplete = true;
  } else if (result.session_status === "failed") {
    console.error("Session failed:", result.error);
    sessionComplete = true;
  } else {
    console.log("Feedback:", result.feedback);
    // Show feedback to user, capture another frame
  }
}

cURL — Health Check

curl -X GET https://api.radanalyzer.com/v3/health \
  -H "x-api-key: YOUR_API_KEY"

Changelog

VersionDateChanges
3.3.0March 2026PoP pipeline, session-based capture, corner detection, rectification
3.0.0March 2026Initial V3 — 5-fold ensemble, batch mode, streaming, confidence scoring