How the Screenshots Live Render API Works — A Practical Guide for Store Listing Automation
A technical walkthrough of how the Screenshots Live render API processes templates, applies YAML overrides, and outputs store-ready screenshots — with real code examples and CI/CD patterns for whitelabel setups.
What This Post Covers
If you're shipping whitelabel apps — or even just one app with multiple store listings — you've probably hit the screenshot wall. Apple wants screenshots for every device size. Google Play wants its own set. Support three languages and you've tripled your workload. Now multiply that by however many branded variants you maintain.
The previous post covered why Screenshots Live exists. This one is the hands-on follow-up: how the render API is designed, what it expects, and how to wire it into your build pipeline so screenshots stop being a manual chore.
How the Render Pipeline Is Designed
The render system isn't a synchronous "send request, get image" endpoint. It's a job-based pipeline, which matters once you're batch-rendering dozens or hundreds of screenshots.
Here's the flow:
- You submit a render request with a template ID and optional YAML overrides (text, screenshots, device frames — anything marked as swappable).
- Validation — the system checks that your template exists, you own it, and every field you're overriding is actually allowed. URLs get validated against private IP ranges and restricted protocols.
- Queuing — the job enters a Redis-backed BullMQ queue. Your API call returns immediately with a
jobIdandPendingstatus. This is the key: you don't wait for the render to finish. - Rendering — a Rust-based worker picks up the job, fetches the template and images (with LRU caching for repeated renders), loads fonts, renders the canvas using Skia, and generates the output.
- Packaging — the rendered images get zipped and uploaded to object storage.
- Download — you poll the status endpoint. When it's
Completed, you hit the download endpoint for a presigned URL (valid for 1 hour).
The async design is intentional. When you need to render 50 templates for a release, you fire them all off, then collect results. No blocking, no timeouts.
Authentication
You need a Pro tier account to access the render API. Create an API key from your dashboard — it'll look like sa_live_.... Every request uses it as a Bearer token:
Authorization: Bearer sa_live_your_key_here
Templates: How They're Structured
Everything revolves around templates. A template is a canvas you build in the visual editor — you place text blocks, device frames, images, and set backgrounds. Each element on the canvas is an item with a unique UUID.
For automation, the critical concept is swappable fields. Not every property of every item is overridable via the API. An admin configures which fields can be swapped — text content, font family, font size, colors, screenshot URLs, device frame IDs, and so on. This is a deliberate constraint: it keeps API consumers from accidentally breaking the layout by changing positions or sizes that the designer locked in.
Think of it as a contract between your designer and your pipeline: the designer owns the layout, the pipeline owns the content.
The YAML Override System
This is the core of the API. Instead of rendering a template exactly as saved, you POST YAML that overrides specific fields on specific items.
Step 1: Get the YAML Scaffold
Every template has a scaffold endpoint that returns all swappable fields as commented YAML:
curl -H "Authorization: Bearer sa_live_your_key_here" \
https://api.screenshots.live/templates/YOUR_TEMPLATE_ID/yaml
You'll get something like:
templateId: "550e8400-e29b-41d4-a716-446655440000"
items:
- itemId: "7c9e6679-7425-40de-944b-e07fc1f90ae7"
type: Text
# text: "Your headline here"
# fontFamily: "Inter"
# fontSize: 48
# color: "#FFFFFF"
- itemId: "a1b2c3d4-5678-90ab-cdef-1234567890ab"
type: DeviceFrame
# screenshotUrl: "https://..."
# frameId: "..."
Uncomment what you want to override. Leave the rest alone — those fields keep their template defaults.
Step 2: Submit the Render
curl -X POST https://api.screenshots.live/render/api \
-H "Authorization: Bearer sa_live_your_key_here" \
-H "Content-Type: text/yaml" \
-d '
templateId: "550e8400-e29b-41d4-a716-446655440000"
items:
- itemId: "7c9e6679-7425-40de-944b-e07fc1f90ae7"
type: Text
text: "Track your deliveries"
color: "#1A73E8"
- itemId: "a1b2c3d4-5678-90ab-cdef-1234567890ab"
type: DeviceFrame
screenshotUrl: "https://your-cdn.com/screenshots/home-screen.png"
'
Response:
{
"data": {
"jobId": "d4e5f6a7-8901-2345-6789-0abcdef12345",
"templateId": "550e8400-e29b-41d4-a716-446655440000",
"status": "Pending",
"createdAt": "2026-03-05T10:30:00.000Z"
}
}
Step 3: Poll for Status
curl -H "Authorization: Bearer sa_live_your_key_here" \
https://api.screenshots.live/render/api/JOB_ID
The status progresses through Pending → Active → Completed (or Failed if something went wrong). For most templates, rendering takes a few seconds.
Step 4: Download
curl -H "Authorization: Bearer sa_live_your_key_here" \
https://api.screenshots.live/render/JOB_ID/download
Returns a presigned URL. The download is a ZIP containing your rendered images.
Uploading Screenshots Directly
You don't need to host your screenshots somewhere just to pass a URL. The /render/api/with-pictures endpoint accepts multipart uploads:
curl -X POST https://api.screenshots.live/render/api/with-pictures \
-H "Authorization: Bearer sa_live_your_key_here" \
-F 'yaml=templateId: "YOUR_TEMPLATE_ID"
items:
- itemId: "DEVICE_FRAME_ITEM_ID"
type: DeviceFrame
screenshotUrl: "picture://home-screen.png"' \
-F 'pictures=@./screenshots/home-screen.png'
The picture:// protocol references uploaded files by filename. You can attach up to 200 files, each up to 10MB. This is what makes the API practical for CI/CD — your pipeline captures screenshots, and you send them straight to the render without needing intermediate storage.
Real-World Example: Whitelabel Pipeline
Here's a concrete scenario. You maintain 5 whitelabel brands, each needing App Store screenshots in 3 languages across 5 key screens. That's 75 rendered images per release.
Your designer has created one template per screen in the editor, with the headline text and device frame marked as swappable. Your CI pipeline (GitLab, GitHub Actions, whatever) captures raw screenshots per brand and locale using Fastlane's snapshot/screengrab.
Here's the script that ties it together:
#!/bin/bash
API_KEY="sa_live_your_key_here"
API_BASE="https://api.screenshots.live"
BRANDS=("acme" "globex" "initech" "umbrella" "stark")
LOCALES=("en" "de" "es")
SCREENS=("home" "profile" "search" "settings" "checkout")
# Template IDs from the editor
declare -A TEMPLATES
TEMPLATES[home]="aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"
TEMPLATES[profile]="aaaaaaaa-bbbb-cccc-dddd-ffffffffffff"
# ... etc
# Localized headlines (loaded from your i18n files)
declare -A HEADLINES
HEADLINES[home:en]="Track your deliveries"
HEADLINES[home:de]="Verfolge deine Lieferungen"
HEADLINES[home:es]="Rastrea tus entregas"
# ... etc
JOB_IDS=()
# Submit all render jobs
for brand in "${BRANDS[@]}"; do
for locale in "${LOCALES[@]}"; do
for screen in "${SCREENS[@]}"; do
HEADLINE="${HEADLINES[$screen:$locale]}"
BRAND_COLOR=$(cat "brands/$brand/primary-color.txt")
RESPONSE=$(curl -s -X POST "$API_BASE/render/api/with-pictures" \
-H "Authorization: Bearer $API_KEY" \
-F "yaml=templateId: \"${TEMPLATES[$screen]}\"
items:
- itemId: \"TEXT_ITEM_UUID\"
type: Text
text: \"$HEADLINE\"
color: \"$BRAND_COLOR\"
- itemId: \"FRAME_ITEM_UUID\"
type: DeviceFrame
screenshotUrl: \"picture://${screen}.png\"" \
-F "pictures=@./artifacts/$brand/$locale/${screen}.png")
JOB_ID=$(echo "$RESPONSE" | jq -r '.data.jobId')
JOB_IDS+=("$JOB_ID|$brand|$locale|$screen")
echo "Dispatched: $brand/$locale/$screen → $JOB_ID"
done
done
done
echo "Submitted ${#JOB_IDS[@]} jobs. Polling for results..."
# Poll and download results
for entry in "${JOB_IDS[@]}"; do
IFS='|' read -r job_id brand locale screen <<< "$entry"
while true; do
STATUS=$(curl -s -H "Authorization: Bearer $API_KEY" \
"$API_BASE/render/api/$job_id" | jq -r '.status')
if [ "$STATUS" = "Completed" ]; then
URL=$(curl -s -H "Authorization: Bearer $API_KEY" \
"$API_BASE/render/$job_id/download" | jq -r '.downloadUrl')
mkdir -p "./output/$brand/$locale"
curl -s -o "./output/$brand/$locale/${screen}.zip" "$URL"
echo "Done: $brand/$locale/$screen"
break
elif [ "$STATUS" = "Failed" ]; then
echo "FAILED: $brand/$locale/$screen"
break
fi
sleep 2
done
done
75 render jobs, all dispatched in parallel, then polled and downloaded. In a real pipeline you'd add retry logic and maybe parallelize the polling, but this shows the pattern. Plug this into your GitLab CI .gitlab-ci.yml or GitHub Actions workflow and the whole flow — from code push to store-ready screenshots — runs without human intervention.
Swapping Device Frames
Device frames are the mockup overlays — iPhone 16 Pro, Pixel 9, iPad Air, Galaxy S24. They're preloaded in your account and referenced by UUID. The same template can render with different device frames by overriding the frameId:
items:
- itemId: "FRAME_ITEM_UUID"
type: DeviceFrame
screenshotUrl: "picture://home.png"
frameId: "IPHONE_16_PRO_FRAME_UUID"
This is how you handle the App Store vs Google Play split. Same template layout, same screenshot, different frame. Submit two render jobs — one with the iPhone frame, one with the Pixel frame — and you've covered both stores from one template.
Rate Limits and Quotas
Some numbers to plan around:
- Render API: 5 requests per 60 seconds
- Status polling: 60 requests per 60 seconds
- Daily renders: 100 per day on Pro tier
- Picture uploads: max 200 files per request, 10MB each
For big whitelabel setups, the rate limit on render submissions is the bottleneck. Pace your requests with a small delay between batches. The async design helps — submissions are fast, the actual rendering happens in the background.
YAML Validation
The YAML parser is deliberately strict: no anchors, no aliases, no custom tags, max 1MB. If something's wrong, the error message tells you exactly what — wrong field name, non-swappable field, invalid UUID format, URL pointing to a private IP range. It's designed to fail fast and fail clearly, which matters when you're debugging a CI pipeline at 11 PM.
What About the OpenAPI Spec?
If you prefer generating a typed client instead of writing curl commands, the full OpenAPI spec is available at:
https://api.screenshots.live/render/openapi.json
And there's an interactive Swagger UI at:
https://api.screenshots.live/render/docs
Both require Pro tier authentication. The spec covers all render endpoints, DTOs, and error responses — feed it into openapi-generator or orval and you'll have a typed client in TypeScript, Python, Go, or whatever you're working with.
Wrapping Up
The render API is built for one specific workflow: your designer creates templates in the visual editor, and your code fills them with the right content at build time. For whitelabel setups where the same template needs to work across dozens of brands and languages, the YAML override system means you maintain one template per screen layout instead of one per brand-language-device combination.
If you've got the raw screenshots covered (Fastlane, Maestro, manual captures — doesn't matter) and you need the processing step automated, this is what Screenshots Live is for. The pipeline captures screenshots, the API renders them into store-ready images, and Fastlane uploads them. No designer in the loop for routine releases.