Skip to main content
This API returns data, not copy. No pre-written sentences, no branded language — you control how results are presented to your own users; we control the accuracy of the underlying analysis.
AI HairScan analyses one or more hair photos and returns a structured hair assessment: an overall health score, flagged findings, region-level density, detailed metrics, and (where configured) product recommendations from your own catalog.

Authentication

All requests require a bearer API key:
Authorization: Bearer sk_live_xxxxxxxxxxxxxxxx
Test keys (sk_test_...) run the full pipeline against non-billed, non-persisted requests — use them for integration testing.
API keys and webhook signing secrets are provisioned during onboarding. Once your account is set up, your HairHealth.ai account manager will issue your live and test keys. Sandbox credentials are available on request if you’d like to integrate against this spec before your account is fully live.

Base URL

Your base URL will be provided by HairHealth.ai in your onboarding document, along with your API key and webhook signing secret.
Endpoint paths below (e.g. /v1/hairscan) are relative to that base URL. The API is versioned in the path; breaking changes will ship as /v2, additive changes (new optional fields) will not change the version.

The scan object

This is the core object returned once a scan completes.
FieldTypeDescription
scan_idstringUnique identifier for this scan
statusenumprocessing, complete, or failed
created_attimestampISO 8601
completed_attimestampISO 8601
subject
object
Echoes the subject info submitted with the scan
hair_health_score
object
Overall score, band, confidence, and 3 pillar scores
flags
array
Notable findings worth surfacing to a user or clinician. No narrative — pair these with your own copy in your UI.
density_map
object
Four fixed scalp regions, each rated low / medium / high with an optional tag
metrics
object
Full detailed metrics, grouped into 2 categories
clinical_classification
object
Structured, not diagnostic language — a data label, not a report sentence
Top 3 ranked SKU matches — present only if a product catalog has been onboarded for your account
crm_summary
object
Compact record for CRM/lead routing

Endpoints

Create a scan

subject
string
required
JSON string — see subject schema above
images_frontal
file
required
Clear frontal hairline photo
images_crown
file
Top-down crown photo — recommended, needed for crown flags & density
images_temples
file
Improves hairline recession accuracy
images_donor
file
Back/sides — needed for donor region reading
intake_answers
string
Free-form JSON object, e.g. family_history, onset_duration_months. Exact fields are configured per account — talk to your account manager to set up your intake schema.
webhook_url
string
Overrides your account’s default webhook URL for this scan
curl -X POST https://<YOUR_BASE_URL>/v1/hairscan \
  -H "Authorization: Bearer sk_live_xxxxxxxxxxxxxxxx" \
  -F "subject={\"age\":34,\"sex\":\"male\"}" \
  -F "images_frontal=@frontal.jpg" \
  -F "images_crown=@crown.jpg"
Response — 202 Accepted, returned immediately:
{
  "scan_id": "scan_8f2a1c9e",
  "status": "processing",
  "created_at": "2026-07-05T14:32:00Z"
}

Retrieve a scan

GET /v1/hairscan/{scan_id}
curl https://<YOUR_BASE_URL>/v1/hairscan/scan_8f2a1c9e \
  -H "Authorization: Bearer sk_live_xxxxxxxxxxxxxxxx"
While processing:
{ "scan_id": "scan_8f2a1c9e", "status": "processing" }
Once complete, returns the full scan object. If it fails:
{
  "scan_id": "scan_8f2a1c9e",
  "status": "failed",
  "error": { "code": "low_image_quality", "message": "Frontal image too blurry to analyse." }
}

Webhooks

Instead of polling, register a webhook_url on your account (or per-request) to be notified when a scan completes.
POST <your webhook_url>
Content-Type: application/json
X-HairHealth-Signature: sha256=<hmac>
{
  "event": "hairscan.completed",
  "scan_id": "scan_8f2a1c9e",
  "data": { /* full scan object */ }
}
event is hairscan.completed or hairscan.failed.
Always verify the X-HairHealth-Signature header before trusting a webhook payload. Compute an HMAC-SHA256 of the raw request body using your webhook signing secret (issued alongside your API key) and compare it to the header. Reject the request if they don’t match.

Errors

Errors return a standard shape:
{
  "error": {
    "type": "invalid_request_error",
    "code": "missing_image",
    "message": "images_frontal is required."
  }
}
HTTP statustype
400invalid_request_error
401authentication_error
404not_found_error
422processing_error (e.g. image quality)
429rate_limit_error
500api_error
message is for developers/logs — don’t surface it directly to end users.

Rate limits

Each API key has a request quota tied to your account’s plan. Every response includes:
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 987
X-RateLimit-Reset: 1720188000
Exceeding your quota returns 429 with type: rate_limit_error.

Changelog

  • v1 — initial release.