Error handling
The Skin Analysis API has two layers of failure: HTTP-level (transport, auth, rate-limiting) and document-level (image quality, per-metric model confidence). Each calls for different handling.
HTTP-level
Section titled “HTTP-level”| Status | Cause | What to do |
|---|---|---|
| 200 | Success — but check data.message | Branch on message, then status[]. |
| 400 | Malformed request | Fix the call. Almost always a missing image field. |
| 413 | Image too large | Resize client-side; aim for < 5 MB. |
| 429 | Rate limited | Honor Retry-After. Back off exponentially. |
| 5xx | Transient infra issue | Retry up to 2 times with jitter. |
Document-level
Section titled “Document-level”A 200 OK HTTP response can still report problems via data.message and data.status[]. The handling matrix:
message | Meaning | UX |
|---|---|---|
OK | Clean run | Render results. |
Warning | Results usable, quality issue noted | Render results. Optionally surface advice from status[]. |
Error | No usable results | Hide result UI. Surface the first status[].description. |
Per-finding local_warnings[] arrays apply at the metric level — e.g. pore dilation may fail because face resolution wasn’t sufficient even when the overall call succeeded. Hide that one card, keep the rest.
A defensive client
Section titled “A defensive client”async function analyze(file: File) { const url = new URL("https://partner-test.revieve.com/api/3/analyzeImage/"); url.searchParams.set("skintone", "0"); url.searchParams.set("gender", "unknown");
const form = new FormData(); form.append("partner_id", PARTNER_ID); form.append("image", file);
for (let attempt = 0; attempt < 3; attempt++) { const res = await fetch(url, { method: "POST", body: form });
if (res.status === 429) { const wait = Number(res.headers.get("Retry-After") ?? 1) * 1000; await new Promise((r) => setTimeout(r, wait)); continue; }
if (res.status >= 500 && res.status < 600) { await new Promise((r) => setTimeout(r, 250 * 2 ** attempt)); continue; }
if (!res.ok) { throw new ApiError(`HTTP ${res.status}`, res.status); }
const data = await res.json();
if (data.message === "Error") { throw new AnalysisError( data.status?.[0]?.description ?? "Analysis failed", data.status?.[0]?.idx, ); }
return data; }
throw new Error("Exhausted retries");}Surface to users, log to ops
Section titled “Surface to users, log to ops”The status[] array is partner-facing copy. The model-internal codes (local_warnings[] per finding, raw idx numbers) are useful for your observability stack but not for end-user copy. Translate before rendering.
A copy library that maps idx → friendly message keeps your UX consistent and translatable; treat the descriptions in the spec as a starting point, not a final UI string.