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.

Request
POST https://api.exacthue.com/v1/match
Authorization: Bearer eh_live_…
Content-Type: application/json
Idempotency-Key: 7f3c1a…

{
  "capture": { "image": "<base64>",
               "device": "web-cam" },
  "catalogue": "aw26-foundation",
  "illuminant": "auto",
  "max_results": 3
}

Endpoints

Match, render, catalogue.

That is the whole surface. Most integrations only ever call the first one.

Endpoint What it does Billable
POST /v1/match Qualifies a capture, recovers reflectance, and returns ranked matches with ΔE00, confidence and undertone Yes, when it returns a qualified result
POST /v1/render Composites a matched shade back onto the capture under one or more modelled illuminants No, on Growth and Enterprise
PUT /v1/catalogue Ingests or updates measured product colour, returning per-SKU validation warnings No
GET /v1/catalogue/:id Exports your measured catalogue in an open format — available at any time, including after termination No

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.

200 OK
{
  "quality": {
    "usable": true,
    "cct_k": 4120,
    "mixed_illuminant": false,
    "clipping": false
  },
  "band": "IV",
  "matches": [
    { "sku": "FDN-240N",
      "delta_e00": 0.68,
      "confidence": 0.94,
      "undertone": "neutral" },
    { "sku": "FDN-245W",
      "delta_e00": 1.41,
      "confidence": 0.81,
      "undertone": "warm" }
  ]
}

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.

Code Cause What to tell the shopper
MIXED_ILLUMINANTTwo light sources far apart in colour temperatureTurn off one light, or move away from the window
UNDEREXPOSEDToo little light to recover reflectance reliablyFind a brighter spot, ideally facing a window
CHANNEL_CLIPPEDOne or more channels blown out, commonly from direct flashTurn the flash off and try again
MOTION_BLURThe frame is not sharp enough to sample cleanlyHold still for a moment and retake
NO_SKIN_REGIONNo usable area of skin found in the frameFill more of the frame with your face
HEAVY_COSMETICSExisting coverage is obscuring the underlying skinTry on bare skin for the most accurate result

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.