SkinLabs® Platform API
Build South African skin intelligence into your clinic or product. The SkinLabs Platform API gives clinics, retailers and integrators access to SA-specific skin data: UV and humidity by city, independent product reviews scored for local climate, and an ingredient catalogue built for melanin-rich skin and Highveld-to-coastal conditions.
GET https://gnkpzijxuciiaamakgzm.supabase.co/functions/v1/skin-weather?city=johannesburg
{
"city": "johannesburg", "cityLabel": "Johannesburg",
"uvNow": 9.4, "uvMax": 11.2, "uvPeakAt": "2026-10-03T10:00:00.000Z",
"humidity": 28, "tempMax": 27,
"fetchedAt": "2026-10-03T08:41:12.000Z", "stale": false,
"attribution": "Weather data © OpenWeather"
}Response values are illustrative; the shape matches the deployed function.
What's ready today, and what isn't
Every endpoint carries one of two labels. Select a label to filter the reference.
Getting started
Base URL
| Environment | Base URL | Status |
|---|---|---|
| Current | https://gnkpzijxuciiaamakgzm.supabase.co/functions/v1 | Live |
| Partner v1 | https://api.skinlabs.co.za/v1 | Proposed |
Partners should integrate against the v1 domain once it exists. The current URL exposes the hosting project reference and will change if SkinLabs ever migrates projects.
Authentication
The live skin weather endpoint is public and needs no credentials. Every v1 endpoint will require a partner key sent as a header, issued per organisation so usage can be metered and revoked independently.
X-SkinLabs-Key: sk_partner_live_xxxxxxxxxxxxxxxx
Conventions
| Topic | Convention |
|---|---|
| Format | JSON request and response bodies, UTF-8. |
| Money | South African rand. Fields end in _zar, and a currency field reads "ZAR". |
| Time | ISO 8601 in UTC, e.g. 2026-10-03T10:00:00.000Z. |
| Scores | 0 to 10, one decimal place. |
| Location | City keys only, never coordinates. Snap the user to the nearest supported city before calling. |
| CORS | Browser calls to the live endpoint are allowed from any origin. |
Skin weather
Returns today's UV index, UV peak, humidity and forecast high for one South African city. Clinics use it to time SPF reminders and adjust routine advice on high-UV or very dry days. POST with a JSON body of {"city": "…"} is also accepted and behaves identically.
Supported cities
| City key | Label |
|---|---|
johannesburg | Johannesburg |
pretoria | Pretoria |
cape-town | Cape Town |
durban | Durban |
gqeberha | Gqeberha |
bloemfontein | Bloemfontein |
east-london | East London |
polokwane | Polokwane |
mbombela | Mbombela |
kimberley | Kimberley |
Example request
curl "https://gnkpzijxuciiaamakgzm.supabase.co/functions/v1/skin-weather?city=cape-town"
const res = await fetch(
"https://gnkpzijxuciiaamakgzm.supabase.co/functions/v1/skin-weather?city=cape-town"
);
const weather = await res.json();
if (!res.ok) throw new Error(weather.error); // e.g. "unknown_city"
if (weather.stale) console.info("Cached reading from", weather.fetchedAt);Response fields
| Field | Type | Description |
|---|---|---|
city | string | The city key you sent. |
cityLabel | string | Display name for the city. |
uvNow | number | UV index right now. |
uvMax | number | Today's maximum UV index, local day. |
uvPeakAt | string or null | When today's UV peak occurs. null once the peak has passed. |
humidity | integer | Relative humidity right now, percent. |
tempMax | integer | Today's forecast high, °C. |
observedAt | string | When the weather provider produced the reading. |
fetchedAt | string | When SkinLabs last refreshed this city. |
stale | boolean | true when the provider is unavailable and a reading up to 6 hours old is being served instead. |
attribution | string | Credit line you must display alongside the data. |
Errors
| HTTP | error | What to do |
|---|---|---|
| 400 | unknown_city | Send one of the ten city keys above. |
| 400 | invalid_body | The POST body wasn't valid JSON. |
| 405 | method_not_allowed | Use GET or POST. |
| 502 | weather_unavailable | The provider failed and no reading under 6 hours old exists. Retry later and hide the card meanwhile. |
Product reviews
Independent reviews of skincare products sold in South Africa, each scored on efficacy, value, texture and performance in SA climate. Retailers and clinics can show a SkinLabs verdict next to a product they stock.
Proposed query parameters
| Parameter | Type | Description |
|---|---|---|
category | string | Product category, e.g. Moisturiser, Serum, Cleanser, Sunscreen. |
brand | string | Exact brand name. |
skin_type | string | Matches against skin_type_match. |
origin | string | south_africa or global_available_in_sa. |
limit, cursor | integer, string | Pagination. Default limit 20, maximum 100. |
Review object
| Field | Type | Description |
|---|---|---|
id | string | Slug of brand and product name. |
product_name, brand | string | Product identity. |
category | string | See the category list above. |
local_price_zar | number | Price at time of review. |
score_efficacy, score_value, score_texture, score_climate | number | 0 to 10. score_climate rates performance in SA heat, humidity, sun and dryness. |
verdict | string | One or two sentence editorial verdict. |
key_ingredients | string[] | Two to six actives named in the source. |
skin_type_match | string[] | Skin types the product suits. |
cautions | string[] | Warnings stated by the manufacturer only. Empty when none are stated. |
retailers | object[] | {retailer, price_zar, in_stock, url}. Only prices that SkinLabs has read from the retailer and verified are included. |
origin | string | Where the brand is from. |
is_sponsored | boolean | SkinLabs has a commercial interest in this product. Partners must show this disclosure. |
community_rating, community_rating_count | number or null, integer | Member star rating snapshot. null until a member rates. |
published_date | string | Date the review went live. |
Products and ingredients
A structured catalogue of products sold in South Africa and the ingredients in them. Integrators can enrich their own product listings, and clinics can look up an active a patient asks about. Ingredient search would reuse the alias-aware search that already powers the Ingredient Checker.
| Resource | Contents |
|---|---|
| Products | Product identity, variants and SkinLabs scores. |
| Product ingredients | Ingredient lists linked to each product. |
| Skin-type fit | Which skin types each product suits. |
| Prices by retailer | Only prices read from the retailer's own page and recently verified. |
| Brands | Brand name, origin and profile. |
| Ingredients | Profiles, aliases and cited sources. |
Ingredient compatibility
Send a list of ingredients and get back any pairs that shouldn't be layered, such as a retinoid with a strong acid. This is useful for clinics checking a patient's home routine against an in-clinic treatment.
POST /v1/interactions/check
X-SkinLabs-Key: sk_partner_live_…
Content-Type: application/json
{ "ingredients": ["retinol", "glycolic acid", "niacinamide"] }{
"resolved": [
{ "input": "retinol", "slug": "retinol", "resolved": true },
{ "input": "glycolic acid", "slug": "glycolic-acid", "resolved": true },
{ "input": "niacinamide", "slug": "niacinamide", "resolved": true }
],
"conflicts": [
{ "a": "retinol", "b": "glycolic-acid", "severity": "caution",
"guidance": "Alternate nights rather than layering." }
]
}Shape is a proposal. A pair with no sourced rule returns nothing; it is never inferred.
SKYNN assessments
Would let a clinic submit a patient's questionnaire answers and receive an Advanced AI Dermatology Analysis from SKYNN AI.
Partner enquiries
Would let an integrator's platform hand a clinic or brand lead to SkinLabs, for example a clinic interested in Practice Suite or a brand asking about Spotlight. Until it exists, use Partnerships or Contact us.
Errors and caching
Every error response is JSON with an error field holding a stable, machine-readable code. Branch on the code, not on the HTTP message text.
{ "error": "unknown_city" }Caching
Skin weather readings are cached per city on SkinLabs' side and shared by every caller, so partner traffic never multiplies calls to the weather provider. Responses carry Cache-Control: public, max-age=600; cache for up to 10 minutes on your side.
Proposed v1 rate limits
| Tier | Requests per minute | Requests per day |
|---|---|---|
| Sandbox | 30 | 1,000 |
| Clinic | 120 | 20,000 |
| Integrator | 600 | Agreed per contract |
Over the limit, v1 would return 429 with {"error":"rate_limited"} and a Retry-After header in seconds. These limits are a proposal and are not enforced today.
Usage rules
These rules come from obligations SkinLabs already carries. Partners inherit them when they display SkinLabs data.
| Rule | Why |
|---|---|
| Show “Weather data © OpenWeather” wherever skin weather appears. | Required by the weather provider's licence. |
Show a sponsored label on any review where is_sponsored is true. | SkinLabs has a commercial interest in those products, and hiding it would mislead shoppers. |
| Don't present scores or verdicts as clinical testing or a diagnosis. | SkinLabs scores follow a published editorial method, not lab trials, and its text deliberately avoids naming conditions. |
| Send city keys, never precise coordinates. | POPIA. SkinLabs neither receives nor stores precise location through this API. |
| Don't send patient names, ID numbers or contact details to any endpoint. | POPIA. Clinics keep identifying data on their side and use their own reference ID. |
Before v1 opens
What has to exist before the first partner clinic can use the proposed endpoints. None of this is built yet.
- A single partner gateway on
api.skinlabs.co.zathat maps v1 paths to read-only queries. - Partner keys: issued per organisation, stored hashed, checked on every request and revocable.
- Per-key rate limiting by tier.
- A review that the catalogue data exposed through the gateway contains no member, payment or analytics rows.
- A POPIA operator agreement, needed before any clinic sends patient questionnaire data to SKYNN.
- Published partner terms covering attribution, sponsored disclosure, no-diagnosis framing and pricing per tier.
- A sandbox with seeded catalogue data.
Interested in early access? Talk to us about partnerships.

