← Back to Developersusg/v1 · model 1.6 · service 1.6.0

USG Screening API Reference

Feline urine specific gravity screening from routine bloodwork. Served at api.radanalyzer.com/usg/v1.

Same host, same credential. The USG route lives on the same api.radanalyzer.com host as the cardiac VHS / VLAS API and accepts the same x-api-key credential. Existing customers do not need a new key — usage on /usg/v1/* is metered against the same combined volume tier.

Overview

The USG (Urine Specific Gravity) classifier predicts whether a feline patient's urine is likely impaired (USG < 1.035) from five routine bloodwork values. Intended use is to flag patients whose chemistry suggests reduced urine concentrating ability so a follow-up urinalysis can be ordered — the test that actually reveals early kidney dysfunction.

Every response carries a calibrated probability of impairment, a risk tier, per-feature SHAP attribution, and two uncertainty fields: ensemble_std (a soft uncertainty score — higher means less confident) and a conformal_set with a 90 % coverage guarantee on the true label.

  • Base URL: https://api.radanalyzer.com
  • Path prefix: /usg/v1
  • Latency: ~200 ms warm, ~20 s cold
  • Transport: HTTPS only. Plaintext HTTP is redirected.
  • Content-Type: application/json on every POST.

See the product page for clinical background, the data hub for current performance metrics, and the standalone web tool to try the model interactively before integrating.

Endpoints

MethodPathAuthPurpose
GET/usg/v1/healthNoLiveness probe.
GET/usg/v1/versionNoActive model version, feature list, and thresholds.
POST/usg/v1/predictYesRun a USG impairment prediction.
OPTIONS/usg/v1/predictNoCORS preflight. Returns 204 No Content.

Every response includes permissive CORS headers (Access-Control-Allow-Origin: *, methods GET, POST, OPTIONS, headers Content-Type, x-api-key). Production callers should still proxy through their own backend — see operational notes.

Authentication

Send your API key in the x-api-key header. Required on POST /usg/v1/predict; not required on /health or /version. The same key authorizes /vhs/v3/* and /usg/v1/* — there is no per-algorithm key. Bearer tokens, OAuth, query-string keys, and mTLS are not supported.

x-api-key: YOUR_API_KEY

Failure modes

HTTPerror.codeMeaning
401missing_api_keyx-api-key header was not sent.
403invalid_api_keyHeader sent but key is not active.

Treat 401 and 403 the same way: prompt the operator for a new key. Keys are not recoverable through the API — request a rotation.

GET /usg/v1/health

Liveness probe. Unauthenticated. Used by clients to confirm reachability.

Response — 200 OK

{
  "status": "ok",
  "service": "usg-v1",
  "model_version": "1.6",
  "service_version": "1.6.0"
}

GET /usg/v1/version

Returns the active model version, feature list, accepted aliases, and decision thresholds. Useful for client-side schema validation and reproducibility logging. Record model_version alongside every stored prediction.

Response — 200 OK

{
  "service": "usg-v1",
  "service_version": "1.6.0",
  "model_version": "1.6",
  "features": ["BUN", "CREATININE", "HGB", "ABSOLUTE LYMPHOCYTES", "age_months"],
  "feature_aliases": {
    "absolute_lymphocytes": "ABSOLUTE LYMPHOCYTES"
  },
  "threshold_impaired": 0.535,
  "sg_cutoff": 1.035
}
FieldMeaning
service_versionSemVer of the Cloud Run service (wire contract). Bumps on path version.
model_versionIdentifier of the trained ensemble artifact currently serving traffic.
featuresThe exact ordered list of canonical feature keys the predict route expects.
feature_aliasesDict mapping accepted aliases to canonical feature names (e.g. {"absolute_lymphocytes": "ABSOLUTE LYMPHOCYTES"}). Either form is valid in the request body. An empty {} means no aliases are currently accepted.
threshold_impairedIf probability_impaired ≥ threshold_impaired, classification flips to impaired.
sg_cutoffThe USG cutoff (mg/mL) the model was trained against (1.035).

POST /usg/v1/predict

Run a USG impairment prediction.

Request headers

POST /usg/v1/predict HTTP/1.1
Host: api.radanalyzer.com
Content-Type: application/json
x-api-key: YOUR_API_KEY

Request body

{
  "BUN": 22,
  "CREATININE": 1.3,
  "HGB": 14.2,
  "ABSOLUTE LYMPHOCYTES": 2400,
  "age_months": 96,

  "clinic_code": "rc-vet",
  "uuid": "pat-abc-123"
}

A Firebase callable-style envelope is also accepted for compatibility — if body.data is a JSON object, it is unwrapped and used as the payload:

{ "data": { "BUN": 22, "CREATININE": 1.3, "HGB": 14.2, "ABSOLUTE LYMPHOCYTES": 2400, "age_months": 96 } }

Required features

All five must be present and numeric. Missing any → 400 missing_features.

FieldUnitsPlausible rangeNotes
BUNmg/dL1 – 300Blood urea nitrogen.
CREATININEmg/dL0.1 – 30If submitted value is > 50, service auto-converts from µmol/L (÷ 88.4) and adds a warnings[] entry.
HGBg/dL1 – 25Hemoglobin.
ABSOLUTE LYMPHOCYTES/µL50 – 50 000If submitted value is < 50, service auto-scales by ×1000 (k/µL → /µL) and adds a warnings[] entry.
age_monthsmonths1 – 360Patient age. For ages in years, multiply by 12 before sending.

JSON key note. ABSOLUTE LYMPHOCYTES (uppercase, with a space) is the canonical key — it mirrors the original lab-export column name and the model's internal feature names. The snake_case alias absolute_lymphocytes is also accepted and is mapped to the canonical key server-side before scoring; either form is valid. If both are sent in the same request, the canonical key wins and the alias is dropped silently. The full alias map is returned by /usg/v1/version under feature_aliases. New integrations should prefer absolute_lymphocytes for ergonomics; existing integrations on ABSOLUTE LYMPHOCYTES continue to work unchanged.

Optional fields

Stored on the prediction log; do not affect the model. Silently ignored if absent.

FieldTypePurpose
clinic_codestringStable clinic identifier. Powers per-clinic usage and outcome reports.
uuidstringStable patient identifier. Used for per-patient analytics and outcome tracking.

Response — 200 OK

{
  "result": {
    "classification": "adequate",
    "probability_adequate": 0.8421,
    "probability_impaired": 0.1579,
    "risk_tier": "low",
    "risk_label": "Low risk of impairment",
    "threshold_sg": 1.035,
    "model_version": "1.6",
    "ensemble_std": 0.042,
    "conformal_set": ["adequate"],
    "conformal_classification": "adequate",
    "conformal_alpha": 0.10,
    "explanation": {
      "base_value": -0.42,
      "features": [
        {"feature": "CREATININE",           "label": "Creatinine",       "value": 1.30,   "shap": -0.31, "direction": "adequate"},
        {"feature": "BUN",                  "label": "BUN",              "value": 22.00,  "shap": -0.18, "direction": "adequate"},
        {"feature": "HGB",                  "label": "Hemoglobin",       "value": 14.20,  "shap":  0.04, "direction": "impaired"},
        {"feature": "ABSOLUTE LYMPHOCYTES", "label": "Abs. Lymphocytes", "value": 2400.0, "shap": -0.02, "direction": "adequate"},
        {"feature": "age_months",           "label": "Age (months)",     "value": 96.0,   "shap":  0.01, "direction": "impaired"}
      ]
    }
  },
  "meta": {
    "service": "usg-v1",
    "model_version": "1.6",
    "elapsed_ms": 170.4
  }
}

Response field reference

PathTypeMeaning
result.classificationstring"adequate" if probability_impaired < threshold_impaired, else "impaired".
result.probability_adequatenumber1 - probability_impaired, rounded to 4 dp.
result.probability_impairednumberCalibrated probability of impaired USG, rounded to 4 dp.
result.risk_tierstringOne of low, moderate_low, moderate_high, high. See risk-tier table.
result.risk_labelstringHuman-readable phrasing of the tier, suitable for clinical notes.
result.threshold_sgnumberUSG cutoff (1.035) that defines the impaired class. Echoed for clarity on PDF reports.
result.model_versionstringModel version that produced this specific prediction (currently "1.6").
result.ensemble_stdnumberPer-prediction uncertainty score on a 0–1 scale. Higher values indicate the model is less confident about this patient. Typical range 0 – 0.15. Use as a soft signal (e.g. sort triage queues by it).
result.conformal_setarray of stringsCoverage-guaranteed prediction set. One of ["adequate"], ["impaired"], or ["adequate", "impaired"]. Never empty. The true class is contained in this set at least (1 - conformal_alpha) of the time.
result.conformal_classificationstringSingleton interpretation of conformal_set. "adequate" or "impaired" when the set has exactly one label; "uncertain" when it contains both labels. Use "uncertain" as a hard “do not auto-act” gate.
result.conformal_alphanumberSignificance level used to build conformal_set. Currently fixed at 0.10 (90 % coverage). Echoed on every response so clients can detect a future retune.
result.explanation.base_valuenumberSHAP base value (log-odds). Sum of base_value + all features[].shap reproduces model log-odds output.
result.explanation.features[]arrayPer-feature SHAP attribution, sorted by abs(shap) descending.
result.explanation.features[].featurestringFeature key as accepted in the request body.
result.explanation.features[].labelstringDisplay label suitable for UI / PDF.
result.explanation.features[].valuenumberNumeric value used by the model — post auto-correction, rounded to 2 dp.
result.explanation.features[].shapnumberSigned SHAP contribution (log-odds), rounded to 4 dp.
result.explanation.features[].directionstring"impaired" if shap > 0, else "adequate".
result.warningsarrayPresent only when auto-correction fired. Strings explaining what was changed and why.
meta.servicestring"usg-v1" — useful for log routing on the client side.
meta.model_versionstringSame as result.model_version. Provided in meta so it can be persisted without parsing the result body.
meta.elapsed_msnumberServer-side wall time between request entry and response.

Risk tiers

Tierprobability_impairedRecommended action
low< 0.20No follow-up indicated.
moderate_low0.20 – 0.535Likely adequate; consider confirming with UA.
moderate_high0.535 – 0.75Possible impairment; UA recommended.
high>= 0.75High risk; UA strongly recommended.

The boundary 0.535 is the same threshold_impaired returned by /version — the binary classification flips at that point, while risk_tier provides a finer-grained band suitable for triage UIs.

Auto-correction warnings

Two unit-mismatch corrections are applied automatically before scoring. When either fires, the corrected value is used for the prediction and echoed in result.explanation.features[].value, and a string is appended to result.warnings[]. Out-of-range values that fall outside the plausible band even after correction return 400 out_of_range.

TriggerActionWarning string format
CREATININE > 50divide by 88.4 (µmol/L → mg/dL)"Creatinine (mg/dL): 132.6 → 1.50 (converted from umol/L to mg/dL)"
0 < ABSOLUTE LYMPHOCYTES < 50multiply by 1000 (k/µL → /µL)"Abs. Lymphocytes (/uL): 2.4 → 2400 (value appears to be in thousands — converted to /uL)"

Errors

All errors share the umbrella envelope:

{ "error": { "code": "snake_case_code", "message": "Human-readable detail." } }
HTTPcodeWhen
400invalid_jsonRequest body could not be parsed as JSON.
400invalid_payloadBody parsed, but is not a JSON object (e.g. an array or string).
400missing_featuresOne or more of the five required features was absent.
400out_of_rangeA numeric feature was outside its plausible range after correction.
400invalid_valueA numeric feature could not be coerced to a float.
401missing_api_keyx-api-key header was not sent.
403invalid_api_keyKey is not active.
500internal_errorUnhandled exception. Logged for follow-up; safe to retry once.

code values are stable — safe to branch on programmatically. message strings may evolve and must not be parsed.

End-to-end examples

cURL

KEY=ra_live_xxxxxxxxxxxxxxxxxxxxxxxx

# Reachability
curl https://api.radanalyzer.com/usg/v1/health
curl https://api.radanalyzer.com/usg/v1/version

# Prediction
curl -X POST https://api.radanalyzer.com/usg/v1/predict \
  -H "x-api-key: $KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "BUN": 22,
    "CREATININE": 1.3,
    "HGB": 14.2,
    "ABSOLUTE LYMPHOCYTES": 2400,
    "age_months": 96,
    "clinic_code": "rc-vet",
    "uuid": "pat-buddy-001"
  }'

Python (requests)

import os
import requests

BASE_URL = "https://api.radanalyzer.com"
API_KEY  = os.environ["RA_KEY"]   # same key authorizes /vhs/v3 and /usg/v1

resp = requests.post(
    f"{BASE_URL}/usg/v1/predict",
    headers={"x-api-key": API_KEY, "Content-Type": "application/json"},
    json={
        "BUN": 22,
        "CREATININE": 1.3,
        "HGB": 14.2,
        "ABSOLUTE LYMPHOCYTES": 2400,
        "age_months": 96,
        "clinic_code": "rc-vet",
        "uuid": "pat-buddy-001",
    },
    timeout=15,
)
resp.raise_for_status()
data = resp.json()

result = data["result"]
print(f"{result['classification']:>9}  "
      f"p_impaired={result['probability_impaired']:.4f}  "
      f"tier={result['risk_tier']}  "
      f"model={data['meta']['model_version']}")

Node (fetch, ≥18)

const BASE_URL = "https://api.radanalyzer.com";
const API_KEY  = process.env.RA_KEY;

const resp = await fetch(`${BASE_URL}/usg/v1/predict`, {
  method: "POST",
  headers: { "Content-Type": "application/json", "x-api-key": API_KEY },
  body: JSON.stringify({
    BUN: 22,
    CREATININE: 1.3,
    HGB: 14.2,
    "ABSOLUTE LYMPHOCYTES": 2400,
    age_months: 96,
    clinic_code: "rc-vet",
    uuid: "pat-buddy-001",
  }),
});

if (!resp.ok) {
  const { error } = await resp.json();
  throw new Error(`${error.code}: ${error.message}`);
}

const { result, meta } = await resp.json();
console.log(`${result.classification} | p=${result.probability_impaired} | ${result.risk_tier} | model=${meta.model_version}`);

Operational notes

  • Cold starts. First request after the service has scaled to zero takes ~20 s; subsequent calls in the warm window are ~200 ms. A single ~20 s response should not be interpreted as a regression.
  • Browser embedding. Do not embed ra_live_* keys in shipped JavaScript. Proxy through a backend. The CORS allow-list is permissive only because legitimate callers may run from desktop apps and clinic SaaS frontends — it is not an invitation to ship the key client-side.
  • Idempotency. All POSTs are idempotent on the wire. Safe to retry on 408 / 500 / 503.
  • Billing. Every successful USG prediction is billed at a flat per-call rate — there is no recheck grace. uuid is used for analytics, not deduplication.

Ready to integrate?

Request an API key and start calling /usg/v1/predict today. Your existing VHS / VLAS key already works on this route.