API overview
Three endpoints, plain JSON, no surprises.
Everything the engine does is reachable over HTTPS with a bearer token. This page is the shape of it — enough for an engineer to judge the integration before anyone signs anything.
Authentication
One token, scoped to one environment.
Every request carries a bearer token in the Authorization header. Tokens are issued per environment, and a sandbox token is rejected by production outright — there is no shared key that could be pointed at the wrong data by accident.
Tokens are server-side credentials. Do not put one in client-side code where it can be read out of the bundle; call the API from your own backend, or use the embeddable widget, which authenticates through a short-lived session token your server mints.
Rotating a token takes effect immediately and the old one stops working. There is no grace period, deliberately — a leaked credential should die the moment you say so.
Endpoints
Match, render, catalogue.
That is the whole surface. Most integrations only ever call the first one.
A capture the engine declines is not billed. Charging for an answer we refused to give would quietly reward us for lowering the bar on what counts as a usable capture, and that is exactly the incentive this product exists to avoid.
The response
Every answer shows its working.
A match response carries three things: what the engine thought of the capture, the ranked shades, and how confident it is in each. You can surface all of it, or none of it, but it is always there to audit.
quality— whether the capture was usable, the estimated colour temperature, and any flags raised.matches— ranked, each with the SKU, a ΔE00 against recovered skin reflectance, a calibrated confidence and an undertone classification.band— the Fitzpatrick band the capture falls in, so your analytics can break accuracy down the same way we do.
Note the second match in the example: a higher ΔE and a lower confidence, returned anyway. Showing the runner-up and its distance is usually more useful to a shopper than a single verdict, because it tells them how close the call was.
When it declines
Reason codes you can actually act on.
A refusal returns 200 with usable: false and a reason — not an error. It is a valid answer to a bad question, and your interface should treat it as one.
Genuine failures — a bad token, a malformed body, a catalogue that does not exist — return conventional 4xx and 5xx status codes with a machine-readable error.code and a human-readable error.message.
Operating it
The things you find out at 2am, written down now.
Rate limits
Limits are per environment and returned on every response as X-RateLimit-Remaining and X-RateLimit-Reset. Exceed one and you get a 429 with a Retry-After header. Sandbox is deliberately tighter than production so load problems surface before launch, not after.
Idempotency
Send an Idempotency-Key with any POST and a retry returns the original result rather than doing the work twice. Keys are held for 24 hours. This matters most on flaky mobile connections, where a request can succeed and the response can still be lost.
Versioning
The version is in the path. Additive changes — new response fields, new optional parameters — ship into the current version without notice, so parse defensively. Anything breaking gets a new version and a minimum 90-day deprecation window, with the sunset date in a response header throughout.
Data residency
Processing can be pinned to EU, UK, US or APAC on Enterprise plans, and a pinned request never leaves its region while it is being handled. The region is part of the API host, so residency is visible in your own network logs rather than something you have to take on trust.
What we keep
A capture is processed and then deleted. It is not retained for training unless you have opted in under a separate written agreement. Match results are aggregated and de-identified within 30 days. The privacy policy is specific about all of it.
SDKs
There are none, and that is deliberate for now. Three endpoints over HTTPS with bearer auth do not need a wrapper, and a thin SDK mostly adds a dependency you have to keep updated. If you would genuinely rather have one, tell us which language and why.
Want to call it before you commit?
A sandbox account gives you the full engine against a sample catalogue spanning Fitzpatrick I–VI. No card, and we will not convert you to a paid plan behind your back.