Polotno Studio API

Quickstart

The Polotno Studio API renders images and videos from your templates. It is designed for no-code automation (Zapier, Make, n8n) and direct API calls. This guide gets you from an API key to your first rendered image. For the full endpoint-by-endpoint reference, see the API Reference.

1. Get an API key

API keys are per project. Open the editor, open the projects side-panel, and enable automation on a project — the key is shown once, so copy it immediately. The key scopes every request to that project (its templates, renders, and webhooks).

Send it on every request as a Bearer token, or in the X-API-Key header:

bash
# Either header works:
curl https://api.studio.polotno.com/v1/templates \
  -H "Authorization: Bearer key_live_xxx"

curl https://api.studio.polotno.com/v1/templates \
  -H "X-API-Key: key_live_xxx"

Live keys are prefixed key_live_; test keys key_test_. A revoked or invalid key returns 401; a project with automation disabled returns 409.

2. Render your first image

POST /v1/images creates a render. By default it is asynchronous and returns 202 with a pending image. Add ?sync=true to wait up to 30 seconds for a terminal result (201 if the render completes or fails within the window, otherwise 202 with a pending render to poll).

bash
curl -X POST https://api.studio.polotno.com/v1/images \
  -H "Authorization: Bearer key_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "template_id": "tpl_xxx",
    "dynamic_fields": [
      { "name": "headline", "text": "50% OFF Black Friday" },
      { "name": "product_image", "image_url": "https://example.com/shoe.jpg" }
    ],
    "format": "png",
    "pixel_ratio": 1,
    "webhook_url": "https://hooks.zapier.com/...",
    "metadata": { "campaign_id": "camp_2026" }
  }'

A dynamic_fields entry targets one element by its name and sets one or more of: text, image_url, video_url, color, font_family, font_size, visible. format is png, jpeg, or pdf; pixel_ratio must be greater than 0 and at most 10.

3. Map fields in Zapier / Make / n8n

To show friendly field names in a no-code tool, call GET /v1/templates/{id}/dynamic-fields. It returns a flat list; each field's key encodes the element name and the property to set (e.g. fields__headline__text or fields__product_image__image_url), alongside a human label, a type (string / url / integer / color / boolean), and a required flag:

json
{
  "fields": [
    {
      "key": "fields__headline__text",
      "label": "Headline",
      "type": "string",
      "required": false,
      "help_text": "Main title at the top",
      "default": "Default headline"
    },
    {
      "key": "fields__product_image__image_url",
      "label": "Product Image (URL)",
      "type": "url",
      "required": false
    }
  ]
}

4. Get the result

Poll GET /v1/images/{id} until status is completed and read image_url — or subscribe a webhook (below) and receive the finished render with no polling.

bash
curl https://api.studio.polotno.com/v1/images/img_xxx \
  -H "Authorization: Bearer key_live_xxx"

5. Subscribe to webhooks (optional)

Instead of polling, create a webhook with POST /v1/webhooks. The response includes a one-time secret you use to verify the HMAC signature on each delivery. Events include image.completed, image.failed, video.completed, video.failed, and bulk-job events. Manage subscriptions and inspect deliveries under /v1/webhooks — see the reference.

6. Rate limits, idempotency & errors

Every response carries X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset; a 429 adds Retry-After. Make POSTs safe to retry by sending an Idempotency-Key header — a replay returns the original response with Idempotency-Replayed: true.

Errors share one envelope:

json
{
  "error": {
    "type": "validation_error",
    "code": "invalid_request",
    "message": "template_id is required",
    "param": "template_id",
    "request_id": "req_xxx"
  }
}