Developer Documentation

SKINN AI REST API

Integrate professional AI-powered skin analysis directly into your clinic portal, booking system, or patient management software. The API accepts images and returns a structured JSON report including skin age, condition scores, personalised routines, and 3-month projections.

Base URL https://skinnai-worker.london800800.workers.dev

Introduction

The SKINN AI API is a REST API powered by Anthropic Claude Vision. It accepts facial images and returns detailed skin analysis data including condition detection, scoring, personalised skincare routines, product recommendations, and a 3-month improvement projection.

All responses are JSON. All requests must be authenticated with an API key. The API is available to clinic licensees on the Professional and Enterprise plans.

API Access API keys are issued to clinic licensees. To obtain an API key, subscribe to a Professional or Enterprise plan at skinnai.co.uk/clinics.

Base URL

All API endpoints are served from the Cloudflare Worker. Use HTTPS only — HTTP requests will be redirected.

# Production https://skinnai-worker.london800800.workers.dev # API versioning is in the path https://skinnai-worker.london800800.workers.dev/api/v1/...

Authentication

All endpoints except GET /api/v1/status require authentication. Pass your API key using either of the following methods:

Method 1 — Authorization header (recommended)
Authorization: Bearer sk_live_your_api_key_here
Method 2 — X-API-Key header
X-API-Key: sk_live_your_api_key_here
Keep your API key secret Never expose your API key in client-side JavaScript, mobile app binaries, or public repositories. Always make API calls from your server.

If your key is missing or invalid, the API returns 401 Unauthorized:

{ "error": "Unauthorized", "message": "Provide a valid API key via X-API-Key header or Authorization: Bearer <key>", "docs": "https://skinnai.co.uk/api-docs#authentication" }

Rate Limits

Rate limits are applied per API key. Each key is permitted 60 requests per hour. Rate limit headers are included in every response:

HeaderDescription
X-RateLimit-RemainingRequests remaining in the current window
X-RateLimit-ResetUnix timestamp (ms) when the window resets

When the limit is exceeded, the API returns 429 Too Many Requests:

{ "error": "Rate limit exceeded", "message": "60 requests/hour per API key", "reset_at": "2026-06-05T14:00:00.000Z" }

GET /api/v1/status

GET /api/v1/status No authentication required

Returns the current API status, version, and available endpoints. This endpoint does not require an API key and can be used for health checks.

Example Request
curl https://skinnai-worker.london800800.workers.dev/api/v1/status
200 Response
200 OK
{ "status": "operational", "version": "v1", "model": "claude-opus-4-5", "endpoints": [ "GET /api/v1/status", "GET /api/v1/analyze?image_url=<url>[&language=en]", "POST /api/v1/analyze" ], "docs": "https://skinnai.co.uk/api-docs", "timestamp": "2026-06-05T12:00:00.000Z" }

GET /api/v1/analyze

GET /api/v1/analyze Requires API key

Analyse a skin image by URL. The worker fetches the image, runs Claude Vision analysis, and returns a structured JSON report. Suitable for images already hosted on a publicly accessible URL.

Query Parameters
ParameterTypeRequiredDescription
image_url string Required Publicly accessible URL of a JPG or PNG facial image. Max 8MB.
language string Optional Response language code. Default: en. Supported: en tr ar fr de es it el
curl \ -H "X-API-Key: sk_live_your_api_key_here" \ "https://skinnai-worker.london800800.workers.dev/api/v1/analyze?image_url=https%3A%2F%2Fexample.com%2Fpatient.jpg&language=en"
import requests response = requests.get( "https://skinnai-worker.london800800.workers.dev/api/v1/analyze", headers={"X-API-Key": "sk_live_your_api_key_here"}, params={ "image_url": "https://example.com/patient.jpg", "language": "en" } ) data = response.json() print(f"Skin Score: {data['analysis']['skinScore']}/100") print(f"Skin Age: {data['analysis']['skinAge']}")
const params = new URLSearchParams({ image_url: "https://example.com/patient.jpg", language: "en" }); const res = await fetch( `https://skinnai-worker.london800800.workers.dev/api/v1/analyze?${params}`, { headers: { "X-API-Key": "sk_live_your_api_key_here" } } ); const { analysis } = await res.json(); console.log(`Score: ${analysis.skinScore}/100`);
200 Response
200 OK
{ "success": true, "request_id": "a3f2e1c4-8b7d-4e2a-9f1b-c3d5e6f7a8b9", "timestamp": "2026-06-05T12:00:00.000Z", "analysis": { /* see Response Schema */ } }

POST /api/v1/analyze

POST /api/v1/analyze Requires API key

Analyse a skin image sent as base64. Optionally provide a patient email to receive a branded HTML report by email. Returns the full analysis plus generated report HTML — ready for PDF rendering or embedding in your EHR.

Request Body (JSON)
FieldTypeRequiredDescription
image_base64 string Required Base64-encoded image data (JPG or PNG). Max decoded size: 8MB.
mime_type string Optional MIME type of the image. Default: image/jpeg. Accepted: image/jpeg, image/png
email string Optional Patient email. If provided, a branded HTML report is emailed to this address.
language string Optional Report language. Default: en. Supported: en tr ar fr de es it el
patient_ref string Optional Your internal patient reference ID. Echoed back in the response for record-linking.
curl -X POST \ -H "Authorization: Bearer sk_live_your_api_key_here" \ -H "Content-Type: application/json" \ -d '{"image_base64":"<base64>","email":"[email protected]","patient_ref":"PAT-00142"}' \ https://skinnai-worker.london800800.workers.dev/api/v1/analyze
import requests, base64 with open("patient_photo.jpg", "rb") as f: b64 = base64.b64encode(f.read()).decode() response = requests.post( "https://skinnai-worker.london800800.workers.dev/api/v1/analyze", headers={ "Authorization": "Bearer sk_live_your_api_key_here", "Content-Type": "application/json", }, json={ "image_base64": b64, "mime_type": "image/jpeg", "email": "[email protected]", "language": "en", "patient_ref": "PAT-00142", } ) data = response.json() print(data["analysis"]["summary"])
const toBase64 = file => new Promise((resolve, reject) => { const reader = new FileReader(); reader.onload = () => resolve(reader.result.split(',')[1]); reader.onerror = reject; reader.readAsDataURL(file); }); const b64 = await toBase64(imageFile); const res = await fetch( "https://skinnai-worker.london800800.workers.dev/api/v1/analyze", { method: "POST", headers: { "Authorization": "Bearer sk_live_your_api_key_here", "Content-Type": "application/json", }, body: JSON.stringify({ image_base64: b64, email: "[email protected]", patient_ref: "PAT-00142", }), } ); const { success, analysis, report_html } = await res.json();
200 Response
200 OK
{ "success": true, "request_id": "b4e3f2d5-9c8e-5f3b-a2c4-d6e7f8a9b0c1", "timestamp": "2026-06-05T12:00:00.000Z", "patient_ref": "PAT-00142", "email_sent": true, "analysis": { /* see Response Schema */ }, "report_html": "<!DOCTYPE html>..." }

Response Schema

The analysis object returned by both analyze endpoints has the following structure:

FieldTypeDescription
skinScorenumberOverall skin health score, 0–100
skinAgenumberEstimated biological skin age in years
skinTypestringOne of: Normal, Dry, Oily, Combination, Sensitive
conditionsstring[]Array of detected skin conditions (e.g. "Mild dehydration", "Periorbital pigmentation")
uvDamagestringUV damage level: None, Mild, Moderate, or Severe
hydrationstringEstimated hydration level as a percentage string (e.g. "72%")
concernsstring[]Top 3 skin concerns in priority order
routine.morningstring[]4-step personalised morning skincare routine
routine.eveningstring[]4-step personalised evening skincare routine
productsobject[]Array of 4 recommended product types with key ingredient and reason
nutritionstring[]4 nutritional recommendations for skin health
projection3monthstringNatural language description of expected 3-month improvement
projectedScorenumberProjected skin score after 3 months of recommended routine
gpNotebooleanTrue if unusual lesions or features warranting GP consultation were detected
summarystring2–3 sentence professional summary in the requested language
Full Analysis Object Example
{ "skinScore": 78, "skinAge": 34, "skinType": "Combination", "conditions": ["Mild periorbital dehydration", "T-zone sebaceous activity"], "uvDamage": "Moderate", "hydration": "72%", "concerns": ["UV protection", "Pore management", "Barrier support"], "routine": { "morning": [ "Gentle foaming cleanser", "Vitamin C serum 15%", "Hyaluronic acid moisturiser", "SPF50+ broad spectrum" ], "evening": [ "Oil cleanser double-cleanse", "AHA/BHA toner 2×/week", "Retinol 0.3%", "Ceramide repair cream" ] }, "products": [ { "type": "Sunscreen", "ingredient": "Zinc Oxide", "reason": "Moderate UV damage detected" }, { "type": "Serum", "ingredient": "L-Ascorbic Acid", "reason": "Brightening and UV repair" }, { "type": "Moisturiser", "ingredient": "Ceramide NP", "reason": "Barrier reinforcement" }, { "type": "Exfoliant", "ingredient": "Salicylic Acid 2%", "reason": "Pore clarity" } ], "nutrition": ["Omega-3 (salmon, flaxseed)", "Vitamin E (almonds, avocado)", "Zinc (pumpkin seeds)", "Green tea antioxidants"], "projection3month": "With consistent SPF use and vitamin C, UV damage markers are expected to reduce significantly. Texture and hydration should improve noticeably within 6 weeks.", "projectedScore": 88, "gpNote": false, "summary": "Overall healthy skin with good hydration. Moderate UV exposure history warrants daily SPF50+ and antioxidant support. Combination skin is well-managed; targeted BHA use will address T-zone congestion." }

Error Codes

HTTP CodeErrorCause & Resolution
200 Success. Analysis returned in body.
400 Bad Request Missing required parameters, invalid image format, image too large (>8MB), or invalid JSON body.
401 Unauthorized API key missing or invalid. Check your Authorization or X-API-Key header.
429 Rate Limit Exceeded More than 60 requests/hour from this API key. Wait for the window to reset (see X-RateLimit-Reset header).
500 Internal Server Error Unexpected server-side error. Retry with exponential backoff. Contact support if persistent.
Error Response Shape
{ "error": "Bad Request", "message": "Missing required field: image_base64", "required_fields": ["image_base64"], "optional_fields": ["mime_type", "email", "language", "patient_ref"] }

Quickstart

Get up and running in under 5 minutes. This example reads an image file, sends it to the API, and prints the skin score.

# pip install requests import requests, base64, sys API_KEY = "sk_live_your_api_key_here" IMAGE_PATH = "patient.jpg" with open(IMAGE_PATH, "rb") as f: b64 = base64.b64encode(f.read()).decode() res = requests.post( "https://skinnai-worker.london800800.workers.dev/api/v1/analyze", headers={"Authorization": f"Bearer {API_KEY}"}, json={"image_base64": b64, "mime_type": "image/jpeg"}, timeout=30 ) res.raise_for_status() data = res.json()["analysis"] print(f"Skin Score: {data['skinScore']}/100") print(f"Skin Age: {data['skinAge']}") print(f"UV Damage: {data['uvDamage']}") print(f"Hydration: {data['hydration']}") print(f"Summary: {data['summary']}")
// Node.js 18+ (native fetch) const fs = require('fs'); const API_KEY = 'sk_live_your_api_key_here'; const b64 = fs.readFileSync('patient.jpg').toString('base64'); const res = await fetch( 'https://skinnai-worker.london800800.workers.dev/api/v1/analyze', { method: 'POST', headers: { 'Authorization': `Bearer ${API_KEY}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ image_base64: b64, mime_type: 'image/jpeg' }), } ); const { analysis } = await res.json(); console.log(`Skin Score: ${analysis.skinScore}/100`); console.log(`Summary: ${analysis.summary}`);
<?php $apiKey = 'sk_live_your_api_key_here'; $b64 = base64_encode(file_get_contents('patient.jpg')); $ch = curl_init('https://skinnai-worker.london800800.workers.dev/api/v1/analyze'); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_POST => true, CURLOPT_HTTPHEADER => [ 'Authorization: Bearer ' . $apiKey, 'Content-Type: application/json', ], CURLOPT_POSTFIELDS => json_encode([ 'image_base64' => $b64, 'mime_type' => 'image/jpeg', ]), ]); $data = json_decode(curl_exec($ch), true); echo 'Score: ' . $data['analysis']['skinScore'] . "/100\n";
Next Steps Obtain your API key by subscribing to the Professional or Enterprise plan at skinnai.co.uk/clinics. Keys are issued within 24 hours of plan activation. Set CLINIC_API_KEYS in your Cloudflare Worker environment with the issued key(s).