API Keys & Integration
Template Integration Guide (for Developers)
This document is a technical reference for engineers building applications that generate images or PDFs dynamically using RenderStack templates. It covers how templates are structured, how to discover
Table of Contents#
- Core Concepts
- Discovering a Template's Fields
- Render API
- The
layersObject - Element Types and Overridable Properties
- Auto-Visibility Behavior
- Pinning and Anchored Layouts
- Output Format and Quality
- Response Headers and Warnings
- Plan Feature Gates
- Common Patterns
Core Concepts#
A template is a pixel-precise canvas design. It contains one or more layers (elements). Some layers are static — they always render exactly as designed. Others are marked dynamic — their content can be overridden at render time by your application.
Each dynamic layer has an API Name (a stable, snake_case identifier like event_title or speaker_photo). When calling the render API, you supply a layers object keyed by these API Names. The template designer controls which properties of each layer are dynamic.
The render engine resolves all layout — including pinning, anchoring, and repeater expansion — server-side. Your application only needs to supply the data; you never need to compute positions.
Discovering a Template's Fields#
Before building an integration, fetch the template's metadata to see what fields are available and what types they expect.
List all templates#
GET /api/v1/templates
Authorization: Bearer rs_live_YOUR_KEY
Get a specific template#
GET /api/v1/templates/{id_or_slug}
Authorization: Bearer rs_live_YOUR_KEY
Both endpoints return a layer_schema object that describes every dynamic field:
{
"id": "tmpl_abc123",
"name": "Event Badge",
"slug": "event-badge",
"width": 800,
"height": 500,
"output_format": "png",
"layer_schema": {
"attendee_name": { "type": "text" },
"job_title": { "type": "text" },
"company": { "type": "text" },
"headshot": { "type": "image" },
"qr_code": { "type": "qrcode" },
"line_items": {
"type": "repeater",
"items": {
"item_name": { "type": "text" },
"item_price": { "type": "text" },
"item_image": { "type": "image" }
}
}
}
}
The keys in layer_schema are the exact strings you use as keys in your layers payload. The type tells you what kind of override value to send (see Element Types).
Render API#
Authentication#
| Key Type | Prefix | Used For |
|---|---|---|
| Live key | rs_live_ | POST renders (server-to-server) |
| Master key | rs_master_ | POST renders with elevated access |
| GET key | rs_get_ | Embeddable GET URLs only |
| Read-only key | rs_ro_ | Fetching template metadata only |
For POST requests, pass the key as a Bearer token:
Authorization: Bearer rs_live_YOUR_KEY
For GET render URLs, pass the key as a query parameter (apiKey=rs_get_YOUR_KEY). GET keys are additionally restricted to a list of Allowed Hosts configured in the dashboard — only requests originating from those domains will be served.
POST — Synchronous Render#
Returns the rendered image or PDF binary directly in the response body.
POST /api/v1/renders/sync
Authorization: Bearer rs_live_YOUR_KEY
Content-Type: application/json
Request body:
{
"template": "event-badge",
"layers": {
"attendee_name": { "text": "Sarah Chen" },
"job_title": { "text": "Senior Engineer" },
"company": { "text": "Acme Corp" },
"headshot": { "src": "https://cdn.example.com/sarah.jpg" },
"qr_code": { "value": "https://checkin.example.com/ATT-4829" }
},
"format": "png",
"quality": 90
}
| Field | Type | Required | Description |
|---|---|---|---|
template | string | Yes | Template ID or slug |
layers | object | No | Layer overrides keyed by API Name |
format | string | No | "png", "jpeg", or "pdf". Defaults to template setting. |
quality | integer | No | 1–100. Defaults to template setting (usually 90). |
width | integer | No | Override the template's width in pixels |
height | integer | No | Override the template's height in pixels |
useAws | boolean | No | If true, uploads the result to your connected S3 bucket |
filename | string | No | Filename hint used when uploading to S3 |
sizes | array | No | PDF only. Array of { width, height } objects for multi-page output |
Response: Binary image/PDF with Content-Type: image/png, image/jpeg, or application/pdf.
POST — JSON Response Render#
Returns a JSON body with a URL to the rendered result instead of raw binary.
POST /api/v1/renders/json
Authorization: Bearer rs_live_YOUR_KEY
Content-Type: application/json
Request body is identical to /renders/sync. Response:
{
"renderId": "rnd_xyz",
"url": "https://cdn.renderstack.io/renders/rnd_xyz.png",
"status": "completed",
"durationMs": 143
}
GET — Embeddable URL Render#
Renders an image on demand from a URL. Designed for use in <img> tags, email Open Graph tags, or any context where a URL is required rather than a programmatic call.
GET /api/v1/render?apiKey=rs_get_KEY&template=SLUG&layer.property=value
Reserved query parameters:
| Parameter | Description |
|---|---|
apiKey | Your GET-type API key (rs_get_...) |
template | Template ID or slug |
format | png, jpeg |
quality | 1–100 |
width | Override width |
height | Override height |
Layer overrides use dot notation: {apiName}.{property}=value. For example:
?template=event-badge
&attendee_name.text=Sarah+Chen
&job_title.text=Senior+Engineer
&headshot.src=https%3A%2F%2Fcdn.example.com%2Fsarah.jpg
&qr_code.value=https%3A%2F%2Fcheckin.example.com%2FATT-4829
GET responses are cached aggressively using ETag and Cache-Control headers. Identical parameter combinations will serve from cache. If your data changes frequently, append a cache-busting parameter (e.g., a version or timestamp not used by the render engine).
The layers Object#
The layers object maps each dynamic layer's API Name to an object of property overrides. You only need to include layers you want to change — omitted layers render with their template defaults.
{
"layers": {
"api_name_of_layer": {
"property_to_override": "new_value"
}
}
}
If you supply a layer name that does not exist in the template, the render still succeeds and a warning is returned in the X-Render-Warnings response header (see Response Headers).
Element Types and Overridable Properties#
Text Elements#
layer_schema type: "text"
Override any combination of these properties:
| Property | Type | Description |
|---|---|---|
text | string | Plain text content |
richText | string | HTML-like rich text (takes priority over text) |
textRuns | array | Array of TextRun objects for per-word/character styling (see below) |
fontSize | number | Size in pixels |
fontFamily | string | Font name (must be available in your org's font library or web-safe) |
fontWeight | string | "normal", "bold", "100"–"900" |
fontStyle | string | "normal", "italic" |
fill | string | Text color as hex ("#ffffff") or CSS color |
textAlign | string | "left", "center", "right" |
verticalAlign | string | "top", "middle", "bottom" |
letterSpacing | number | Extra spacing between characters (pixels) |
lineSpacing | number | Extra spacing between lines (pixels) |
textStrategy | string | "normal", "auto-size" (shrinks font to fit), "truncate", "clip" |
backgroundColor | string | Inline highlight color drawn directly behind the text characters |
backgroundPadding | number | Padding around the inline text background |
underline | boolean | Underline the text |
linethrough | boolean | Strikethrough the text |
boxFill | string | Background fill color for the entire text element bounding box (renders behind text like a shape) |
boxFillOpacity | number | Fill opacity 0–1 |
boxStroke | string | Border color for the text element box |
boxStrokeWidth | number | Border thickness value |
boxStrokeWidthUnit | string | Unit for border thickness: "px" (default) or "%" (percentage of element's smallest dimension) |
boxStrokeWidthMin | number | Minimum border thickness in px when using % unit |
boxStrokeWidthMax | number | Maximum border thickness in px when using % unit |
boxStrokeOpacity | number | Border opacity 0–1 |
boxStrokeAlign | string | "inner", "center" (default), "outer" |
boxCornerRadius | number | Corner radius value for the text element box |
boxCornerRadiusUnit | string | Unit for corner radius: "px" (default) or "%" (percentage of element's smallest dimension) |
boxCornerRadiusMin | number | Minimum corner radius in px when using % unit |
boxCornerRadiusMax | number | Maximum corner radius in px when using % unit |
Example:
"event_title": {
"text": "Tech Summit 2026",
"fontSize": 48,
"fill": "#1a1a2e",
"textAlign": "center"
}
Rich Text with textRuns#
For per-word or per-segment styling within a single layer, use textRuns. Each run is a segment with its own style:
"speaker_bio": {
"textRuns": [
{ "text": "Jane Smith", "font": "Arial", "size": 24, "weight": "bold", "color": "#0f172a" },
{ "text": " — Principal Engineer at Acme", "font": "Arial", "size": 18, "color": "#64748b" }
]
}
TextRun fields: text, font, size, weight ("normal" | "bold"), style ("normal" | "italic"), color, underline (boolean), linethrough (boolean).
Image Elements#
layer_schema type: "image"
| Property | Type | Description |
|---|---|---|
src | string | Image URL (HTTPS) or a data URI |
fillMode | string | How the image fills its frame (see below) |
imageAnchor | string | Alignment when image doesn't fill the frame: "top-left", "top-center", "top-right", "center-left", "center-center", "center-right", "bottom-left", "bottom-center", "bottom-right" |
cornerRadius | number | Rounds the image frame corners (pixels) |
stroke | string | Border color |
strokeWidth | number | Border thickness (pixels) |
strokeAlign | string | "inside", "outside", "center" |
Fill modes:
| Mode | Description |
|---|---|
"cover" | Scales image to fill the frame, cropping if needed. Default. |
"contain" | Scales image to fit entirely inside the frame, no cropping. |
"stretch" | Stretches image to exactly match the frame dimensions. |
"original" | Renders image at its native resolution, no scaling. |
"smart-cover" | Like cover, but detects the subject's focal point automatically and crops around it. Best for headshots and product photos. |
Example:
"speaker_photo": {
"src": "https://cdn.example.com/speakers/jane-smith.jpg",
"fillMode": "smart-cover",
"cornerRadius": 50
}
QR Code Elements#
layer_schema type: "qrcode"
Plan requirement: QR code elements require a Pro plan or higher.
| Property | Type | Description |
|---|---|---|
value | string | The data to encode (URL, plain text, vCard, etc.) |
darkColor | string | Foreground color (hex). Default "#000000" |
lightColor | string | Background color (hex). Default "#ffffff" |
errorCorrectionLevel | string | "L" (7%), "M" (15%), "Q" (25%), "H" (30%). Higher = more recoverable if the code is partially obscured. Default "M" |
margin | number | Quiet zone padding around the code |
cornerRadius | number | Rounds the QR code container |
Example:
"check_in_qr": {
"value": "https://events.example.com/check-in?attendee=ATT-4829&event=summit-2026",
"darkColor": "#1a1a2e",
"lightColor": "#ffffff",
"errorCorrectionLevel": "H"
}
Repeater Elements#
layer_schema type: "repeater"
Plan requirement: Repeater elements require a Business or Enterprise plan.
A repeater is a container that clones a row template for each item in a data array. This is the right element for lists of variable length: event schedules, leaderboards, invoice line items, product grids, etc.
The template designer defines the repeater's row template (a fixed set of child layers) and sets the direction, spacing, and maximum items. At render time, you supply an array of objects — one per row.
How to send data:
The key in layers is the repeater's API Name. The value must be an array of objects. Each object's keys map to the API Names of the child layers inside one row:
"layers": {
"schedule_items": [
{
"session_title": { "text": "Opening Keynote" },
"session_time": { "text": "9:00 AM – 10:00 AM" },
"speaker_name": { "text": "Jane Smith" },
"session_image": { "src": "https://cdn.example.com/keynote.jpg" }
},
{
"session_title": { "text": "Deep Dive: AI in Production" },
"session_time": { "text": "10:15 AM – 11:15 AM" },
"speaker_name": { "text": "Marcus Reyes" },
"session_image": { "src": "https://cdn.example.com/ai-session.jpg" }
},
{
"session_title": { "text": "Closing Panel" },
"session_time": { "text": "4:00 PM – 5:00 PM" },
"speaker_name": { "text": "All Speakers" },
"session_image": { "src": "https://cdn.example.com/panel.jpg" }
}
]
}
Child layer overrides follow the same property structure as their respective element types (text, image, etc.).
Repeater layout properties (these are set by the template designer and applied automatically, but can be overridden at render time):
| Property | Type | Description |
|---|---|---|
direction | string | "vertical" (rows stacked), "horizontal" (side by side), "grid" (multi-column) |
rowGap | number | Vertical space between rows (pixels) |
columnGap | number | Horizontal space between columns in grid mode (pixels) |
columns | number | Number of columns in grid mode |
maxItems | number | Cap the number of rendered rows (items beyond this are silently dropped) |
What happens if you send more items than maxItems? Items beyond the cap are silently dropped. No error is returned.
What happens if the array is empty? The repeater renders nothing (zero height/width contribution) unless the template designer enabled the fallbackEmptyVisible option, in which case the row template renders once with all default values.
Shape Elements#
layer_schema types: "rect", "ellipse", "line"
Shapes are usually static design elements (backgrounds, dividers, badges), but when marked dynamic they support:
| Property | Type | Description |
|---|---|---|
fill | string | Fill color (hex or CSS) |
fillOpacity | number | Fill opacity 0–1 |
stroke | string | Border/line color |
strokeWidth | number | Border thickness (pixels) |
strokeOpacity | number | Border opacity 0–1 |
strokeAlign | string | "inside", "outside", "center" |
cornerRadius | number | Corner rounding, rect only (pixels) |
Example — dynamically color-code an event track badge:
"track_color_bar": {
"fill": "#3b82f6"
}
Auto-Visibility Behavior#
Layers configured with auto-visibility automatically hide themselves during rendering if their required content is absent. This prevents broken placeholders from appearing in your output.
Auto-hide rules by element type:
| Type | Hides when… |
|---|---|
| Any dynamic layer | No data is provided for it in layers at all |
image | src is missing or empty |
text | text, richText, and textRuns are all missing or empty |
qrcode | value is missing or empty |
Practical use: If your events application sometimes has a speaker photo and sometimes does not, mark the headshot image layer as auto-visible. When you don't supply a src, the image frame won't appear in the rendered output. You don't need to send a visibility toggle — omitting the field is enough.
To explicitly force a layer to be visible or hidden regardless of content:
"optional_badge": {
"visible": true
}
or
"optional_badge": {
"visible": false
}
Pinning and Anchored Layouts#
Many templates use pinning to anchor elements relative to other elements. For example, a subtitle might be pinned to always appear 12px below the title, or a footer might be pinned to the bottom edge of the canvas.
You do not need to do anything for pinning to work. The render engine resolves all pinned positions server-side before drawing. If you change a text layer's content and the text expands (e.g., from auto-size strategy), any elements pinned to it reposition automatically.
This is especially important for templates that have variable-length text — elements anchored below a text block will shift down to accommodate however much space the text actually needs.
Output Format and Quality#
Set format and quality per-request, or leave them unset to use the template's configured defaults.
| Format | Content-Type | Notes |
|---|---|---|
"png" | image/png | Lossless. Best for text-heavy images and transparency. |
"jpeg" | image/jpeg | Lossy, smaller files. quality (1–100) controls compression. |
"pdf" | application/pdf | Requires Pro plan. Use sizes array for multi-page output. |
Multi-page PDF example:
{
"template": "report-cover",
"layers": { ... },
"format": "pdf",
"sizes": [
{ "width": 1000, "height": 1414 },
{ "width": 1000, "height": 1414 }
]
}
Each entry in sizes corresponds to one page. The same template and layers data is used for all pages; page-specific content is typically driven by the template's design using separate element groups per page.
Response Headers and Warnings#
Every render response includes these headers:
| Header | Description |
|---|---|
X-Render-Id | Unique identifier for this render (useful for support/debugging) |
X-Render-Warnings | JSON array of non-fatal issues (unknown layers, type mismatches, etc.) |
X-Cache | HIT or MISS (GET renders only) |
Warning object shape:
[
{
"element": "speaker_name",
"code": "UNKNOWN_LAYER",
"message": "Layer 'speaker_name' does not exist in template"
},
{
"element": "schedule_items",
"code": "REPEATER_NOT_ARRAY",
"message": "Layer 'schedule_items' is a repeater but provided value is not an array"
}
]
Common warning codes:
| Code | Meaning |
|---|---|
UNKNOWN_LAYER | A layer key doesn't match any element's API Name in the template |
REPEATER_NOT_ARRAY | A repeater layer value must be an array |
PRESIGNED_URL_IN_LAYER | An image src in layers is a short-lived pre-signed URL (GCS/AWS) that will expire — replace with a stable URL |
MISSING_DYNAMIC_LAYER | A dynamic element was omitted from layers; its template default value was rendered |
Always log or monitor X-Render-Warnings during development. A typo in an API Name will silently produce output with default values rather than an error — the warning header is how you catch it.
Pre-signed URLs: If your automation pipeline fetches asset URLs by following a redirect (e.g., from a
/objects/path), you may receive a time-limited GCS or AWS pre-signed URL. Do not pass these directly assrcvalues in your layers — they expire in minutes and will break your render history. Use a stable CDN or object-storage URL instead.
Plan Feature Gates#
Some element types require a specific plan. If your render request triggers a feature your organization's plan does not include, the API returns a 403 with a structured error:
{
"error": "feature_not_available",
"feature": "qr_codes",
"message": "This template contains QR code layers which are not available on your current plan.",
"upgrade_hint": "Available on Pro, Business, and Enterprise plans."
}
| Feature | Minimum Plan |
|---|---|
| QR code elements | Pro |
| Repeater elements | Business |
| PDF output | Pro |
Common Patterns#
Events application — generating attendee badges#
Fetch the template schema once at startup, then call the render API per attendee:
const response = await fetch("https://your-renderstack.com/api/v1/renders/sync", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.RENDERSTACK_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
template: "event-badge",
layers: {
attendee_name: { text: attendee.fullName },
job_title: { text: attendee.title },
company: { text: attendee.company },
session_track: { text: attendee.track },
track_color: { fill: TRACK_COLORS[attendee.track] ?? "#6b7280" },
headshot: attendee.photoUrl ? { src: attendee.photoUrl, fillMode: "smart-cover" } : undefined,
check_in_qr: { value: `https://events.example.com/check-in?id=${attendee.id}` },
},
format: "png",
quality: 95,
}),
});
const imageBuffer = await response.arrayBuffer();
Omitting
headshotentirely (or setting it toundefined) causes the headshot layer to be absent fromlayers. If the template uses auto-visibility, the frame simply won't render — no placeholder, no broken image.
Variable-length schedule — using a repeater#
body: JSON.stringify({
template: "event-schedule",
layers: {
event_title: { text: event.name },
event_date: { text: formatDate(event.date) },
sessions: event.sessions.map(session => ({
session_title: { text: session.name },
session_time: { text: `${session.startTime} – ${session.endTime}` },
speaker_name: { text: session.speaker },
session_image: session.thumbnailUrl ? { src: session.thumbnailUrl } : undefined,
})),
},
})
The repeater handles all layout — spacing, overflow clipping, and grid arrangement are resolved automatically from the template configuration. If you supply 12 sessions and the template designer set maxItems: 10, you'll get exactly 10 rows with no error.
Embedding a dynamic OG image in your web app#
Use a GET key to generate a dynamic Open Graph image:
<meta property="og:image" content="https://your-renderstack.com/api/v1/render
?apiKey=rs_get_YOUR_KEY
&template=event-og-card
&event_title.text=Tech+Summit+2026
&event_date.text=June+12%2C+2026
&location.text=San+Francisco%2C+CA
&hero_image.src=https%3A%2F%2Fcdn.example.com%2Fevents%2Fsummit.jpg" />
The URL is constructed per page. The render is cached, so repeated requests for the same parameter set are served instantly.