MaIA Scan

MaIA API · v1 · technical documentation

MaIA API: skin and hair analysis and virtual try-on, over HTTP.

One key, two endpoints, one credit per successful call. This page describes exactly what the gateway at maiascan.ai/api accepts and returns — fields, headers, error codes and limits — with samples in curl, Node.js and Python.

Overview

The MaIA API is the HTTPS gateway B4A exposes at maiascan.ai for two of the three MaIA products. You authenticate with a key from your account, send a photo as multipart/form-data and get the answer synchronously: a beauty profile as JSON (MaIA Scan) or the same photo as PNG with the product applied (MaIA Try-On). Every successful call consumes one credit from the account balance.

Nothing is assumed about your stack: any HTTP client that can send multipart works. There is no mandatory SDK, no webhook and no session state — the response of the call is the result.

Availability of the three MaIA products
ProductAvailabilityEndpointDescription
MaIA ScanSelf-service via APIPOST /api/v1/scanSkin and hair profile as JSON from a front selfie (side photo optional). 1 credit per analysis.
MaIA Try-OnSelf-service via APIPOST /api/v1/try-onThe customer's photo comes back as PNG with the lipstick applied — in this version, the six demo shades and three finishes. 1 credit per render. A palette calibrated to your products: as a project.
MaIA Beauty AdvisorAs a projectNot a self-service endpoint. B4A delivers it as a project — on the brand's WhatsApp, as a product-page widget or in chat — using the same profile data MaIA Scan returns and your brand's integrated catalog.

Start in four steps

  1. Create the account

    Sign in with Google or receive a magic link by e-mail. No password, no manual approval.

    Create account
  2. Buy a credit package

    Starter (500 credits), Growth (2,500) or Scale (10,000) — card, Pix or boleto in our own checkout. Credits never expire.

    See packages
  3. Create the API key

    Key creation unlocks after the first purchase. The key (msk_live_…) is shown exactly once — copy it into your secrets vault.

    My account
  4. Make the first call

    Send a selfie to POST /api/v1/scan, or a photo with a shade and a finish to POST /api/v1/try-on. The answer comes back in the same request.

curl -X POST https://maiascan.ai/api/v1/scan \
  -H "Authorization: Bearer msk_live_…" \
  -H "x-request-id: order-8841" \
  -F "front=@selfie.jpg;type=image/jpeg" \
  -F "side=@profile.jpg;type=image/jpeg"

Authentication

Every route under /api/v1 requires the Authorization header with an API key in Bearer format. The key identifies the account (and therefore the credit balance) and the key itself — usage is recorded per key.

Authorization: Bearer msk_live_…
  • Format: msk_live_ followed by 32 alphanumeric characters. We store only the SHA-256 hash and a display prefix; the full text is shown once, at creation.
  • Up to 10 active keys per account. Revoke any key at any time in the portal (My account → API keys); revocation applies from the next call.
  • Missing, invalid or revoked key: 401 with { "error": "invalid_api_key" }.
  • Use the key server-side only. Never embed it in browser code or a distributed app — whoever holds the key spends your credits.

Credits

1 credit = 1 successful call, whether an analysis (scan) or a render (try-on). The credit is reserved before we call the model and settled when the answer arrives; if the model fails, the reservation is refunded automatically — you never pay for a result you did not receive, and we never process a photo we cannot charge.

PackagePriceCreditsper call
StarterR$ 998500R$ 2.00
GrowthR$ 1,9982,500R$ 0.80
ScaleR$ 4,99810,000R$ 0.50

One-off and automatic top-ups use the per-call price of the last paid package. Credits never expire. Enterprise: talk to us.

  • The balance after the call comes with every successful response: credits_remaining in the scan JSON and the X-Credits-Remaining header on the try-on.
  • Zero balance: the call is refused before any processing with 402 and { "error": "insufficient_credits", "balance": 0 }. The attempt is recorded in your usage, with no charge.
  • Auto top-up: in the portal you set a threshold and an amount; when the balance drops below it we create an order and e-mail the payment link (we never charge a card without you).
See packages and balance

POST/api/v1/scan

Send a front selfie (and optionally a side photo) and receive the person's beauty profile as JSON. Synchronous: the response is the result.

Production status (2026-09-22): the gateway is not yet connected to the MaIA Scan API analysis service. Until the credential is issued, POST /api/v1/scan answers 503 upstream_not_configured and no credit is charged. The contract in this section is final — integrate against it; the answer will become 200 with no change on your side.

Request

multipart/form-data body with the fields below. Headers: Authorization (required) and x-request-id (optional).

FieldTypeRequiredDescription
frontfileyesFront photo of the face. Declared Content-Type must be image/jpeg, image/png, image/webp or image/heic. Up to 10 MB.
sidefileoptionalSide (profile) photo of the same person, same types and limit. Sent in the same call — no extra credit.
x-request-idheaderoptionalYour request identifier (up to 80 characters). Stored with the usage event for reconciliation; it does not deduplicate calls.

Response

200 OK, application/json. The envelope is fixed; result is the analysis document returned by the MaIA Scan API.

FieldTypeDescription
session_idstringIdentifier of the usage event (the charged session). Keep it for support and reconciliation.
capture_idstringIdentifier of the capture at the MaIA Scan API.
credits_remainingintegerAccount balance after this call.
resultobjectBeauty profile: skin tone and undertone, skin type, Skin Scan metrics (redness, oiliness, texture, pores, spots, hydration, pigmentation), hair color and structure, photo-quality checks and per-field confidence. Keys follow the MaIA Scan API version — treat it as a JSON document and read the fields you need.
Response
HTTP/1.1 200 OK
Content-Type: application/json

{
  "session_id": "cmg1x3k9h0001abcd",
  "capture_id": "cap_01J8…",
  "credits_remaining": 499,
  "result": { … }
}

Errors

StatusCodeMeaningCredit charged?
400front_requiredThe front field was not sent.no
400unsupported_media_typeContent-Type of one of the files outside JPEG/PNG/WEBP/HEIC.no
401invalid_api_keyMissing, invalid or revoked key.no
402insufficient_creditsZero balance; the body carries balance.no
413File above 10 MB (refused on receipt; generic body).no
429More than 120 requests per minute from the same IP.no
502analysis_failedThe model did not complete the analysis. The reserved credit was refunded; the body carries session_id.no
503upstream_not_configuredThe analysis service is not connected in this environment. Nothing is charged.no

Limits

  • Up to 10 MB per file, at most 2 files (front + side).
  • Synchronous call; the gateway waits for the model for up to 60 s.
  • 120 requests per minute per source IP.

Consent is mandatory: obtain the person's explicit authorization before sending the photo (LGPD). MaIA Scan does no facial recognition or identification; it returns cosmetic analysis for personalization.

Samples

curl -X POST https://maiascan.ai/api/v1/scan \
  -H "Authorization: Bearer msk_live_…" \
  -H "x-request-id: order-8841" \
  -F "front=@selfie.jpg;type=image/jpeg" \
  -F "side=@profile.jpg;type=image/jpeg"

POST/api/v1/try-on

Send a front photo with a shade and a finish and receive the same photo as PNG with the lipstick applied. Only the makeup area changes. Synchronous.

Request

multipart/form-data body with exactly the fields below (unknown text fields are refused with 400). Headers: Authorization (required) and x-request-id (optional).

FieldTypeRequiredDescription
photofileyesFront photo, JPEG or PNG (we check the bytes, not the Content-Type). Up to 6 MB; short side of at least 320 px; mouth visible.
shadetextyesOne of the demo shades: rose, red, berry, coral, plum or nude (see GET /api/v1/try-on/shades).
finishtextyesmatte, satin or gloss.
x-request-idheaderoptionalYour request identifier (up to 80 characters), stored on the usage event.

Response

200 OK, image/png — the body is the rendered image. The receipt travels in headers:

HeaderDescription
X-Session-IdIdentifier of the usage event (the charged session).
X-Credits-RemainingAccount balance after this call.
X-TryOn-Finish-RequestedThe finish you asked for.
X-TryOn-Finish-AppliedThe finish actually applied. When the model cannot apply the requested finish to a photo, the gateway renders at the shade's trained finish (matte for red/berry/plum, satin for rose/coral/nude) instead of failing — compare the two headers to tell the person.
Cache-Controlno-store. The image sits in no cache on our side.
Response
HTTP/1.1 200 OK
Content-Type: image/png
Cache-Control: no-store
X-Session-Id: cmg1x4p2r0002efgh
X-Credits-Remaining: 498
X-TryOn-Finish-Requested: gloss
X-TryOn-Finish-Applied: gloss

<PNG bytes>

Errors

StatusCodeMeaningCredit charged?
400photo_requiredThe photo field was not sent.no
400Unknown or malformed text field (body validation).no
401invalid_api_keyMissing, invalid or revoked key.no
402insufficient_creditsZero balance; the body carries balance.no
413photo_too_largePhoto above 6 MB. Uploads far above the limit are cut on receipt with a generic 413.no
422invalid_selectionshade or finish outside the list.no
422unsupported_photoThe bytes are neither JPEG nor PNG (or the file is empty).no
422bad_photoThe model refused the photo (face not frontal, mouth not visible, short side below 320 px). Credit refunded.no
429More than 120 requests per minute from the same IP.no
503upstream_unavailableThe render service failed or is busy. Credit refunded; retry in a moment.no
503upstream_not_configuredThe render service is not connected in this environment. Nothing is charged.no

Limits

  • JPEG or PNG, up to 6 MB, short side ≥ 320 px. Photos larger than 1600 × 1079 px may be downscaled by the model before rendering.
  • About 2 to 4 seconds per render. The render service scales to zero: the first call after an idle period can take longer (the gateway waits up to 60 s and retries a late cold-start 422 once).
  • 120 requests per minute per source IP.
  • Simulated color may differ from the actual product. In this API version the shades are the six demo shades; a palette calibrated to your brand's products is delivered as a project.

Samples

curl -X POST https://maiascan.ai/api/v1/try-on \
  -H "Authorization: Bearer msk_live_…" \
  -F "photo=@selfie.jpg;type=image/jpeg" \
  -F "shade=rose" \
  -F "finish=gloss" \
  -D headers.txt \
  -o try-on.png
# headers.txt now holds X-Session-Id, X-Credits-Remaining, X-TryOn-Finish-Applied

GET/api/v1/try-on/shades

Lists the shades and finishes accepted by POST /api/v1/try-on, with each shade's reference hex and trained finish. Consumes no credit.

Response

200 OK, application/json, Cache-Control: private, max-age=3600. Requires the same key (401 without it).

FieldTypeDescription
shades[].idstringValue accepted in the try-on shade field.
shades[].hexstringReference color of the shade (#RRGGBB) to show in your UI.
shades[].trained_finishstringFinish the shade was trained with — the X-TryOn-Finish-Applied fallback.
finishesstring[]Values accepted in the finish field.
max_upload_bytesintegerPhoto upload limit in bytes (6 MB).
Response
{
  "shades": [
    { "id": "rose",  "hex": "#B85C75", "trained_finish": "satin" },
    { "id": "red",   "hex": "#B8293D", "trained_finish": "matte" },
    { "id": "berry", "hex": "#883252", "trained_finish": "matte" },
    { "id": "coral", "hex": "#D96758", "trained_finish": "satin" },
    { "id": "plum",  "hex": "#703B5B", "trained_finish": "matte" },
    { "id": "nude",  "hex": "#AF786F", "trained_finish": "satin" }
  ],
  "finishes": ["matte", "satin", "gloss"],
  "max_upload_bytes": 6291456
}

Samples

curl https://maiascan.ai/api/v1/try-on/shades \
  -H "Authorization: Bearer msk_live_…"

Errors and status codes

Every error is JSON with a stable error field (for your code) and a readable message (for your logs). Compare error, not message. No error consumes a credit: when the failure happens after the reservation (502 on scan, 422 bad_photo or 503 on try-on), the credit is refunded in the same transaction.

StatusMeaning
400Malformed request: required field missing, file type not declared correctly or unknown text field.
401invalid_api_key — missing, invalid or revoked key.
402insufficient_credits — zero balance; buy a package or wait for the auto top-up.
413File above the limit (10 MB on scan, 6 MB on try-on).
422Well-formed input, invalid content: invalid_selection, unsupported_photo, bad_photo (try-on).
429Requests-per-minute limit exceeded. Wait and retry with backoff.
502analysis_failed — the analysis did not complete; credit refunded (scan).
503upstream_unavailable (credit refunded) or upstream_not_configured (nothing charged).
Shape of an error
HTTP/1.1 402 Payment Required
Content-Type: application/json

{
  "error": "insufficient_credits",
  "message": "Buy a credit package to continue.",
  "balance": 0
}

Limits, idempotency and x-request-id

  • Rate limit: 120 requests per minute per source IP, on every /api/v1 route. There is no per-key limit yet; for higher volumes, talk to us.
  • Idempotency: calls are not idempotent — every 200 consumes a credit. Do not automatically repeat a call that returned 200; retry only 429, 502 and 503 (which do not charge), with exponential backoff.
  • x-request-id: send your identifier (up to 80 characters). It is stored on the usage event and shows up in reconciliation; quote it with session_id / X-Session-Id when talking to support.
  • Timeouts: the gateway waits for the model for up to 60 s. Give your HTTP client at least 90 s so you do not abandon a call that will still be charged and answered.

Data handling and consent

  • Consent: you are responsible for obtaining the person's explicit authorization before sending any photo, under LGPD. In our own demo the wording is: "I authorize the processing of this photo only to generate the simulation; it is not stored."
  • Photos at the gateway: they live in memory for the duration of the request and are forwarded once to the model. The gateway writes photos to no disk, database or log — logs carry size, shade, finish, latency and status.
  • Try-on: the photo is stored neither by the gateway nor by the render service; only the PNG comes back to you, with Cache-Control: no-store.
  • Scan: the MaIA Scan API stores the structured result with model and pipeline versions; raw-image retention is configurable per project — talk to us to set yours.
  • What we do not do: facial recognition, identification of people, inference of protected traits or medical diagnosis. Low-confidence fields should be confirmed with the person.
  • Usage record: per call we keep account, key, endpoint, status, credit, latency, x-request-id and, on scan, the capture_id — that is what your usage page shows.
B4A Privacy Policy →

Base URL, versioning and contact

https://maiascan.ai/api
  • Base URL: https://maiascan.ai/api — always HTTPS. Public routes live under /v1.
  • Versioning: the version is in the path (v1). Compatible changes (new JSON fields, new headers, new shades) ship without a version change; any incompatible change is born as /v2, with /v1 kept in parallel for an announced period.
  • Gateway status: GET https://maiascan.ai/api/health returns { "status": "ok", "commit": "…" } — no authentication.
  • Technical and commercial support: comercial@b4a.ai.

Need more volume, per-key limits, a palette calibrated to your catalog, MaIA Beauty Advisor or an enterprise contract? Talk to us — B4A delivers full projects on top of the same API.