Templates & Rendering

Using CURL with RenderStack

RenderStack provides a REST API for rendering images from templates programmatically. All API endpoints require authentication via API key.

Base URL#

https://your-app.replit.app/api/v1

Authentication#

All API requests must include an API key in the Authorization header:

Authorization: Bearer YOUR_API_KEY

API keys can be created and managed from the API Keys page in the dashboard. See the API Keys documentation for more details.

Error Format#

All errors follow a consistent format:

{
  "type": "https://renderstack.dev/errors/error-type",
  "title": "Error Title",
  "status": 400,
  "detail": "A human-readable description of the error"
}

Error Types#

StatusTypeDescription
400validation-errorInvalid or missing request parameters
401authentication-errorInvalid, missing, or revoked API key
403forbiddenKey type mismatch or origin not allowed
404not-foundTemplate or resource not found
414uri-too-longURL exceeds 8,192 character limit (GET only)
429rate-limitedRate limit exceeded (GET only)
500server-errorInternal server error during processing

Endpoints#

Render Image (Binary)#

Renders a template and returns the image as binary data (raw image file).

POST /api/v1/renders/sync

Request Body:

FieldTypeRequiredDescription
templatestringYesTemplate ID or slug
layersobjectNoDynamic layer overrides (see below)
formatstringNoOutput format: png, jpeg, or pdf (default: png)
qualitynumberNoJPEG quality 1-100 (default: 90, JPEG only)
widthnumberNoOverride output width in pixels
heightnumberNoOverride output height in pixels
sizesarrayNoArray of { width, height } for multi-page PDF (max 50 entries, PDF only)
useAwsbooleanNoUpload the rendered output to your configured AWS S3 bucket
filenamestringNoCustom filename for the S3 object (e.g. "my-render.png"). Ignored if useAws is not true

Example Request:

curl -X POST https://your-app.replit.app/api/v1/renders/sync \
  -H "Authorization: Bearer rs_abc123..." \
  -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" },
      "date_text": { "text": "March 15, 2026" }
    },
    "format": "png"
  }' \
  --output social-card.png

Success Response:

  • Status: 200
  • Content-Type: image/png, image/jpeg, or application/pdf
  • Body: Raw binary data (image or PDF document)

Response Headers:

HeaderDescription
X-Render-IdUnique ID of the render record
X-Render-Duration-MsTime taken to render in milliseconds
X-Render-WarningsJSON array of warnings (if any)
X-Render-S3-UrlS3 object URL (only when useAws: true and upload succeeds)
X-Render-S3-KeyS3 object key (only when useAws: true and upload succeeds)

Render Image (JSON)#

Renders a template and returns the image as a base64-encoded JSON response.

POST /api/v1/renders/json

Request Body: Same as the binary render endpoint above.

Example Request:

curl -X POST https://your-app.replit.app/api/v1/renders/json \
  -H "Authorization: Bearer rs_abc123..." \
  -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:

FieldTypeDescription
idstringUnique render record ID
statusstringRender status (completed)
templatestringTemplate ID used for the render
template_versionnumberTemplate version at time of render
created_atstringISO 8601 timestamp when the render was created
completed_atstringISO 8601 timestamp when the render completed
duration_msnumberRender duration in milliseconds
output.datastringBase64-encoded data URI of the rendered output
output.mime_typestringMIME type (image/png, image/jpeg, or application/pdf)
output.size_bytesnumberSize of the output in bytes
output.widthnumberWidth of the rendered output
output.heightnumberHeight of the rendered output
output.page_countnumberNumber of pages (PDF only, included when format is pdf)
warningsarrayList of warning objects (see below)
aws_s3objectS3 upload details (only present when useAws: true and upload succeeds)
aws_s3.urlstringPublic URL of the uploaded S3 object
aws_s3.keystringS3 object key
aws_s3.bucketstringS3 bucket name

List Templates#

Lists all templates accessible by the authenticated API key.

GET /api/v1/templates

Example Request:

curl https://your-app.replit.app/api/v1/templates \
  -H "Authorization: Bearer rs_abc123..."

Success Response:

{
  "data": [
    {
      "id": "template_abc123",
      "name": "Social Card",
      "slug": "social-card",
      "width": 1200,
      "height": 630,
      "version": 3,
      "is_published": true,
      "layer_schema": { ... },
      "created_at": "2026-01-15T10:30:00Z",
      "updated_at": "2026-02-01T14:22:00Z"
    }
  ],
  "total": 1
}

Get Template#

Retrieves a single template by ID.

GET /api/v1/templates/:id

Example Request:

curl https://your-app.replit.app/api/v1/templates/template_abc123 \
  -H "Authorization: Bearer rs_abc123..."

Success Response:

{
  "id": "template_abc123",
  "name": "Social Card",
  "slug": "social-card",
  "width": 1200,
  "height": 630,
  "version": 3,
  "is_published": true,
  "layer_schema": { ... },
  "canvas_data": { ... },
  "created_at": "2026-01-15T10:30:00Z",
  "updated_at": "2026-02-01T14:22:00Z"
}

Response Fields:

FieldTypeDescription
idstringUnique template ID
namestringTemplate display name
slugstringURL-friendly identifier used in API calls
widthnumberCanvas width in pixels
heightnumberCanvas height in pixels
versionnumberCurrent template version number
is_publishedbooleanWhether the template is published
layer_schemaobjectSchema describing dynamic layers
canvas_dataobjectFull canvas element data
created_atstringISO 8601 creation timestamp
updated_atstringISO 8601 last-updated timestamp

Get Render#

Retrieves a single render record by ID.

GET /api/v1/renders/:id

Example Request:

curl https://your-app.replit.app/api/v1/renders/render_abc123 \
  -H "Authorization: Bearer rs_abc123..."

Success Response:

{
  "id": "render_abc123",
  "userId": "user_id",
  "templateId": "template_abc123",
  "templateVersion": 3,
  "status": "completed",
  "format": "png",
  "quality": 90,
  "width": 1200,
  "height": 630,
  "durationMs": 234,
  "fileSize": 45320,
  "resultUrl": "https://...",
  "warnings": [],
  "layers": { ... },
  "createdAt": "2026-02-01T14:22:00Z",
  "completedAt": "2026-02-01T14:22:00.234Z"
}

Render Image (GET — Dynamic Image URL)#

Renders a template directly via URL query parameters. Returns the image as binary data suitable for use in <img> tags. Requires a GET-type API key (rs_get_...).

GET /api/v1/render?apiKey=YOUR_GET_KEY&template=TEMPLATE_SLUG

Authentication: API key is passed as a query parameter (apiKey), not in the Authorization header.

Requirements:

  • A GET-type API key (prefix rs_get_)
  • At least one active Allowed Host configured in Settings
  • Request must originate from an allowed host

Query Parameters:

ParameterTypeRequiredDescription
apiKeystringYesGET API key (starts with rs_get_)
templatestringYesTemplate ID or slug
widthnumberNoOverride output width in pixels
heightnumberNoOverride output height in pixels
formatstringNopng or jpeg only (default: png)
qualitynumberNoJPEG quality 1-100 (default: 90)
{layer}.{prop}stringNoLayer overrides in dot notation

Layer Overrides via Dot Notation:

Use layerName.property=value as query parameters:

?title.text=Hello+World&title.fill=%23ff0000&logo.src=https://example.com/logo.png

Example Request:

<img src="https://your-app.replit.app/api/v1/render?apiKey=rs_get_abc123&template=social-card&title.text=Hello+World&subtitle.text=Dynamic+Images" />

Success Response:

  • Status: 200
  • Content-Type: image/png or image/jpeg
  • Body: Raw binary image data

Response Headers:

HeaderDescription
X-CacheHIT (cached) or MISS (freshly rendered)
X-Render-IdUnique render record ID (only on cache MISS)
ETagCache key hash for browser caching
Cache-Controlpublic, max-age=604800, immutable

Rate Limits:

ScopeLimit
Per API key60 requests/minute
Per IP address30 requests/minute
Cache hitsCount as 0.25x toward limits

Limitations:

  • Maximum URL length: 8,192 characters
  • Maximum dimensions: 8,192 × 8,192 pixels
  • No PDF format support (PNG and JPEG only)

See Dynamic Image URLs for complete documentation and examples.


Layer Overrides#

The layers object in render requests maps layer API names to property overrides. The key for each layer is the element's API Name from the editor — a snake_case identifier that is auto-generated from the layer's display Name (e.g., "Category Label" becomes category_label). You can view and customize the API Name in the Properties panel. The properties you can override depend on the layer type.

Auto-Hide Visibility: Layers with visibility mode set to Auto in the template editor are completely hidden from the rendered output when their content is empty or not provided — no placeholder, no border, nothing. Simply omit the layer from your layers object (or pass an empty value) to hide it. See Layers — Visibility Modes for details.

Text Layer Overrides#

{
  "headline": {
    "text": "Custom text content",
    "fill": "#ff0000",
    "fontSize": 48,
    "fontFamily": "Arial"
  }
}
PropertyTypeDescription
textstringText content to display
fillstringText color (hex, rgb, or named color)
fontSizenumberFont size in pixels
fontFamilystringFont family name
fontWeightstringFont weight: normal or bold
fontStylestringFont style: normal or italic
textAlignstringAlignment: left, center, or right
boxFillstringBackground fill color for the entire text element bounding box
boxFillOpacitynumberFill opacity 0–1
boxStrokestringBorder color for the text element box
boxStrokeWidthnumberBorder thickness value
boxStrokeWidthUnitstringUnit for border thickness: "px" (default) or "%" (percentage of min dimension)
boxStrokeWidthMinnumberMinimum border thickness in px when using % unit
boxStrokeWidthMaxnumberMaximum border thickness in px when using % unit
boxStrokeOpacitynumberBorder opacity 0–1
boxStrokeAlignstring"inner", "center" (default), or "outer"
boxCornerRadiusnumberCorner radius value for the text element box
boxCornerRadiusUnitstringUnit for corner radius: "px" (default) or "%" (percentage of min dimension)
boxCornerRadiusMinnumberMinimum corner radius in px when using % unit
boxCornerRadiusMaxnumberMaximum corner radius in px when using % unit
boxPaddingTopnumberTop padding between the box edge and text content
boxPaddingRightnumberRight padding between the box edge and text content
boxPaddingBottomnumberBottom padding between the box edge and text content
boxPaddingLeftnumberLeft padding between the box edge and text content
boxPaddingUnitstringUnit for all padding values: "px" (default), "%-width" (% of element width), or "%-height" (% of element height)

Image Layer Overrides#

{
  "profile_photo": {
    "src": "https://example.com/photo.jpg",
    "fillMode": "cover"
  }
}
PropertyTypeDescription
srcstringURL of the image to display
fillModestringcover, contain, stretch, or original

QR Code Layer Overrides#

{
  "ticket_qr": {
    "value": "https://example.com/ticket/12345",
    "darkColor": "#1a1a2e",
    "lightColor": "#ffffff",
    "errorCorrectionLevel": "H",
    "margin": 1
  }
}
PropertyTypeDescription
valuestringData to encode in the QR code (URL, text, vCard, etc.)
darkColorstringColor of QR modules (hex color, default: #000000)
lightColorstringBackground color (hex color or transparent, default: #ffffff)
errorCorrectionLevelstringError correction: L, M (default), Q, or H
marginnumberQuiet zone in modules (0–10, default: 2)
cornerRadiusnumberCorner radius (px or % based on unit setting)
cornerRadiusUnitstringUnit for corner radius: "px" (default) or "%"

Shape Layer Overrides#

{
  "background_box": {
    "fill": "#3b82f6",
    "stroke": "#1d4ed8",
    "strokeWidth": 2
  }
}
PropertyTypeDescription
fillstringFill color
strokestringBorder/stroke color
strokeWidthnumberBorder thickness (px or % based on unit setting)
strokeWidthUnitstringUnit for stroke width: "px" (default) or "%"
strokeWidthMinnumberMin stroke width in px when using % (optional)
strokeWidthMaxnumberMax stroke width in px when using % (optional)
strokeAlignstringStroke alignment: "center" (default), "inner", or "outer"
cornerRadiusnumberCorner radius for rects (px or % based on unit setting)
cornerRadiusUnitstringUnit for corner radius: "px" (default) or "%"
cornerRadiusMinnumberMin corner radius in px when using % (optional)
cornerRadiusMaxnumberMax corner radius in px when using % (optional)

Transform Overrides (All Layer Types)#

Any layer can include transform overrides to reposition or resize elements at render time. When a layer uses percentage-based units for its transform properties (X, Y, Width, Height), each property has a configurable reference dimension that controls whether the percentage resolves against the canvas width or the canvas height.

{
  "headline": {
    "text": "Custom text",
    "percentRefX": "width",
    "percentRefY": "width",
    "percentRefW": "width",
    "percentRefH": "height"
  }
}
PropertyTypeDefaultDescription
percentRefX"width" | "height""width"Reference dimension for percentage-based X position. When set to "width", the X percentage resolves against the canvas width; when "height", it resolves against the canvas height.
percentRefY"width" | "height""width"Reference dimension for percentage-based Y position. Defaults to width-based so that equal X and Y percentages produce equal pixel offsets.
percentRefW"width" | "height""width"Reference dimension for percentage-based Width.
percentRefH"width" | "height""height"Reference dimension for percentage-based Height.

Pin Edge Overrides#

When a layer uses pinning (anchoring to canvas edges or other elements), each pin edge supports a percentRef field that controls the reference dimension for percentage-based offsets.

{
  "headline": {
    "text": "Pinned text",
    "pinLeft": {
      "enabled": true,
      "target": "(edge)",
      "offsetPercent": 10,
      "unit": "%",
      "percentRef": "width"
    },
    "pinTop": {
      "enabled": true,
      "target": "(edge)",
      "offsetPercent": 10,
      "unit": "%",
      "percentRef": "width"
    }
  }
}
PropertyTypeDefaultDescription
percentRef"width" | "height""width"Determines whether percentage-based pin offsets resolve against the canvas width or canvas height. When set to "width" (default), a 10% offset on any edge resolves to 10% of the canvas width, producing uniform spacing regardless of aspect ratio. Set to "height" when the offset should scale relative to the canvas height instead.

Tip: Using width-based reference (the default) for all pin edges ensures that equal percentage values on different edges produce equal pixel distances. For example, setting 10% on both left and top pins with percentRef: "width" means both offsets resolve against the canvas width, so the element maintains equal margins from the left and top edges even when the template is rendered at a different aspect ratio.


Warnings#

Warnings are returned when a render completes but encountered non-fatal issues. The render still produces an image, but some elements may not appear as expected.

Warning Object:

{
  "element": "profile_photo",
  "code": "IMAGE_LOAD_FAILED",
  "message": "Could not load image: 404 Not Found"
}

Warning Codes:

CodeDescription
IMAGE_LOAD_FAILEDCould not load an image from the provided URL
IMAGE_URL_BLOCKEDImage URL was blocked for security (internal network, etc.)
UNKNOWN_LAYERA layer name in the request doesn't exist in the template
PRESIGNED_URL_IN_LAYERA layer image src is a short-lived pre-signed URL (GCS or AWS). The render succeeds but the URL will expire, making the render record and any future re-render unreliable. Replace with a stable, permanent URL.
MISSING_DYNAMIC_LAYERA dynamic template element was not provided in layers. The element's template default value was rendered in its place.
S3_UPLOAD_FAILEDFailed to upload the rendered file to AWS S3

Rate Limits#

GET Render Endpoint#

The GET render endpoint enforces rate limits:

ScopeLimit
Per API key60 requests/minute
Per IP address30 requests/minute
Cache hitsCount as 0.25x toward limits

When rate-limited, the response includes a Retry-After header. The response status code is 429.

POST Render Endpoints#

POST render endpoints do not currently enforce strict rate limits, but excessive usage may be throttled. We recommend:

  • Caching rendered images when the same data is used repeatedly
  • Using reasonable image dimensions (avoid exceeding 4096x4096 unless needed)
  • Batching requests where possible

Code Examples#

Node.js#

const response = await fetch('https://your-app.replit.app/api/v1/renders/sync', {
  method: 'POST',
  headers: {
    'Authorization': 'Bearer YOUR_API_KEY',
    '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();
// Save or process the image buffer

Python#

import requests

response = requests.post(
    'https://your-app.replit.app/api/v1/renders/sync',
    headers={
        'Authorization': 'Bearer YOUR_API_KEY',
        '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)

cURL#

curl -X POST https://your-app.replit.app/api/v1/renders/sync \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"template":"social-card","layers":{"title":{"text":"Hello"}}}' \
  -o output.png

Tip: You can auto-generate a curl command pre-populated with your template's slug, dimensions, and dynamic layers directly from the template editor. Open your template and click the API Payload button (code icon) in the bottom-right corner of the canvas.