Base URL https://api.layerframes.com. All endpoints are JSON over HTTPS. Versioning is in the path; we never break a released version.
Every request carries a bearer key. Secret keys (lf_live_…, lf_test_…) are server-side only. Publishable keys (lf_pub_…) are safe in the browser and are scoped to the domains you allowlist in the dashboard.
Authorization: Bearer lf_live_9c2f7ad1e3b04c...
Content-Type: application/json
LayerFrames-Version: 2026-05-01A frame is one wearable SKU/colorway. Create frames once; renders reference them by id. You can also sync automatically from a Shopify, BigCommerce, or CSV product feed.
/v1/framesskustringrequiredYour internal identifier. Used to map renders back to your catalog.
namestringrequiredDisplay name, e.g. "Classic Acetate — Black".
images[]string[]required1–6 product URLs. Front-on and 3/4 views give the best geometry recovery. A GLB model can be supplied instead via model_url.
measurementsobjectoptionallens_width_mm, bridge_mm, temple_mm, lens_height_mm. Supplying these makes fit scoring exact; without them we estimate from imagery.
lensobjectoptionaltint (none | gradient | solid | mirror), color hex, opacity 0–1, polarized boolean.
metadataobjectoptionalArbitrary key/value pairs echoed back on every render and webhook.
curl https://api.layerframes.com/v1/frames \
-H "Authorization: Bearer lf_live_..." \
-d '{
"sku": "CA-BLK-52",
"name": "Classic Acetate — Black",
"images": ["https://cdn.yourstore.com/ca-blk-front.jpg",
"https://cdn.yourstore.com/ca-blk-angle.jpg"],
"measurements": { "lens_width_mm": 52, "bridge_mm": 18, "temple_mm": 145 },
"lens": { "tint": "none" }
}'{
"id": "frm_classic_acetate_black",
"sku": "CA-BLK-52",
"status": "processing", // -> "ready" in ~40s, or subscribe to frame.ready
"preview_url": null,
"created_at": "2026-05-01T12:04:11Z"
}Also available: GET /v1/frames (paginated list), GET /v1/frames/:id, PATCH /v1/frames/:id, DELETE /v1/frames/:id, and POST /v1/frames/bulk for up to 500 SKUs per request.
The core endpoint. Give it a subject and a frame; get back a photoreal composite. Synchronous by default (p50 ≈ 800 ms); pass async: true for batch jobs and receive a webhook.
/v1/tryonframe_idstringrequiredA ready frame. Pass an array of up to 8 ids to render a whole set from one face pass — much cheaper than 8 separate calls.
subjectobjectrequiredOne of image_url, image_base64, or face_id (a stored face profile from a prior session, if the shopper consented).
options.outputstringoptionalpng | jpeg | webp | base64. Default png.
options.sizestringoptionalUp to 2048x2048. Default matches the input aspect ratio at 1024 px on the long edge.
options.lens_tintstringoptionalauto follows the frame record; off renders clear lenses for optical SKUs.
options.backgroundstringoptionalkeep | blur | remove | a hex color for on-brand PDP renders.
options.return_fitbooleanoptionalInclude measurements and a fit score. Default true.
idempotency_keystringoptionalSafe retries. Identical keys within 24 h return the original render.
{
"id": "try_8Kd21mQz",
"status": "succeeded",
"renders": [
{ "frame_id": "frm_classic_acetate_black",
"url": "https://cdn.layerframes.com/r/8Kd21mQz-1.png",
"expires_at": "2026-05-08T12:04:11Z" }
],
"fit": {
"score": 0.91,
"pupillary_distance_mm": 63.4,
"face_width_mm": 141.2,
"notes": ["temple_length_ideal", "bridge_slightly_wide"]
},
"latency_ms": 812,
"usage": { "renders": 1 }
}For mirror-style try-on, open a WebSocket session and stream frames. The shopper can swap SKUs mid-session with no re-capture; head tracking persists.
/v1/liveconst session = await layerframes.live.connect({
publishableKey: "lf_pub_9c2f...",
video: document.querySelector("#cam"),
frameId: "frm_classic_acetate_black",
fps: 30, // 24–30 typical on mobile
quality: "balanced", // "fast" | "balanced" | "max"
});
session.on("ready", ({ latencyMs }) => console.log(latencyMs));
session.swapFrame("frm_aviator_gold"); // instant, no re-capture
const still = await session.capture(); // shareable PNG
session.close();Live sessions bill at one render per second of active streaming, rounded up, and stop billing the moment the tab is hidden.
Rank your catalog against a shopper's measured face. Great for "frames that fit you" rails and for filtering out SKUs that physically won't work.
/v1/recommendations{
"face": { "width_mm": 141.2, "shape": "oval", "pd_mm": 63.4 },
"results": [
{ "frame_id": "frm_aviator_gold", "fit_score": 0.94, "reason": "width_match" },
{ "frame_id": "frm_round_tortoise","fit_score": 0.88, "reason": "shape_contrast" },
{ "frame_id": "frm_square_navy", "fit_score": 0.61, "reason": "bridge_narrow" }
]
}Register endpoints in the dashboard or via POST /v1/webhooks. Every delivery is signed with HMAC-SHA256 in the LayerFrames-Signature header and retried with exponential backoff for 24 hours.
frame.readyA catalog asset finished processing and can be rendered.
frame.failedIngestion failed; payload includes the reason.
tryon.succeededAn async render completed.
tryon.failedAn async render failed.
session.startedA shopper opened live try-on.
session.added_to_cartAdd-to-cart fired inside a try-on session.
usage.thresholdYou crossed 80% or 100% of a billing threshold.
{
"error": {
"type": "no_face_detected",
"message": "No face was detected in the supplied subject image.",
"docs_url": "https://layerframes.com/docs#errors",
"request_id": "req_7Xa9wQ"
}
}401 invalid_keyMissing, revoked, or wrong-mode API key.
403 domain_not_allowedPublishable key used from an unlisted origin.
404 frame_not_foundUnknown frame_id, or the frame is still processing.
422 no_face_detectedFace too small, occluded, or out of frame.
429 rate_limitedRetry after the seconds given in Retry-After.
503 capacityRender capacity saturated; safe to retry idempotently.
Rate limits: 60 renders/second on Growth, 10/second in sandbox, custom on Enterprise. Subject images are processed in memory and deleted within 60 seconds unless you pass store_face: true with the shopper's consent. Renders are served from a signed CDN URL that expires in 7 days by default.
npm i @layerframes/node # server
npm i @layerframes/react # storefront components
pip install layerframes # python
# also: PHP, Ruby, Go, and a Shopify appWe hand out keys on the intro call so we can pre-load your catalog and show you real renders of your own frames.
Schedule a call