Skip to content

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.

StatusCauseWhat to do
200Success — but check data.messageBranch on message, then status[].
400Malformed requestFix the call. Almost always a missing image field.
413Image too largeResize client-side; aim for < 5 MB.
429Rate limitedHonor Retry-After. Back off exponentially.
5xxTransient infra issueRetry up to 2 times with jitter.

A 200 OK HTTP response can still report problems via data.message and data.status[]. The handling matrix:

messageMeaningUX
OKClean runRender results.
WarningResults usable, quality issue notedRender results. Optionally surface advice from status[].
ErrorNo usable resultsHide 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.

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");
}

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.