Cách Render API của Screenshots Live hoạt động — Hướng dẫn thực tế cho tự động hóa store listing
Walkthrough kỹ thuật về cách Render API của Screenshots Live xử lý template, áp dụng override YAML và tạo ảnh chụp sẵn sàng cho store — với ví dụ code thực tế và pattern CI/CD cho setup whitelabel.
Bài viết này nói về gì
Nếu bạn đang phát hành app whitelabel — hoặc thậm chí chỉ một app với nhiều store listing — bạn chắc chắn đã gặp vấn đề screenshot. Apple muốn ảnh chụp cho mọi kích thước thiết bị. Google Play muốn bộ riêng. Hỗ trợ ba ngôn ngữ là bạn đã gấp ba công việc. Giờ nhân thêm với số lượng biến thể thương hiệu bạn duy trì.
Bài trước đã giải thích tại sao Screenshots Live ra đời. Bài này là phần tiếp theo thực hành: render API được thiết kế như thế nào, nó cần gì, và cách kết nối nó vào build pipeline để screenshot không còn là công việc thủ công.
Pipeline render được thiết kế như thế nào
Hệ thống render không phải là endpoint đồng bộ kiểu "gửi request, nhận ảnh". Nó là pipeline dựa trên job — điều này quan trọng khi bạn render hàng chục hoặc hàng trăm ảnh chụp theo batch.
Luồng xử lý:
- Bạn gửi render request với template ID và YAML override tùy chọn (text, screenshot, device frame — bất cứ thứ gì được đánh dấu là có thể hoán đổi).
- Validation — hệ thống kiểm tra template của bạn tồn tại, thuộc về bạn, và mọi field bạn muốn ghi đè thực sự được phép. URL được kiểm tra với dải IP private và protocol bị hạn chế.
- Hàng đợi — job đi vào hàng đợi BullMQ dựa trên Redis. API call trả về ngay lập tức với
jobIdvà trạng tháiPending. Đây là điểm mấu chốt: bạn không chờ render xong. - Rendering — worker viết bằng Rust nhận job, tải template và ảnh (với LRU cache cho render lặp lại), tải font, render canvas bằng Skia và tạo output.
- Đóng gói — ảnh đã render được nén ZIP và upload lên object storage.
- Download — bạn poll endpoint trạng thái. Khi
Completed, gọi endpoint download để lấy URL có chữ ký (có hiệu lực 1 giờ).
Thiết kế bất đồng bộ là có chủ đích. Khi cần render 50 template cho một release, bạn bắn hết và thu thập kết quả. Không blocking, không timeout.
Xác thực
Bạn cần tài khoản Pro tier để truy cập render API. Tạo API key từ dashboard — nó trông như sa_live_.... Mọi request dùng nó làm Bearer token:
Authorization: Bearer sa_live_your_key_here
Template: Cấu trúc như thế nào
Mọi thứ xoay quanh template. Template là canvas bạn xây dựng trong editor trực quan — bạn đặt khối text, device frame, ảnh và background. Mỗi phần tử trên canvas là một item với UUID riêng.
Cho tự động hóa, khái niệm then chốt là trường có thể hoán đổi (swappable field). Không phải mọi thuộc tính của mọi item đều ghi đè được qua API. Admin cấu hình field nào có thể hoán đổi — nội dung text, font family, cỡ font, màu sắc, URL screenshot, ID device frame, v.v. Đây là ràng buộc có chủ đích: ngăn người dùng API vô tình phá layout bằng cách thay đổi vị trí hoặc kích thước mà designer đã cố định.
Hãy nghĩ nó như hợp đồng giữa designer và pipeline: designer sở hữu layout, pipeline sở hữu nội dung.
Hệ thống YAML override
Đây là phần thú vị cho tự động hóa. Thay vì render template đúng y như đã lưu, bạn POST YAML ghi đè field cụ thể trên item cụ thể.
Bước 1: Lấy YAML scaffold
Mỗi template có endpoint scaffold trả về tất cả field hoán đổi được dưới dạng YAML có comment:
curl -H "Authorization: Bearer sa_live_your_key_here" \
https://api.screenshots.live/templates/TEMPLATE_ID_CUA_BAN/yaml
Bạn sẽ nhận được kiểu:
templateId: "550e8400-e29b-41d4-a716-446655440000"
items:
- itemId: "7c9e6679-7425-40de-944b-e07fc1f90ae7"
type: Text
# text: "Tiêu đề của bạn"
# fontFamily: "Inter"
# fontSize: 48
# color: "#FFFFFF"
- itemId: "a1b2c3d4-5678-90ab-cdef-1234567890ab"
type: DeviceFrame
# screenshotUrl: "https://..."
# frameId: "..."
Bỏ comment những gì bạn muốn ghi đè. Để nguyên phần còn lại — những field đó giữ giá trị mặc định của template.
Bước 2: Gửi 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: "Theo dõi đơn hàng của bạn"
color: "#1A73E8"
- itemId: "a1b2c3d4-5678-90ab-cdef-1234567890ab"
type: DeviceFrame
screenshotUrl: "https://your-cdn.com/screenshots/home-screen.png"
'
Bước 3: Kiểm tra trạng thái
curl -H "Authorization: Bearer sa_live_your_key_here" \
https://api.screenshots.live/render/api/JOB_ID
Trạng thái tiến triển: Pending → Active → Completed (hoặc Failed). Hầu hết template render trong vài giây.
Bước 4: Tải về
curl -H "Authorization: Bearer sa_live_your_key_here" \
https://api.screenshots.live/render/JOB_ID/download
Trả về URL có chữ ký. Tải về là file ZIP chứa ảnh đã render.
Upload screenshot trực tiếp
Bạn không cần host screenshot ở đâu đó chỉ để truyền URL. Endpoint /render/api/with-pictures chấp nhận multipart upload:
curl -X POST https://api.screenshots.live/render/api/with-pictures \
-H "Authorization: Bearer sa_live_your_key_here" \
-F 'yaml=templateId: "TEMPLATE_ID_CUA_BAN"
items:
- itemId: "DEVICE_FRAME_ITEM_ID"
type: DeviceFrame
screenshotUrl: "picture://home-screen.png"' \
-F 'pictures=@./screenshots/home-screen.png'
Giao thức picture:// tham chiếu file đã upload theo tên. Bạn có thể đính kèm tối đa 200 file, mỗi file 10MB. Đây là thứ làm API thực tế cho CI/CD — pipeline capture screenshot và bạn gửi thẳng đến render mà không cần storage trung gian.
Ví dụ thực tế: Pipeline whitelabel
Kịch bản cụ thể: bạn duy trì 5 thương hiệu whitelabel, mỗi thương hiệu cần screenshot App Store bằng 3 ngôn ngữ cho 5 màn hình chính. Đó là 75 ảnh render mỗi release.
Designer đã tạo một template cho mỗi màn hình trong editor, với headline text và device frame đánh dấu là hoán đổi được. Pipeline CI (GitLab, GitHub Actions, bất kỳ) capture screenshot thô theo thương hiệu và ngôn ngữ bằng snapshot/screengrab của Fastlane.
Script kết nối tất cả:
#!/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")
declare -A TEMPLATES
TEMPLATES[home]="aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"
TEMPLATES[profile]="aaaaaaaa-bbbb-cccc-dddd-ffffffffffff"
declare -A HEADLINES
HEADLINES[home:en]="Track your deliveries"
HEADLINES[home:de]="Verfolge deine Lieferungen"
HEADLINES[home:es]="Rastrea tus entregas"
JOB_IDS=()
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")
done
done
done
echo "Đã gửi ${#JOB_IDS[@]} job. Đang chờ kết quả..."
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 "Xong: $brand/$locale/$screen"
break
elif [ "$STATUS" = "Failed" ]; then
echo "LỖI: $brand/$locale/$screen"
break
fi
sleep 2
done
done
75 render job, tất cả bắn song song, sau đó poll và tải. Trong pipeline thực tế bạn sẽ thêm retry logic và polling song song, nhưng pattern đã rõ. Nhét vào .gitlab-ci.yml hoặc GitHub Actions và toàn bộ luồng — từ push code đến screenshot sẵn sàng cho store — chạy mà không cần con người can thiệp.
Đổi device frame
Device frame là overlay mockup — iPhone 16 Pro, Pixel 9, iPad Air, Galaxy S24. Chúng được tải sẵn trong tài khoản và tham chiếu bằng UUID. Cùng một template có thể render với frame khác nhau:
items:
- itemId: "FRAME_ITEM_UUID"
type: DeviceFrame
screenshotUrl: "picture://home.png"
frameId: "IPHONE_16_PRO_FRAME_UUID"
Đây là cách giải quyết App Store vs Google Play: cùng layout template, cùng screenshot, frame khác. Hai render job — một với frame iPhone, một với Pixel — và bạn đã cover cả hai store bằng một template.
Rate limit và quota
Số liệu để lập kế hoạch:
- Render API: 5 request mỗi 60 giây
- Polling trạng thái: 60 request mỗi 60 giây
- Render hàng ngày: 100/ngày với Pro tier
- Upload ảnh: tối đa 200 file mỗi request, 10MB mỗi file
Với setup whitelabel lớn, rate limit trên render submission là nút cổ chai. Thêm khoảng nghỉ nhỏ giữa các batch. Thiết kế async giúp ích — submission nhanh, render thực tế diễn ra ở background.
Validation YAML
Parser YAML cố tình nghiêm ngặt: không anchor, không alias, không custom tag, tối đa 1MB. Nếu sai, thông báo lỗi nói chính xác — tên field sai, field không hoán đổi được, format UUID không hợp lệ, URL trỏ đến dải IP private. Thiết kế để fail nhanh và rõ ràng — điều này quan trọng khi bạn debug pipeline CI lúc 11 giờ đêm.
Còn OpenAPI spec thì sao?
Nếu bạn muốn generate typed client thay vì viết lệnh curl, OpenAPI spec đầy đủ có tại:
https://api.screenshots.live/render/openapi.json
Và có Swagger UI tương tác tại:
https://api.screenshots.live/render/docs
Cả hai cần xác thực Pro tier. Spec cover tất cả render endpoint, DTO và error response — đưa vào openapi-generator hoặc orval và bạn có typed client bằng TypeScript, Python, Go hoặc bất kỳ ngôn ngữ nào.
Kết luận
Render API được xây dựng cho một workflow cụ thể: designer tạo template trong editor trực quan, và code của bạn điền nội dung đúng vào lúc build. Với setup whitelabel nơi cùng template phải hoạt động cho hàng chục thương hiệu và ngôn ngữ, hệ thống YAML override nghĩa là bạn duy trì một template cho mỗi layout màn hình thay vì một cho mỗi tổ hợp thương hiệu-ngôn ngữ-thiết bị.
Nếu bạn đã có screenshot thô (Fastlane, Maestro, chụp thủ công — không quan trọng) và cần tự động hóa bước xử lý, Screenshots Live là cho việc đó. Pipeline capture screenshot, API render chúng thành ảnh sẵn sàng cho store, và Fastlane upload. Không cần designer trong loop cho release thường xuyên.