Templates & Rendering
Render API Reference
The RenderStack Render API lets you generate images and PDFs programmatically from your templates. All endpoints live under the `/api/v1` base path.
Base URL#
https://renderstack.io/api/v1
Authentication#
All POST render requests require an API key in the Authorization header:
Authorization: Bearer YOUR_API_KEY
The GET render endpoint uses a query parameter instead — see GET Render below.
Create and manage API keys from the API Keys page in your dashboard. See API Keys Overview for the full breakdown of key types and security guidance.
Error Format#
All error responses use this structure:
{
"type": "https://renderstack.io/errors/error-type",
"title": "Error Title",
"status": 400,
"detail": "A human-readable description of the error"
}
| Status | Type | Description |
|---|---|---|
| 400 | validation-error | Invalid or missing request parameters |
| 401 | authentication-error | Invalid, missing, or revoked API key |
| 403 | forbidden | Key type not permitted for this endpoint, or request origin not in Allowed Hosts |
| 404 | not-found | Template not found |
| 414 | uri-too-long | URL exceeds 8,192 characters (GET endpoint only) |
| 429 | rate-limited | Rate limit exceeded |
| 500 | server-error | Internal rendering error |
Endpoints#
Render Image — Binary#
Renders a template and streams the result back as raw binary data. This is the most common endpoint for server-side integrations — save the response body directly to a file or upload it to storage.
POST /api/v1/renders/sync
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
template | string | Yes | Template ID or slug |
layers | object | No | Dynamic layer overrides (see Layer Overrides) |
format | string | No | png, jpeg, or pdf (default: png) |
quality | number | No | JPEG quality 1–100 (default: 90, JPEG only) |
width | number | No | Override canvas width in pixels |
height | number | No | Override canvas height in pixels |
useAws | boolean | No | Upload rendered output to your configured AWS S3 bucket |
filename | string | No | Custom S3 object filename — ignored if useAws is not true |
Example:
curl -X POST https://renderstack.io/api/v1/renders/sync \
-H "Authorization: Bearer rs_live_..." \
-H "Content-Type: application/json" \
-d '{
"template": "social-card",
"layers": {
"title": { "text": "Welcome to Our Event" },
"subtitle": { "text": "Join us for an amazing experience" },
"background_image": { "src": "https://example.com/bg.jpg" },
"event_date": { "text": "March 15, 2026" }
},
"format": "png"
}' \
--output social-card.png
Response: 200 with Content-Type: image/png, image/jpeg, or application/pdf. Body is raw binary.
Response headers:
| Header | Description |
|---|---|
X-Render-Id | Unique render record ID |
X-Render-Duration-Ms | Time taken to render in milliseconds |
X-Render-Warnings | JSON array of non-fatal warnings, if any |
X-Render-S3-Url | S3 public URL (only when useAws: true and upload succeeds) |
X-Render-S3-Key | S3 object key (only when useAws: true) |
Render Image — JSON#
Renders a template and returns the result as a base64-encoded JSON response. Useful when you need render metadata alongside the image data, or when you want to process the image in-memory without writing to disk.
POST /api/v1/renders/json
Request body: Identical to the binary endpoint above.
Example:
curl -X POST https://renderstack.io/api/v1/renders/json \
-H "Authorization: Bearer rs_live_..." \
-H "Content-Type: application/json" \
-d '{"template":"social-card","layers":{"title":{"text":"Hello World"}}}'
Success response:
{
"id": "render_abc123",
"status": "completed",
"template": "template_id_here",
"template_version": 3,
"created_at": "2026-02-01T14:22:00.000Z",
"completed_at": "2026-02-01T14:22:00.234Z",
"duration_ms": 234,
"output": {
"data": "data:image/png;base64,iVBORw0KGgo...",
"mime_type": "image/png",
"size_bytes": 45320,
"width": 1200,
"height": 630
},
"warnings": []
}
Response fields:
| Field | Type | Description |
|---|---|---|
id | string | Unique render record ID |
status | string | completed |
template | string | Template ID |
template_version | number | Template version at render time |
created_at | string | ISO 8601 timestamp when render was queued |
completed_at | string | ISO 8601 timestamp when render finished |
duration_ms | number | Render duration in milliseconds |
output.data | string | Base64 data URI (e.g., data:image/png;base64,...) |
output.mime_type | string | image/png, image/jpeg, or application/pdf |
output.size_bytes | number | Output file size in bytes |
output.width | number | Rendered width in pixels |
output.height | number | Rendered height in pixels |
output.page_count | number | Number of pages (PDF only) |
warnings | array | Non-fatal warning objects (see Warnings) |
aws_s3.url | string | S3 public URL (only when useAws: true and upload succeeds) |
aws_s3.key | string | S3 object key |
aws_s3.bucket | string | S3 bucket name |
Render Image — GET (Dynamic Image URL)#
Renders a template via URL query parameters and returns the result as binary image data. Designed for embedding rendered images directly in HTML, email, or any platform that renders <img> tags. Requires a GET-type API key (rs_get_...) and at least one Allowed Host configured in Settings.
GET /api/v1/render?apiKey=YOUR_GET_KEY&template=TEMPLATE_SLUG
The API key is passed as a query parameter — not in the Authorization header.
Query parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
apiKey | string | Yes | GET API key (starts with rs_get_) |
template | string | Yes | Template ID or slug |
format | string | No | png or jpeg only — PDF not supported (default: png) |
quality | number | No | JPEG quality 1–100 (default: 90) |
width | number | No | Override canvas width in pixels |
height | number | No | Override canvas height in pixels |
{layer}.{property} | string | No | Layer overrides in dot notation (e.g., title.text=Hello) |
Layer overrides via dot notation:
Pass layer overrides as layerName.property=value query parameters. Encode spaces as + or %20, and # color values as %23:
?title.text=Hello+World&title.fill=%23ff0000&logo.src=https://example.com/logo.png
Example:
<img src="https://renderstack.io/api/v1/render?apiKey=rs_get_abc123&template=social-card&title.text=Hello+World&subtitle.text=Dynamic+Images" />
Response: 200 with Content-Type: image/png or image/jpeg. Body is raw binary image data.
Response headers:
| Header | Description |
|---|---|
X-Cache | HIT (served from cache) or MISS (freshly rendered) |
X-Render-Id | Render record ID (only on cache MISS) |
ETag | Cache key hash for browser-level caching |
Cache-Control | public, max-age=604800, immutable |
Limits:
- Maximum URL length: 8,192 characters
- Maximum canvas dimensions: 8,192 × 8,192 pixels
- PDF format not supported on this endpoint
Rate limits:
| Scope | Limit |
|---|---|
| Per API key | 60 requests/minute |
| Per IP address | 30 requests/minute |
| Cache hits | Count as 0.25× toward limits |
List Templates#
Returns all templates accessible by the authenticated key.
GET /api/v1/templates
Example:
curl https://renderstack.io/api/v1/templates \
-H "Authorization: Bearer rs_live_..."
Response:
{
"data": [
{
"id": "template_abc123",
"name": "Social Card",
"slug": "social-card",
"width": 1200,
"height": 630,
"version": 3,
"is_published": true,
"created_at": "2026-01-15T10:30:00Z",
"updated_at": "2026-02-01T14:22:00Z"
}
],
"total": 1
}
Get Template#
Retrieves a single template by ID, including full canvas data and the layer schema.
GET /api/v1/templates/:id
Response fields:
| Field | Type | Description |
|---|---|---|
id | string | Unique template ID |
name | string | Display name |
slug | string | URL-friendly identifier used in render calls |
width | number | Canvas width in pixels |
height | number | Canvas height in pixels |
version | number | Current template version number |
is_published | boolean | Whether the template is published |
layer_schema | object | Schema of dynamic layers and their overridable properties |
canvas_data | object | Full element data for all canvas layers |
created_at | string | ISO 8601 creation timestamp |
updated_at | string | ISO 8601 last-modified timestamp |
Get Render Record#
Retrieves metadata for a completed render by ID.
GET /api/v1/renders/:id
Example:
curl https://renderstack.io/api/v1/renders/render_abc123 \
-H "Authorization: Bearer rs_live_..."
Layer Overrides#
The layers object in a render request maps each layer's API Name to the properties you want to override. The API Name is the snake_case identifier set in the template editor (e.g., a layer named "Speaker Name" becomes speaker_name). Only layers marked as Dynamic in the editor can be overridden.
Auto-hide: Layers with Auto visibility mode are completely hidden — no placeholder, no empty box — when you omit them from
layersor pass an empty value. Use this to conditionally show or hide elements at render time without modifying the template.
Text layers#
{
"headline": {
"text": "Custom text content",
"fill": "#ff0000",
"fontSize": 48,
"fontFamily": "Arial",
"fontWeight": "bold",
"textAlign": "center"
}
}
| Property | Type | Description |
|---|---|---|
text | string | Text content to display |
fill | string | Text color (hex, rgb, or named color) |
fontSize | number | Font size in pixels |
fontFamily | string | Font family name |
fontWeight | string | normal or bold |
fontStyle | string | normal or italic |
textAlign | string | left, center, or right |
boxFill | string | Background fill color for the text element bounding box |
boxCornerRadius | number | Corner radius of the text box |
boxPaddingTop / boxPaddingRight / boxPaddingBottom / boxPaddingLeft | number | Padding between box edge and text content |
For per-character styling (mixed fonts, bold, italic, color, superscript within a single element), see Rich Text & Inline Styling.
Image layers#
{
"profile_photo": {
"src": "https://example.com/photo.jpg",
"fillMode": "cover"
}
}
| Property | Type | Description |
|---|---|---|
src | string | URL of the image to display |
fillMode | string | cover, contain, stretch, or original |
For AI-powered face/subject-aware cropping, see Smart Cover.
QR Code layers#
{
"ticket_qr": {
"value": "https://example.com/ticket/12345",
"darkColor": "#1a1a2e",
"lightColor": "#ffffff",
"errorCorrectionLevel": "H"
}
}
| Property | Type | Description |
|---|---|---|
value | string | Data to encode — URL, plain text, vCard, etc. |
darkColor | string | QR module color (default: #000000) |
lightColor | string | Background color, or transparent (default: #ffffff) |
errorCorrectionLevel | string | L, M (default), Q, or H |
margin | number | Quiet zone in modules, 0–10 (default: 2) |
Shape layers#
{
"background_box": {
"fill": "#3b82f6",
"stroke": "#1d4ed8",
"strokeWidth": 2,
"cornerRadius": 12
}
}
| Property | Type | Description |
|---|---|---|
fill | string | Fill color |
stroke | string | Border color |
strokeWidth | number | Border thickness in pixels |
cornerRadius | number | Corner radius (rectangles only) |
Warnings#
Warnings are returned when a render completes but encountered a non-fatal issue. The image is still produced, but some elements may not appear as expected. Check the X-Render-Warnings response header (binary endpoint) or the warnings array (JSON endpoint).
| Code | Description |
|---|---|
IMAGE_LOAD_FAILED | Could not fetch an image from the provided URL |
IMAGE_URL_BLOCKED | Image URL was blocked (internal network, SSRF protection) |
UNKNOWN_LAYER | A key in layers does not match any layer in the template |
MISSING_DYNAMIC_LAYER | A dynamic layer was not provided — the template default was rendered instead |
PRESIGNED_URL_IN_LAYER | Image src is a short-lived pre-signed URL (GCS or AWS). Render succeeded but the URL will expire — replace with a stable, permanent URL |
S3_UPLOAD_FAILED | Failed to upload the rendered file to AWS S3 |
Code Examples#
Node.js#
const response = await fetch('https://renderstack.io/api/v1/renders/sync', {
method: 'POST',
headers: {
'Authorization': 'Bearer rs_live_...',
'Content-Type': 'application/json',
},
body: JSON.stringify({
template: 'social-card',
layers: {
title: { text: 'Dynamic Title' },
subtitle: { text: 'Generated with RenderStack' },
},
}),
});
const imageBuffer = await response.arrayBuffer();
// Stream to disk, upload to S3, or attach to an email
Python#
import requests
response = requests.post(
'https://renderstack.io/api/v1/renders/sync',
headers={
'Authorization': 'Bearer rs_live_...',
'Content-Type': 'application/json',
},
json={
'template': 'social-card',
'layers': {
'title': {'text': 'Dynamic Title'},
'subtitle': {'text': 'Generated with RenderStack'},
},
},
)
with open('output.png', 'wb') as f:
f.write(response.content)
PHP#
$ch = curl_init('https://renderstack.io/api/v1/renders/sync');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer rs_live_...',
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'template' => 'social-card',
'layers' => ['title' => ['text' => 'Dynamic Title']],
]),
]);
$image = curl_exec($ch);
curl_close($ch);
file_put_contents('output.png', $image);
cURL#
curl -X POST https://renderstack.io/api/v1/renders/sync \
-H "Authorization: Bearer rs_live_..." \
-H "Content-Type: application/json" \
-d '{"template":"social-card","layers":{"title":{"text":"Hello"}}}' \
-o output.png
Tip: You can auto-generate a pre-filled
curlcommand from the template editor. Open your template and click the API Payload button (code icon) in the bottom-right corner — it includes your template's slug, canvas dimensions, and all dynamic layer keys already filled in.