{"id":6643,"plugin_id":"plugin_asdk_app_6a79affedd808191b69f2e62014b8235","kind":"skill","collection_source":null,"comparison_source":null,"observed_at":"2026-09-30T22:48:46.959Z","digest":"e82fb9d3403ae3c18adc2463a26b3d178c57799591ecf56754f3383d400965a8","against":null,"payload":{"name":"orshot","description":"Generate images, PDFs, and videos from templates with Orshot — via the REST API, SDKs (Node/Python/PHP/Ruby), the remote MCP server, or no-code tools (Zapier, Make, n8n, Airtable), and wire those renders into a recurring automation. Use whenever the user mentions Orshot, connects Orshot to an AI agent or MCP client, wants a render to run unattended on a schedule or trigger, or needs to programmatically produce marketing visuals, OG images, social carousels, certificates/invoices/tickets as PDF, or video from templates — including when migrating from Bannerbear, Placid, Creatomate, RenderForm, Abyssale, or similar. Always load this skill for Orshot tasks, even simple ones, because it carries gotchas (modifications key by parameterId, multi-page needs a page1@ prefix, video output needs video elements in the template) that otherwise cause failed renders. Not for generating standalone AI images with no template or Orshot involved.","included_files":[],"skill_md_contents":"---\nname: orshot\ndescription: Generate images, PDFs, and videos from templates with Orshot — via the REST API, SDKs (Node/Python/PHP/Ruby), the remote MCP server, or no-code tools (Zapier, Make, n8n, Airtable), and wire those renders into a recurring automation. Use whenever the user mentions Orshot, connects Orshot to an AI agent or MCP client, wants a render to run unattended on a schedule or trigger, or needs to programmatically produce marketing visuals, OG images, social carousels, certificates/invoices/tickets as PDF, or video from templates — including when migrating from Bannerbear, Placid, Creatomate, RenderForm, Abyssale, or similar. Always load this skill for Orshot tasks, even simple ones, because it carries gotchas (modifications key by parameterId, multi-page needs a page1@ prefix, video output needs video elements in the template) that otherwise cause failed renders. Not for generating standalone AI images with no template or Orshot involved.\nmetadata:\n  author: Rishi Mohan\n  version: \"1.1.0\"\n---\n\n# Orshot – Automated Visual Content Generation\n\n[Orshot](https://orshot.com) is an automated image, PDF, and video generation platform. Design templates in Orshot Studio (or import from Canva/Figma), then generate renders via REST API, SDKs, or no-code integrations.\n\n- **Documentation:** https://orshot.com/docs\n- **API Base URL:** https://api.orshot.com/v1\n\n## When to Use This Skill\n\nUse this skill whenever the user mentions **Orshot**, or when the task is:\n\n- Generating images, PDFs, or videos programmatically from templates\n- Building automated marketing visual pipelines (OG images, ad creatives, thumbnails)\n- Creating dynamic social media content (carousels, posts, stories) and publishing it\n- Generating certificates, invoices, tickets, or reports as PDFs\n- Building image/PDF/video generation into a product\n- Automating visuals with Zapier, Make, n8n, Airtable, or the CLI\n- Connecting Orshot to an AI agent or MCP client (Claude, Cursor, Codex, Windsurf, ChatGPT)\n- Embedding a white-label design editor into an app\n- Migrating from Bannerbear, Placid, Creatomate, RenderForm, Abyssale, DynaPictures, or Contentdrips\n\n**Don't use this skill** for generating standalone AI images with no template or Orshot involved (a one-off \"make me an image\" request) — that isn't what Orshot does.\n\n## Accessing Detailed Documentation\n\nAny page on orshot.com can be fetched as clean markdown by appending `.md` to the URL or sending `Accept: text/markdown` header. Use this to get detailed, up-to-date information on demand without relying solely on this skill file.\n\n**Examples:**\n```\nhttps://orshot.com/docs/api-reference.md\nhttps://orshot.com/docs/sdks/node.md\nhttps://orshot.com/docs/publish/publish-from-api.md\nhttps://orshot.com/docs/developers/oauth-overview.md\nhttps://orshot.com/docs/orshot-embed/introduction.md\nhttps://orshot.com/blog/bannerbear-api-alternative.md\n```\n\n**Key documentation pages:**\n| Topic | URL |\n|-------|-----|\n| API Reference | `https://orshot.com/docs/api-reference.md` |\n| Node.js SDK | `https://orshot.com/docs/sdks/node.md` |\n| Python SDK | `https://orshot.com/docs/sdks/python.md` |\n| PHP SDK | `https://orshot.com/docs/sdks/php.md` |\n| Ruby SDK | `https://orshot.com/docs/sdks/ruby.md` |\n| Studio Templates | `https://orshot.com/docs/orshot-studio/introduction.md` |\n| Style Parameters | `https://orshot.com/docs/orshot-studio/style-parameters.md` |\n| Setting Parameters | `https://orshot.com/docs/orshot-studio/setting-parameters.md` |\n| Image Generation | `https://orshot.com/docs/image-generation.md` |\n| Video Generation | `https://orshot.com/docs/video-generation.md` |\n| PDF Generation | `https://orshot.com/docs/pdf-generation.md` |\n| Social Publishing | `https://orshot.com/docs/publish/introduction.md` |\n| OAuth / Developer Apps | `https://orshot.com/docs/developers.md` |\n| White-Label Embed | `https://orshot.com/docs/orshot-embed/introduction.md` |\n| Integrations | `https://orshot.com/docs/integrations.md` |\n| Dynamic URLs | `https://orshot.com/docs/integrations/dynamic-urls.md` |\n| Webhooks | `https://orshot.com/docs/integrations/webhooks.md` |\n| Error Reference | `https://orshot.com/docs/error-reference.md` |\n\nWhen a user asks about a specific topic, fetch the relevant `.md` URL for the latest details.\n\n## Getting Started\n\n### Authentication\n\nAll API requests require a Bearer token in the `Authorization` header:\n\n```\nAuthorization: Bearer <ORSHOT_API_KEY>\n```\n\n[Get your API key](https://orshot.com/docs/quick-start/get-api-key) from **Workspace Settings → API Keys** in the Orshot dashboard.\n\n### Remote MCP Server\n\nOrshot runs a hosted MCP server so agents (Claude, Cursor, Codex, Windsurf, ChatGPT, etc.) can use Orshot as tools — no local install.\n\n- **URL:** `https://mcp.orshot.com/mcp` (transport: `streamable-http`)\n- **Auth:** OAuth 2.0 (the client walks you through it), or send an Orshot API key as a Bearer token\n- **Claude Code:** `claude mcp add --transport http orshot https://mcp.orshot.com/mcp`\n- **Cursor / Windsurf / VS Code / ChatGPT / Codex:** add the URL as a remote/HTTP MCP server\n\nIt exposes tools for rendering, studio + library templates, brand assets, workflows, social accounts, and workspace/logs. Full discovery doc: `https://orshot.com/.well-known/mcp.json`. Setup guide: `https://orshot.com/docs/integrations/mcp-server.md`.\n\n### SDKs\n\n#### Node.js\n\n```bash\nnpm install orshot\n```\n\n```js\nimport { Orshot } from \"orshot\";\nconst orshot = new Orshot(\"<ORSHOT_API_KEY>\");\n\n// Render from template\nconst response = await orshot.renderFromTemplate({\n  templateId: \"open-graph-image-1\",\n  modifications: { title: \"Hello World\" },\n  responseType: \"base64\", // \"base64\" | \"url\" | \"binary\"\n  responseFormat: \"png\", // \"png\" | \"webp\" | \"jpg\" | \"pdf\"\n});\n\n// Generate signed URL\nconst signedUrl = await orshot.generateSignedUrl({\n  templateId: \"open-graph-image-1\",\n  modifications: { title: \"Hello\" },\n  expiresAt: 1744276943,\n  renderType: \"images\",\n  responseFormat: \"png\",\n});\n```\n\n#### Python\n\n```bash\npip install orshot\n```\n\n```python\nimport orshot\nos = orshot.Orshot('<ORSHOT_API_KEY>')\n\nresponse = os.render_from_template({\n  'template_id': 'open-graph-image-1',\n  'modifications': {'title': 'Hello World'},\n  'response_type': 'base64',\n  'response_format': 'png'\n})\n```\n\n#### Other SDKs\n\n- **PHP:** `composer require rishimohan/orshot`\n- **Ruby:** `gem install orshot`\n\n## Common Gotchas (read before rendering)\n\nThe mistakes that most often make Orshot calls fail or return the wrong output:\n\n1. **Modifications key by `parameterId`, not layer name.** `modifications: { \"headline\": \"...\" }` targets the element whose `parameterId` is `headline`. If a value is ignored, the key is wrong — fetch the template's modifications (`GET /v1/studio/templates/:id`) to see the real keys instead of guessing.\n2. **Multi-page templates need a `pageN@` prefix.** Use `\"page1@title\"`, `\"page2@title\"`. A bare `\"title\"` only affects page 1.\n3. **Studio `templateId` is an integer; utility `templateId` is a string.** `/v1/studio/render` takes an integer ID; `/v1/generate/:renderType` takes a string slug like `\"website-screenshot\"`.\n4. **Video output requires video elements in the template.** Requesting `format: \"mp4\"` on an image-only template fails — the template must contain at least one video element.\n5. **Image/video URLs in modifications must be publicly reachable.** The renderer fetches them server-side, so `localhost`, expired signed URLs, or auth-gated URLs won't load.\n6. **Style overrides use dot notation on the parameterId** — `\"title.fontSize\": \"48px\"`, not a nested object.\n7. **Response shape differs by page count:** single page → `data` is an object (read `data.content`); multi-page/carousel → `data` is an array of `{ page, content }`. Handle both.\n8. **`base64` and `binary` response types don't combine** — `binary` returns one raw file stream.\n9. **Credits vs AI Credits are separate** — normal renders spend credits; `.prompt` (AI) modifications additionally spend AI Credits.\n10. **On `429`, back off** using the `Retry-After` response header.\n\nWhen unsure about a template's parameters, always fetch its modifications first.\n\n## Template Architecture\n\nThis section describes the complete structure of an Orshot template for MCP tools and AI agents.\n\n### Template Structure\n\nAn Orshot template consists of **pages**, each containing a **canvas** and **elements**.\n\n```\nTemplate\n├── id: number | string\n├── name: string\n├── description: string\n├── width: number\n├── height: number\n├── pages_data: Array\n    └── Page\n        ├── id: string (UUID)\n        ├── name: string\n        ├── canvas: CanvasConfig\n            ├── width: number\n            ├── height: number\n            ├── backgroundColor: string\n            ├── backgroundImage: string\n        ├── elements: Element[]\n        ├── modifications: Modification[] (API parameters)\n            ├── id: string\n            ├── type: string\n            ├── element: Element\n            ├── description: string\n        └── thumbnail_url: string | null\n```\n\n### Canvas Configuration\n\n| Property          | Type   | Default         | Description                               |\n| ----------------- | ------ | --------------- | ----------------------------------------- |\n| `width`           | number | 800             | Canvas width in pixels (max: 5000)        |\n| `height`          | number | 800             | Canvas height in pixels (max: 5000)       |\n| `backgroundColor` | string | \"#ffffff\"       | Background color (hex, rgba, or gradient) |\n| `backgroundImage` | string | \"\"              | URL to background image                   |\n| `borderWidth`     | number | 0               | Border width in pixels                    |\n| `borderColor`     | string | \"rgba(0,0,0,1)\" | Border color                              |\n| `borderStyle`     | string | \"solid\"         | Border style (solid, dashed, etc)         |\n\n### Canvas Size Presets\n\n| Name                 | Dimensions | Use Case                        |\n| -------------------- | ---------- | ------------------------------- |\n| Square               | 1080×1080  | Instagram posts, general social |\n| Instagram Story      | 1080×1920  | Stories, Reels, TikTok          |\n| Slide/Presentation   | 1920×1080  | Presentations, slides           |\n| YouTube Thumbnail    | 1280×720   | Video thumbnails                |\n| Twitter Post         | 1600×900   | X/Twitter posts                 |\n| Open Graph           | 1200×630   | Link previews, Facebook         |\n| Pinterest Pin        | 1000×1500  | Pinterest                       |\n| A4 Document          | 2480×3508  | Print documents                 |\n| App Store Screenshot | 1290×2796  | iOS app screenshots             |\n\n### Universal Element Properties\n\nAll elements share these base properties:\n\n| Property            | Type    | Description                                  |\n| ------------------- | ------- | -------------------------------------------- |\n| `id`                | string  | Unique identifier (UUID)                     |\n| `name`              | string  | Display name in layer list (was `layerName`) |\n| `type`              | string  | \"text\", \"image\", \"shape\", \"video\"            |\n| `position`          | object  | `{ x: number, y: number }` from top-left     |\n| `dimensions`        | object  | `{ width: number, height: number }`          |\n| `rotation`          | number  | Rotation in degrees (0-360)                  |\n| `zIndex`            | number  | Layer order (higher = on top)                |\n| `aspectRatioLocked` | boolean | Lock aspect ratio during resize              |\n| `isHidden`          | boolean | Hide element from render                     |\n| `skewX`             | number  | Horizontal skew angle                        |\n| `skewY`             | number  | Vertical skew angle                          |\n\n### Text Element\n\n**Content Types:**\n\n- **Plain text**: `\"Hello World\"` - Standard text string\n- **Multi-line**: Use `\\n` for line breaks: `\"Line 1\\nLine 2\"`\n- **Dynamic via API**: Use `.prompt` modifier for AI-generated text\n\n```javascript\n{\n  type: \"text\",\n  content: string,          // Plain text string\n  layerName: string,        // Display name\n  zIndex: number,\n  rotation: number,\n  position: { x: number, y: number },\n  dimensions: { width: number, height: number },\n  style: {\n    // Typography\n    fontFamily: string,     // e.g., \"Inter\", \"Prata\", \"SF Pro\"\n    fontSize: string,       // e.g., \"48px\"\n    fontWeight: string | number, // \"400\", \"700\", 700\n    fontStyle: string,      // \"normal\", \"italic\"\n    lineHeight: number,     // e.g., 1.2\n    letterSpacing: string,  // e.g., \"0px\", \"2px\"\n\n    // Appearance\n    fill: string,           // Color or gradient\n    color: string,          // Hex or rgba\n    opacity: number,        // 0-1\n    stroke: string,         // Stroke color\n    strokeWidth: string,    // e.g., \"0px\"\n\n    // Alignment & Layout\n    textAlign: string,      // \"left\", \"center\", \"right\"\n    verticalAlign: string,  // \"flex-start\", \"center\", \"flex-end\"\n    textTransform: string,  // \"none\", \"uppercase\"\n    textDecoration: string, // \"none\", \"underline\"\n    textMode: string,       // \"overflow\", \"fit\"\n    paddingX: string,\n    paddingY: string,\n\n    // Borders & Backgrounds\n    borderColor: string,\n    borderWidth: string,\n    borderRadius: string,\n    textBackgroundColor: string,\n    textBackgroundRadius: string,\n    textStrokeColor: string,\n    textStrokeWidth: string,\n\n    // Effects\n    minFontSize: string,    // For \"fit\" mode\n    filter: string,         // e.g., \"blur(0px)\"\n    mixBlendMode: string,   // \"normal\", \"multiply\", etc.\n    boxShadowX: string,\n    boxShadowY: string,\n    boxShadowBlur: string,\n    boxShadowColor: string,\n    dropShadowX: string,\n    dropShadowY: string,\n    dropShadowBlur: string,\n    dropShadowColor: string\n  },\n  // Parameterization\n  parameterizable: boolean,\n  parameterId: string,\n  parameterType: \"text\"\n}\n```\n\n**Gradient text:**\n\n```javascript\ncolor: \"linear-gradient(90deg, #FF6B6B 0%, #4ECDC4 100%)\";\n```\n\n### Image Element\n\n**Content Types:**\n\n- **URL** (recommended): `\"https://example.com/image.png\"` - Best for dynamic content\n- **Base64**: `\"data:image/png;base64,iVBORw0KGgo...\"` - For embedded images\n- **Binary**: Raw binary data (API upload only)\n\n```javascript\n{\n  type: \"image\",\n  content: string,          // URL (preferred), base64, or binary\n  isSvg: boolean,\n  layerName: string,\n  style: {\n    // Sizing & Positioning\n    objectFit: string,      // \"contain\", \"cover\", \"fill\"\n    objectPosition: string, // \"center\", \"top left\"\n\n    // Appearance\n    opacity: number,\n    fill: string,           // Background fill\n    stroke: string,         // Border stroke\n\n    // Borders\n    borderRadius: string,   // \"0px\", \"12px\", \"50%\"\n    borderWidth: string,\n    borderColor: string,\n\n    // Effects\n    filter: string,         // \"blur(2px)\", \"grayscale(100%)\"\n    mixBlendMode: string,\n    boxShadowX: string,\n    boxShadowY: string,\n    boxShadowBlur: string,\n    boxShadowColor: string,\n    dropShadowX: string,\n    dropShadowY: string,\n    dropShadowBlur: string,\n    dropShadowColor: string,\n\n    svgColor: string        // Recolor monochrome SVGs\n  },\n  parameterType: \"imageUrl\"\n}\n```\n\n### Shape Element\n\n```javascript\n{\n  type: \"shape\",\n  shapeType: string,        // \"rectangle\", \"circle\", \"arrow\"\n  layerName: string,\n  style: {\n    // Fill & Stroke\n    fill: string,           // Color or gradient\n    stroke: string,\n    strokeWidth: string,    // e.g. \"0px\"\n\n    // Dimensions\n    borderRadius: string,   // Rectangle only\n    borderWidth: string,\n    borderColor: string,\n    borderStyle: string,\n\n    // Appearance\n    opacity: number,\n    filter: string,\n    mixBlendMode: string,\n\n    // Shadows\n    boxShadowX: string,\n    boxShadowY: string,\n    boxShadowBlur: string,\n    boxShadowColor: string,\n    dropShadowX: string,\n    dropShadowY: string,\n    dropShadowBlur: string,\n    dropShadowColor: string\n  },\n  parameterType: \"fill\"\n}\n```\n\n**Gradient fills:**\n\n```javascript\nfill: \"linear-gradient(180deg, rgba(0,0,0,0.7) 0%, transparent 100%)\";\nfill: \"radial-gradient(circle at center, #FF6B6B 0%, #4ECDC4 100%)\";\n```\n\n### Video Element\n\n**Content Types:**\n\n- **URL** (required): `\"https://example.com/video.mp4\"` - Must be a publicly accessible URL\n- Supported formats: MP4, WebM, MOV\n- For best results, use MP4 with H.264 codec\n\n```javascript\n{\n  type: \"video\",\n  content: string,          // Video URL (must be publicly accessible)\n  videoOptions: {\n    loop: boolean,\n    muted: boolean,\n    trim_start_time: string,\n    trim_end_time: string,\n    duration: number | null\n  },\n  style: {\n    // Sizing & Positioning\n    objectFit: string,      // \"contain\", \"cover\", \"fill\"\n    objectPosition: string,\n\n    // Appearance\n    opacity: number,\n    filter: string,\n    mixBlendMode: string,\n\n    // Borders & Shadows\n    borderRadius: string,\n    borderWidth: string,\n    borderColor: string,\n    elementBoxShadowX: string,\n    elementBoxShadowY: string,\n    elementBoxShadowBlur: string,\n    elementBoxShadowColor: string\n  },\n  parameterType: \"videoUrl\"\n}\n```\n\n### Parameterization Best Practices\n\nWhen creating or updating templates, **always ensure all text, image, and video elements are parameterizable** with unique IDs. This enables dynamic content replacement via the API.\n\n#### Required Setup\n\nEvery dynamic element MUST have:\n\n```javascript\n{\n  parameterizable: true,\n  parameterId: \"unique_id\",  // Unique across template, lowercase with underscores\n  parameterType: \"text\" | \"imageUrl\" | \"videoUrl\"\n}\n```\n\n#### Naming Conventions\n\n| Element Type | parameterId Examples                        | parameterType |\n| ------------ | ------------------------------------------- | ------------- |\n| Text         | `headline`, `subtitle`, `cta_text`, `price` | `\"text\"`      |\n| Image        | `product_image`, `logo`, `background_image` | `\"imageUrl\"`  |\n| Video        | `hero_video`, `background_video`            | `\"videoUrl\"`  |\n\n#### Best Practices\n\n1. **Use descriptive IDs:** `product_title` not `text1`\n2. **Be consistent:** Use snake_case across all templates\n3. **Unique per template:** No duplicate parameterIds on same page\n4. **Group logically:** Related elements share naming prefix (e.g., `card_title`, `card_image`)\n\n#### Validation Checklist\n\nBefore finalizing any template update:\n- [ ] All text elements have `parameterizable: true` and unique `parameterId`\n- [ ] All image elements have `parameterizable: true` and unique `parameterId`\n- [ ] All video elements have `parameterizable: true` and unique `parameterId`\n- [ ] No duplicate parameterIds exist on the same page\n- [ ] parameterIds are descriptive and follow snake_case convention\n\n### Design Best Practices\n\n#### Typography Guidelines\n- **Font limit:** Use 2-3 fonts maximum per template\n- **Hierarchy:** Headings should be 1.5-2x larger than body text\n- **Minimum size:** 24px for social media readability\n- **Weights:** Headings 600-900 (bold), Body 400-500 (regular)\n\n**Popular font pairings:**\n- `Prata` + `Inter`\n- `Instrument Serif` + `DM Sans`\n- `Playfair Display` + `Lato`\n- `Montserrat` + `Open Sans`\n\n**Platform-specific fonts:**\n- iOS: `SF Pro Display`, `SF Pro Text`\n- Android: `Google Sans`, `Roboto`\n\n#### Color Guidelines\n**Luxury/Gold palette:**\n- Gold: `#D4AF37`\n- Dark gold: `#B8860B`\n- Light gold: `#F5E7A3`\n\n**iOS system colors:**\n- Blue: `#007AFF`\n- Gray: `#8E8E93`\n- Background: `#F5F5F7`\n\n**Professional dark:**\n- Navy: `#0F172A`\n- Slate: `#1E293B`, `#334155`\n- Muted: `#64748B`, `#94A3B8`\n\n**Best practices:**\n- Ensure 4.5:1 minimum contrast for text readability\n- Use gradients sparingly for premium effects\n- Consistent color palette (3-5 colors max)\n\n#### Layout Guidelines\n- **Edge padding:** 40-60px from canvas edges\n- **Element spacing:** 20-40px between elements\n- **Alignment:** Center for formal, left for modern\n- **Visual flow:** Guide eye with size, color, position\n\n**zIndex ordering:**\n- Background images/colors: 1\n- Overlay shapes: 2-3\n- Text elements: 4-6\n- Interactive elements: 7+\n\n#### Common Operations (Design Automation)\n\n**Add text element:**\n```javascript\naddElement(\"text\", {\n  content: \"Hello World\",\n  fontFamily: \"Inter\",\n  fontSize: 48,\n  fontWeight: \"700\",\n  color: \"#FFFFFF\",\n  x: 100,\n  y: 100,\n  width: 400,\n  height: 60,\n});\n```\n\n**Add shape backdrop:**\n```javascript\naddElement(\"rectangle\", {\n  fill: \"rgba(0,0,0,0.5)\",\n  width: 1080,\n  height: 200,\n  x: 0,\n  y: 800,\n  borderRadius: \"0px\",\n});\n```\n\n**Batch update multiple elements:**\n```javascript\nbatchUpdate([\n  {\n    elementId: \"heading\",\n    type: \"ORSHOT_UPDATE_ELEMENT\",\n    updates: { style: { fontSize: 72 } },\n  },\n  {\n    elementId: \"subtitle\",\n    type: \"ORSHOT_UPDATE_ELEMENT\",\n    updates: { style: { color: \"#94A3B8\" } },\n  },\n  { type: \"ORSHOT_UPDATE_CANVAS\", updates: { backgroundColor: \"#0F172A\" } },\n]);\n```\n\n## API Reference\n\n### 1. Render from Studio Template\n\nGenerate images/PDFs/videos from templates designed in Orshot Studio.\n\n**POST** `https://api.orshot.com/v1/studio/render`\n\n```js\nawait fetch(\"https://api.orshot.com/v1/studio/render\", {\n  method: \"POST\",\n  headers: {\n    \"Content-Type\": \"application/json\",\n    Authorization: \"Bearer <ORSHOT_API_KEY>\",\n  },\n  body: JSON.stringify({\n    templateId: 123, // Integer - your studio template ID\n    modifications: {\n      title: \"Hello World\",\n      imageUrl: \"https://example.com/photo.jpg\",\n      canvasBackgroundColor: \"#eff2fa\",\n    },\n    response: {\n      type: \"base64\", // \"base64\" | \"url\" | \"binary\"\n      format: \"png\", // \"png\" | \"webp\" | \"jpg\" | \"pdf\" | \"mp4\" | \"webm\" | \"gif\"\n      scale: 1, // 1 = original size, 2 = double\n      includePages: [1, 3], // optional – only for multi-page templates\n      fileName: \"my-render\", // optional – custom filename (without extension)\n    },\n    pdfOptions: {\n      // optional – only when format is \"pdf\"\n      margin: \"20px\",\n      rangeFrom: 1,\n      rangeTo: 2,\n      colorMode: \"rgb\", // \"rgb\" or \"cmyk\"\n      dpi: 300,\n    },\n  }),\n});\n```\n\n**Response (single page):**\n```json\n{\n  \"data\": {\n    \"content\": \"data:image/png;base64,iVBORw0.....\",\n    \"format\": \"png\",\n    \"type\": \"base64\",\n    \"responseTime\": 325.22\n  }\n}\n```\n\n**Response (multi-page/carousel):**\n```json\n{\n  \"data\": [\n    { \"page\": 1, \"content\": \"https://storage.orshot.com/.../image1.png\" },\n    { \"page\": 2, \"content\": \"https://storage.orshot.com/.../image2.png\" }\n  ],\n  \"format\": \"png\",\n  \"type\": \"url\",\n  \"responseTime\": 3166.01,\n  \"totalPages\": 2,\n  \"renderedPages\": 2\n}\n```\n\n### 2. Render from Utility Template\n\nGenerate renders from Orshot's pre-built utility templates (e.g., website screenshots, tweet images).\n\n**POST** `https://api.orshot.com/v1/generate/{renderType}`\n\n- `renderType`: `images` or `pdfs`\n\n```js\nawait fetch(\"https://api.orshot.com/v1/generate/images\", {\n  method: \"POST\",\n  headers: {\n    \"Content-Type\": \"application/json\",\n    Authorization: \"Bearer <ORSHOT_API_KEY>\",\n  },\n  body: JSON.stringify({\n    templateId: \"website-screenshot\", // String ID for utility templates\n    response: {\n      format: \"png\",\n      type: \"base64\",\n    },\n    modifications: {\n      websiteUrl: \"https://example.com\",\n      fullCapture: false,\n      delay: 500,\n      width: 1200,\n      height: 1000,\n    },\n  }),\n});\n```\n\n### 3. Generate Signed URL\n\nCreate publicly accessible render URLs without exposing your API key.\n\n**POST** `https://api.orshot.com/v1/signed-url/create`\n\n```js\nawait fetch(\"https://api.orshot.com/v1/signed-url/create\", {\n  method: \"POST\",\n  headers: {\n    Authorization: \"Bearer <ORSHOT_API_KEY>\",\n    \"Content-Type\": \"application/json\",\n  },\n  body: JSON.stringify({\n    templateId: \"website-screenshot\",\n    expiresAt: 1744550160505, // UNIX timestamp, or null for no expiry\n    renderType: \"images\",\n    modifications: {\n      websiteUrl: \"https://example.com\",\n    },\n  }),\n});\n```\n\n### 4. List Studio Templates\n\n**GET** `https://api.orshot.com/v1/studio/templates/all?page=1&limit=10`\n\nResponse includes `data` array of templates and `pagination` object with `page`, `limit`, `total`, `totalPages`.\n\n### 5. Get Studio Template\n\n**GET** `https://api.orshot.com/v1/studio/templates/:templateId`\n\nReturns the template metadata including available `modifications`.\n\n### 6. Delete Studio Template\n\n**DELETE** `https://api.orshot.com/v1/studio/templates/:templateId`\n\n### 7. Duplicate Studio Template\n\n**POST** `https://api.orshot.com/v1/studio/templates/:templateId/duplicate`\n\n### 8. Get Profile & Workspaces\n\n**GET** `https://api.orshot.com/v1/me`\n\nReturns your profile and the workspaces your API key or OAuth token can access, including plan info and credit usage.\n\n### 9. Brand Assets API\n\nBrand assets are grouped by type — **images**, **colors**, **fonts**, **videos**, **audio** — each with its own routes under `/v1/brand-assets/{type}/…`.\n\n#### Images\n- **Get:** `GET https://api.orshot.com/v1/brand-assets/images/get`\n- **Upload:** `POST https://api.orshot.com/v1/brand-assets/images/add` — `multipart/form-data` with a `file` field\n- **Update tags:** `PATCH https://api.orshot.com/v1/brand-assets/images/update/:id`\n- **Delete:** `DELETE https://api.orshot.com/v1/brand-assets/images/delete/:id`\n\n#### Colors\n- **Get:** `GET https://api.orshot.com/v1/brand-assets/colors/get`\n- **Add:** `POST https://api.orshot.com/v1/brand-assets/colors/add` — body `{ type: \"hex\" | \"gradient\", value, tags? }`\n- **Update tags:** `PATCH https://api.orshot.com/v1/brand-assets/colors/update/:id`\n- **Delete:** `DELETE https://api.orshot.com/v1/brand-assets/colors/delete/:id`\n\n#### Fonts / Videos / Audio\nSame shape — swap the type segment (`fonts`, `videos`, `audio`):\n- **Get:** `GET /v1/brand-assets/{type}/get`\n- **Upload:** `POST /v1/brand-assets/{type}/add` (`multipart/form-data`, `file` field)\n- **Update tags:** `PATCH /v1/brand-assets/{type}/update/:id`\n- **Delete:** `DELETE /v1/brand-assets/{type}/delete/:id`\n\n#### Search\n- **Search assets:** `GET https://api.orshot.com/v1/brand-assets/search`\n\nUse an uploaded asset in a render by passing its hosted URL as an image/video modification value.\n\n### 10. Enterprise API Endpoints\n\nThese endpoints require an Enterprise plan.\n\n#### Create Studio Template\n**POST** `https://api.orshot.com/v1/studio/templates/create`\n\n```js\n{\n  name: \"Product Banner\",        // required, max 255 chars\n  description: \"Banner template\", // optional\n  canvas_width: 1200,             // required, 1-5000\n  canvas_height: 628,             // required, 1-5000\n  pages_data: [...]               // optional - array of page objects with elements\n}\n```\n\n#### Bulk Create Studio Templates\n**POST** `https://api.orshot.com/v1/studio/templates/bulk-create`\nCreate multiple templates at once via CSV or JSON.\n\n#### Update Template\n**PATCH** `https://api.orshot.com/v1/studio/templates/:templateId`\nUpdate template name and description.\n\n#### Update Template Modifications\n**PATCH** `https://api.orshot.com/v1/studio/templates/:templateId/update-modifications`\nUpdate text and image content in template layers.\n\n#### Generate Template Variants\n**POST** `https://api.orshot.com/v1/studio/templates/:templateId/generate-variants`\nGenerate multiple size variants of a template using AI.\n\n### 11. Dynamic URLs\n\nGenerate images directly from URL parameters:\n\n```\nhttps://api.orshot.com/v1/studio/dynamic-url/my-image?title=Hello%20World&title.fontSize=48px&title.color=%23ff0000\n```\nURL-encode special characters (e.g., `#` → `%23`).\n\n### 12. Template Folders & Sharing\n\nOrganize studio templates into folders and share them:\n\n- **List folders:** `GET https://api.orshot.com/v1/studio/folders`\n- **Create folder:** `POST https://api.orshot.com/v1/studio/folders`\n- **Update folder:** `PATCH https://api.orshot.com/v1/studio/folders/:folderId`\n- **Delete folder:** `DELETE https://api.orshot.com/v1/studio/folders/:folderId`\n- **Move template into folder:** `PATCH https://api.orshot.com/v1/studio/templates/:templateId/folder`\n- **Get template sharing:** `GET https://api.orshot.com/v1/studio/templates/:templateId/share`\n- **Update template sharing:** `POST https://api.orshot.com/v1/studio/templates/:templateId/share`\n\n### 13. Workflows API\n\nWorkflows automate multi-step pipelines (trigger → fetch data → render → publish/deliver). Available to first-party clients (like the Orshot MCP server) and Enterprise workspaces.\n\n- **List workflows:** `GET https://api.orshot.com/v1/workflows`\n- **Create workflow:** `POST https://api.orshot.com/v1/workflows`\n- **Get workflow:** `GET https://api.orshot.com/v1/workflows/:id`\n- **Update workflow:** `PATCH https://api.orshot.com/v1/workflows/:id`\n- **Delete workflow:** `DELETE https://api.orshot.com/v1/workflows/:id`\n- **Run workflow:** `POST https://api.orshot.com/v1/workflows/:id/run`\n- **List runs:** `GET https://api.orshot.com/v1/workflows/:id/runs`\n- **Get a run:** `GET https://api.orshot.com/v1/workflows/:id/runs/:runId`\n- **Validate a workflow:** `POST https://api.orshot.com/v1/workflows/validate`\n- **List available nodes:** `GET https://api.orshot.com/v1/workflows/nodes`\n- **Get/update sharing:** `GET` / `POST https://api.orshot.com/v1/workflows/:id/share`\n\n## Render Configuration\n\n### Dynamic Parameters\n\nOverride template styles, content, and behavior at render time using dot notation.\n\n#### Style Parameters\n\nFormat: `parameterId.property`\n\n```json\n{\n  \"modifications\": {\n    \"title\": \"Hello World\",\n    \"title.fontSize\": \"48px\",\n    \"title.color\": \"#ff0000\",\n    \"title.fontFamily\": \"Roboto\",\n    \"title.textAlign\": \"center\",\n    \"logo.borderRadius\": \"50%\",\n    \"logo.objectFit\": \"cover\"\n  }\n}\n```\n\n**Text properties:** `fontSize`, `fontWeight`, `fontStyle`, `fontFamily`, `lineHeight`, `letterSpacing`, `textAlign`, `verticalAlign`, `textDecoration`, `textTransform`, `color`, `backgroundColor`, `backgroundRadius`, `textStrokeWidth`, `textStrokeColor`, `opacity`, `filter`, `dropShadowX/Y/Blur/Color`\n\n**Image properties:** `objectFit`, `objectPosition`, `borderRadius`, `borderWidth`, `borderColor`, `boxShadowX/Y/Blur/Color`, `opacity`, `filter`\n\n**Shape properties:** `fill`, `stroke`, `strokeWidth`, `borderRadius`, `opacity`\n\n**Position/Size (all elements):** `x`, `y`, `width`, `height`\n\nProperty names are **case-insensitive**.\n\n#### Multi-Page Templates\n\nPrefix modifications with page number:\n```json\n{\n  \"modifications\": {\n    \"page1@title\": \"Page 1 Title\",\n    \"page2@title\": \"Page 2 Title\",\n    \"page1@title.fontSize\": \"48px\"\n  }\n}\n```\n\n#### AI Content Generation (.prompt)\n\nGenerate text or images using AI:\n```json\n{\n  \"modifications\": {\n    \"headline.prompt\": \"Write a catchy headline about coffee\",\n    \"background.prompt\": \"A serene mountain landscape at sunset\"\n  }\n}\n```\n- `.prompt` on a text element generates copy; on an image element it generates imagery. The underlying AI models are managed by Orshot and may change over time — don't hardcode a specific model. AI modifications consume AI Credits.\n\n#### Interactive Links (.href)\n\nAdd clickable links in PDF outputs:\n```json\n{\n  \"modifications\": {\n    \"cta_button.href\": \"https://example.com/signup\",\n    \"logo.href\": \"https://company.com\"\n  },\n  \"response\": { \"format\": \"pdf\" }\n}\n```\n\n#### Video Parameters\n\nControl video elements dynamically:\n```json\n{\n  \"modifications\": {\n    \"bgVideo\": \"https://example.com/video.mp4\",\n    \"bgVideo.trimStart\": 5,\n    \"bgVideo.trimEnd\": 15,\n    \"bgVideo.muted\": false,\n    \"bgVideo.loop\": true\n  },\n  \"response\": { \"format\": \"mp4\" }\n}\n```\n\n### Video Render Example\n\nRender templates with video elements as MP4, WebM, or GIF:\n\n```js\nawait fetch(\"https://api.orshot.com/v1/studio/render\", {\n  method: \"POST\",\n  headers: {\n    \"Content-Type\": \"application/json\",\n    Authorization: \"Bearer <ORSHOT_API_KEY>\",\n  },\n  body: JSON.stringify({\n    templateId: 123,\n    modifications: {\n      videoElement: \"https://example.com/custom-video.mp4\",\n      \"videoElement.trimStart\": 0,\n      \"videoElement.trimEnd\": 10,\n      \"videoElement.muted\": false,\n      \"videoElement.loop\": true,\n    },\n    videoOptions: {\n      trimStart: 0,\n      trimEnd: 20,\n      muted: true,\n      loop: true,\n    },\n    response: {\n      type: \"url\",\n      format: \"mp4\",\n    },\n  }),\n});\n```\n\n### Smart Resize (one design → any size)\n\nRender a template at a **different canvas size without redesigning it** — the layout is deterministically re-solved to fit (elements re-anchor, backgrounds stretch, groups move together). Set these on the `response` object:\n\n- **`size`** — *replace* the render size. A preset slug (`\"instagram-story\"`, `\"og-image\"`, `\"youtube-thumbnail\"`, …), a `\"WIDTHxHEIGHT\"` string (`\"1080x1920\"`), or use `width` + `height` (10–5000px each).\n- **`extraSizes`** — *add* the same design at extra sizes in one call. An array (`[\"1080x1920\", \"1080x1080\"]`) or a named object (`{ story: \"1080x1920\", square: \"1080x1080\" }`). Each output gains a nested `extraSizes` array of `{ size, width, height, content }`.\n\n```json\n{\n  \"templateId\": 123,\n  \"modifications\": { \"title\": \"Launch Day\" },\n  \"response\": {\n    \"type\": \"url\",\n    \"format\": \"png\",\n    \"size\": \"1200x630\",\n    \"extraSizes\": [\"1080x1920\", \"1080x1080\"]\n  }\n}\n```\n\nImage formats only (`png`/`jpg`/`webp`/`avif`), up to 50 extra outputs per call. Each extra output is billed like a page (1 credit). Saved sizes from the Studio Smart Resize panel reproduce their approved preview exactly.\n\n### Response Types\n\n| Type     | Description                                     |\n| -------- | ----------------------------------------------- |\n| `url`    | Returns a hosted URL to the rendered file       |\n| `base64` | Returns base64-encoded content as a string      |\n| `binary` | Returns binary file content for custom handling |\n\n### Response Formats\n\n| Format | Type  | Notes                                      |\n| ------ | ----- | ------------------------------------------ |\n| `png`  | Image | Best quality, larger size                  |\n| `webp` | Image | Smaller size, good quality                 |\n| `jpg`  | Image | Compressed, no transparency                |\n| `avif` | Image | Smallest size, modern browsers             |\n| `pdf`  | Doc   | Supports multi-page, clickable links, CMYK |\n| `mp4`  | Video | H.264, requires video elements in template |\n| `webm` | Video | VP9, web-optimized                         |\n| `mov`  | Video | QuickTime container                        |\n| `mkv`  | Video | Matroska container                         |\n| `gif`  | Video | Animated, no audio support                 |\n\n### Render Usage & Costs\n\nUsage is measured in **credits**. 1 credit = 1 image, 1 PDF page, or 1 second of video.\n\n| Output               | Cost                     |\n| -------------------- | ------------------------ |\n| Image (PNG/JPG/WebP/AVIF) | 1 credit per image  |\n| PDF                  | 1 credit per page        |\n| Video (MP4/WebM/MOV/MKV/GIF) | 1 credit per second |\n\nMulti-page templates and Smart Resize extra sizes: each output page/size counts as its own credit. AI modifications (`.prompt`) additionally consume AI Credits.\n\n## Common Recipes\n\nEnd-to-end patterns for the jobs people most often automate with Orshot.\n\n### OG image on every deploy\nRender a studio template to a stable hosted URL and drop it into your `<meta>` tags.\n```js\nconst { data } = await fetch(\"https://api.orshot.com/v1/studio/render\", {\n  method: \"POST\",\n  headers: { \"Content-Type\": \"application/json\", Authorization: \"Bearer <KEY>\" },\n  body: JSON.stringify({\n    templateId: 123,\n    modifications: { title: post.title, author: post.author },\n    response: { type: \"url\", format: \"png\", size: \"og-image\" },\n  }),\n}).then((r) => r.json());\n// <meta property=\"og:image\" content={data.content} />\n```\n\n### Bulk-generate from a CSV (certificates, badges, invoices)\nLoop rows and render one PDF each. Describe image layers with `.alt` for accessible PDFs.\n```js\nfor (const row of rows) {\n  await fetch(\"https://api.orshot.com/v1/studio/render\", {\n    method: \"POST\",\n    headers: { \"Content-Type\": \"application/json\", Authorization: \"Bearer <KEY>\" },\n    body: JSON.stringify({\n      templateId: 456,\n      modifications: { name: row.name, course: row.course, date: row.date },\n      response: { type: \"url\", format: \"pdf\" },\n      pdfOptions: { title: `${row.name} — Certificate` },\n    }),\n  });\n}\n```\n\n### Render and auto-post to social in one call\nAdd a `publish` object (see Social Publishing) — no second request.\n```js\nbody: JSON.stringify({\n  templateId: 123,\n  modifications: { title: \"Launch day!\" },\n  response: { type: \"url\", format: \"png\" },\n  publish: { accounts: [1, 2], content: \"We just shipped 🚀\" },\n});\n```\n\n### One design, every social size (Smart Resize)\n```js\nresponse: { type: \"url\", format: \"png\", extraSizes: [\"1080x1920\", \"1080x1080\", \"1200x630\"] }\n// each output gains a nested extraSizes[] of { size, width, height, content }\n```\n\n### Automate a recurring pipeline (Workflows)\nCreate a workflow (trigger → fetch data → render → publish/deliver) with `POST /v1/workflows`, then trigger it with `POST /v1/workflows/:id/run`. Inspect results via `GET /v1/workflows/:id/runs`. Best driven through the MCP server or the Workflows API on Enterprise/first-party clients.\n\nSee **Setting Up Recurring Automation** below for the full playbook, including how\nto wire it into an n8n/Make/Zapier setup the user already runs.\n\n## Setting Up Recurring Automation\n\nA one-off render is a demo. The value shows up when it runs unattended. Pick the\nroad that matches where the user ALREADY works, not the one easiest to describe.\n\n### Choose the road first\n\nCheck what they already run before pitching anything. `GET /v1/workspace/logs?limit=100`\nreturns a `source` on every row, which answers it factually:\n\n| `source` in their logs | They already run | Lead with |\n| --- | --- | --- |\n| `n8n-integration` | n8n | Add a node to the n8n workflow they have |\n| `orshot-make` | Make | Add a module to their scenario |\n| `zapier-integration` | Zapier | Add an action to their Zap |\n| `orshot-pipedream` | Pipedream | Add a step |\n| `orshot-*-sdk`, `api`, `cli` | Their own code | Write the call into their repo |\n| only `playground` / `orshot-mcp-server` | Nothing yet | Offer Orshot Workflows |\n\nNever pitch a second orchestrator to someone who already has one. A user with a\nlive n8n setup wants this template added to it, not a new tool to learn.\n\nIf you have browser control or repo access, offer to DO the setup rather than\ndescribe it. Whichever road you take, **run it once and show the resulting image\nURL** before calling it done.\n\n### Road A — Orshot Workflows (no orchestrator yet)\n\nOrshot runs the whole loop: trigger, render, deliver. Over MCP:\n\n1. `orshot_suggest_workflows` with the `templateId` — returns automations that\n   fit this template, with draft-ready steps\n2. `orshot_list_workflow_nodes` — use the EXACT node keys it returns. Invented\n   keys (e.g. `render_studio_template`, `slack_send`) fail as `unknown node`;\n   the real keys are `render`, `slack`, and so on\n3. `orshot_list_connected_integrations` — see what is already connected\n4. `orshot_get_workflow_connection_data` — resolve \"my content sheet\" into a\n   concrete id, and confirm the name back to the user\n5. `orshot_validate_workflow` — read `errors`, `warnings` AND `gated`. A `gated`\n   entry means a plan block, not a config problem: the draft still saves, only\n   activation is blocked, so tell the user which plan unlocks it instead of\n   editing steps\n6. `orshot_create_workflow` with `status: \"draft\"`, then share the edit link from\n   the result — it opens pre-configured in their dashboard\n7. `orshot_run_workflow` — prove it works, then they activate\n\n**Scaffolding shapes.** If they have no data source ready, use\n`webhook` → `webhook_body` → `render` → `orshot_url`. It is the only\ntrigger→source pair needing no OAuth AND it validates clean with empty configs,\nso it is runnable immediately and they can POST to it from cron, their app, n8n,\nor Make. A bare `schedule` → `render` is NOT runnable — it fails with \"no data\nsource\"; a schedule needs a source step after it.\n\n### Road B — their existing automation platform\n\nEvery platform needs the same three things: endpoint, auth header, modification\nkeys. Get the exact keys first — never guess them:\n\n```\norshot_get_studio_template_modifications   (or GET /v1/studio/templates/:id/modifications)\n```\n\nThen the call to drop in:\n\n```http\nPOST https://api.orshot.com/v1/studio/render\nAuthorization: Bearer <ORSHOT_API_KEY>\nContent-Type: application/json\n\n{\n  \"templateId\": 1234,\n  \"modifications\": { \"headline\": \"{{ row.title }}\", \"hero_image\": \"{{ row.image_url }}\" },\n  \"response\": { \"type\": \"url\", \"format\": \"png\" }\n}\n```\n\nThe response contains the rendered URL — map it to whatever the next step needs.\n\n- **n8n** — HTTP Request node, POST, header auth, body as above. A dedicated\n  Orshot node also exists in the n8n library.\n- **Make** — HTTP \"Make a request\" module, or the Orshot app modules.\n- **Zapier** — Webhooks by Zapier → POST, or the Orshot Zapier app.\n- **Pipedream** — HTTP step; Orshot components are published.\n- **Own code / cron** — any HTTP client. Use `response.type: \"url\"` in production\n  so you are not moving base64 around, and keep the API key in an environment\n  variable, never inline.\n\nRecurring triggers that work well: a new spreadsheet/Airtable/Notion row, a\nschedule, an inbound webhook, or a CMS publish event.\n\n### Verify before you call it done\n\n1. Trigger one real run\n2. `orshot_list_workspace_logs` (or `GET /v1/workspace/logs`) — the new render\n   appears with the `source` of whatever fired it, proving which system made it\n3. Show the user the image URL from that run\n\nIf the render 403s, read the body: an expired key, an inactive subscription, and\na plan limit all return 403 with different messages. A plan block is not an auth\nproblem — do not retry it as one.\n\n## Integrations & Helps\n\n### Integrations Service Support\nOrshot connects with:\n- **No-code:** Zapier, Make (Integromat), n8n, Pipedream, Airtable\n- **Storage:** Amazon S3, Cloudflare R2, Google Drive, Dropbox\n- **Notifications:** Slack, Webhooks\n- **Design:** Figma Plugin, Canva Import, Polotno Import\n- **CLI:** `npx orshot-cli` for terminal-based generation\n- **MCP Server:** Use with Claude, Cursor, Windsurf via MCP protocol\n- **Embed:** White-label design editor for your app (React SDK, Vue SDK, iframe)\n\n### Common Error Codes\n\n| Code | Error                          | Fix                                          |\n| ---- | ------------------------------ | -------------------------------------------- |\n| 400  | `templateId missing`           | Add `templateId` to request body             |\n| 400  | `Invalid API Key`              | Generate new key from dashboard              |\n| 403  | `Authorization header missing` | Add `Authorization: Bearer <KEY>` header     |\n| 403  | `Subscription inactive`        | Check usage or upgrade plan                  |\n| 403  | `Template not found`           | Verify template ID belongs to your workspace |\n| 403  | `Video on free plan`           | Upgrade to paid plan for video generation    |\n\n### General Best Practices\n\n1. **Use `url` response type** for production – avoids large base64 payloads\n2. **Use `webp` format** for smaller file sizes with good quality\n3. **Test in Playground** before coding – each template has an interactive playground\n4. **Use style parameters** instead of creating multiple template variants\n5. **Use consistent units** – stick to `px` for sizes\n6. **Multi-page prefix** – always use `page1@paramId` format for carousel templates\n7. **Cache renders** – use `?cache=false` query param to bypass cache when needed\n8. **Handle errors** – check for 403/400 status codes and parse error messages\n\n## Social Publishing\n\nPublish rendered images/videos directly to 13+ social platforms via API. Supports Twitter/X, Instagram, LinkedIn, Pinterest, Facebook, TikTok, YouTube, Threads, Bluesky, Reddit, Telegram, Snapchat, and Google Business.\n\nFor detailed setup: fetch `https://orshot.com/docs/publish/introduction.md`\n\n### Connect Accounts\n\nConnect social accounts in **Workspace Settings → Social Accounts** in the Orshot dashboard. Each connected account gets a numeric ID used in API calls.\n\n### Publish from API\n\nAdd the `publish` object to any render request:\n\n```js\nawait fetch(\"https://api.orshot.com/v1/studio/render\", {\n  method: \"POST\",\n  headers: {\n    \"Content-Type\": \"application/json\",\n    Authorization: \"Bearer <ORSHOT_API_KEY>\",\n  },\n  body: JSON.stringify({\n    templateId: 123,\n    modifications: { title: \"New Post\" },\n    response: { type: \"url\", format: \"png\" },\n    publish: {\n      accounts: [1, 3],          // Social account IDs\n      content: \"Check this out!\", // Caption text (max 5000 chars)\n      isDraft: false,             // true = save as draft\n      schedule: {                 // Optional: schedule for later\n        scheduledFor: \"2026-04-25T14:00:00Z\"\n      },\n      timezone: \"America/New_York\",\n      platformOptions: {          // Per-account overrides (keyed by account ID)\n        \"1\": { firstComment: \"https://example.com\" },     // LinkedIn\n        \"3\": { title: \"Pin Title\", link: \"https://...\" }   // Pinterest\n      }\n    }\n  }),\n});\n```\n\n**Response includes publish status:**\n```json\n{\n  \"data\": { \"content\": \"https://storage.orshot.com/...\" },\n  \"publish\": [\n    { \"platform\": \"twitter\", \"username\": \"acme\", \"status\": \"published\", \"url\": \"https://x.com/...\" },\n    { \"platform\": \"linkedin\", \"username\": \"acme\", \"status\": \"scheduled\" }\n  ]\n}\n```\n\n**Format compatibility:**\n- PDF cannot be published to any platform\n- Video (mp4/webm/gif) cannot be published to Google Business\n- Image (png/jpg/webp) cannot be published to TikTok or YouTube (video-only)\n\n## Developer Apps & OAuth\n\nBuild third-party integrations with Orshot using OAuth 2.0. Register apps, authenticate users, and access their workspaces programmatically.\n\nFor detailed setup: fetch `https://orshot.com/docs/developers/oauth-overview.md`\n\n### Register an App\n\n1. Go to **Workspace Settings → Developer Apps** in Orshot dashboard\n2. Create a new app with name, redirect URI, and required scopes\n3. Receive `client_id` and `client_secret`\n\n### OAuth 2.0 Flows\n\n**Authorization Code Flow** (web apps):\n```\nGET https://orshot.com/oauth/authorize?\n  response_type=code&\n  client_id=YOUR_CLIENT_ID&\n  redirect_uri=https://yourapp.com/callback&\n  scope=workspace:templates:read workspace:templates:write render:generate\n```\n\n**Device Flow** (CLI/headless):\n```\nPOST https://orshot.com/oauth/device/code\n  client_id=YOUR_CLIENT_ID&\n  scope=workspace:templates:read render:generate\n```\n\n### Token Exchange\n\n```\nPOST https://orshot.com/oauth/token\n  grant_type=authorization_code&\n  code=AUTH_CODE&\n  client_id=YOUR_CLIENT_ID&\n  client_secret=YOUR_CLIENT_SECRET&\n  redirect_uri=https://yourapp.com/callback\n```\n\n### Available Scopes\n\n| Scope | Description |\n|-------|-------------|\n| `workspace:read` | List and read workspace details |\n| `workspace:templates:read` | List and read templates |\n| `workspace:templates:write` | Update template modifications via API |\n| `render:generate` | Generate images, PDFs, and videos |\n| `mcp:access` | Access via Model Context Protocol |\n| `offline_access` | Long-lived refresh tokens |\n\nFor endpoint details: fetch `https://orshot.com/docs/developers/oauth-endpoints.md`\n\n## White-Label Embed (Orshot Embed)\n\nEmbed Orshot's template editor into your application as a white-label component. Users can design and customize templates directly in your app.\n\nFor detailed setup: fetch `https://orshot.com/docs/orshot-embed/introduction.md`\n\n### Quick Setup (iframe)\n\n```html\n<iframe\n  src=\"https://orshot.com/embeds/YOUR_EMBED_ID?userId=USER_123\"\n  width=\"100%\"\n  height=\"600\"\n  frameborder=\"0\"\n></iframe>\n```\n\n### React SDK\n\n```bash\nnpm install @orshot/react\n```\n\n```jsx\nimport { OrshotEmbed } from \"@orshot/react\";\n\n<OrshotEmbed\n  embedId=\"YOUR_EMBED_ID\"\n  userId=\"user_123\"\n  token=\"JWT_TOKEN\"\n  onRender={(data) => console.log(\"Rendered:\", data)}\n  onSave={(data) => console.log(\"Saved:\", data)}\n/>\n```\n\n### Vue SDK\n\n```bash\nnpm install @orshot/vue\n```\n\n```vue\n<template>\n  <OrshotEmbed\n    embed-id=\"YOUR_EMBED_ID\"\n    user-id=\"user_123\"\n    @render=\"onRender\"\n    @save=\"onSave\"\n  />\n</template>\n```\n\n### Key Features\n\n- **Per-user templates**: Pass `userId` to give each user their own template copies\n- **JWT authentication**: Secure embed access with domain whitelist validation\n- **Webhooks**: Get notified on render completion, template save, etc.\n- **Custom buttons**: Add your own action buttons to the editor toolbar\n- **PostMessage API**: Control the embed programmatically from your app\n\nFor React SDK details: fetch `https://orshot.com/docs/orshot-embed/react-sdk.md`\nFor Vue SDK details: fetch `https://orshot.com/docs/orshot-embed/vue-sdk.md`\nFor webhook setup: fetch `https://orshot.com/docs/orshot-embed/webhooks.md`\n\n## Migrating from Other Platforms\n\nThis section maps authentication, endpoints, and request formats for migrating from competing platforms. For detailed comparison articles, fetch the corresponding blog post URL.\n\n### Migration Playbook (any platform)\n\nThe API-call swap is the easy part (mapped per-platform below). The real work is recreating the design, because you can't import another platform's template JSON:\n\n1. **Recreate the template in Orshot Studio** (or via `POST /v1/studio/templates/create`). Rebuild the layout, then mark every dynamic layer parameterizable with a clear `parameterId`.\n2. **Map old field names → Orshot `parameterId`s.** Other platforms key by layer name, `image_url`, `payload`, etc.; Orshot keys by `parameterId`. Keep a lookup from old names to new ones.\n3. **Swap the API call** using the tables below — almost always `Authorization: Bearer` + `POST /v1/studio/render`.\n4. **Handle sync vs async.** Most platforms are async (submit → poll/webhook); Orshot renders **synchronously**, so delete the polling/webhook code and read `data.content` from the response.\n5. **Verify the response shape** — single page is an object, multi-page is an array (Common Gotchas #7) — before cutting traffic over.\n\n### Migrating from BannerBear\n\nBlog: `https://orshot.com/blog/bannerbear-api-alternative.md`\n\n**Authentication:**\n| BannerBear | Orshot |\n|------------|--------|\n| `Authorization: Bearer BB_API_KEY` | `Authorization: Bearer ORSHOT_API_KEY` |\n\n**Endpoint Mapping:**\n| Action | BannerBear | Orshot |\n|--------|-----------|--------|\n| Generate image | `POST /v2/images` (async, returns 202) | `POST /v1/studio/render` (sync) |\n| Generate image (sync) | `POST sync.api.bannerbear.com/v2/images` (10s timeout) | `POST /v1/studio/render` (sync by default) |\n| List templates | `GET /v2/templates` | `GET /v1/studio/templates/all?page=1&limit=10` |\n| Get template | `GET /v2/templates/:uid` | `GET /v1/studio/templates/:id` |\n| Generate video | `POST /v2/videos` | `POST /v1/studio/render` with `format: \"mp4\"` |\n| Multi-template batch | `POST /v2/collections` | Loop `POST /v1/studio/render` per template |\n\n**Request Format Translation:**\n\nBannerBear uses a `modifications` **array** with named layers:\n```json\n// BannerBear\n{\n  \"template\": \"TEMPLATE_UID\",\n  \"modifications\": [\n    { \"name\": \"title\", \"text\": \"Hello World\" },\n    { \"name\": \"hero\", \"image_url\": \"https://example.com/img.jpg\" },\n    { \"name\": \"bg\", \"color\": \"#FF0000\" }\n  ]\n}\n\n// Orshot equivalent\n{\n  \"templateId\": 123,\n  \"modifications\": {\n    \"title\": \"Hello World\",\n    \"hero\": \"https://example.com/img.jpg\",\n    \"canvasBackgroundColor\": \"#FF0000\"\n  },\n  \"response\": { \"type\": \"url\", \"format\": \"png\" }\n}\n```\n\n**Key differences:**\n- BannerBear modifications is an **array**, Orshot is a **flat object**\n- BannerBear uses `image_url` for images, Orshot uses the parameter ID directly with a URL value\n- BannerBear requires polling for async results, Orshot returns synchronously\n- BannerBear `template` is a UID string, Orshot `templateId` is an integer for studio templates\n- Orshot supports style overrides via dot notation (e.g., `\"title.fontSize\": \"48px\"`) — BannerBear does not\n\n### Migrating from Placid\n\nBlog: `https://orshot.com/blog/placid-api-alternative.md`\n\n**Authentication:**\n| Placid | Orshot |\n|--------|--------|\n| `Authorization: Bearer PLACID_TOKEN` | `Authorization: Bearer ORSHOT_API_KEY` |\n\n**Endpoint Mapping:**\n| Action | Placid | Orshot |\n|--------|--------|--------|\n| Generate image | `POST /api/rest/{template_uuid}` | `POST /v1/studio/render` |\n| Get image status | `GET /api/rest/images/{id}` | Not needed (sync response) |\n| List templates | `GET /api/rest/templates` | `GET /v1/studio/templates/all?page=1&limit=10` |\n| Get template | `GET /api/rest/templates/{uuid}` | `GET /v1/studio/templates/:id` |\n| Delete render | `DELETE /api/rest/images/{id}` | Not needed (renders don't expire) |\n\n**Request Format Translation:**\n\nPlacid uses a `layers` **object** with type-specific properties:\n```json\n// Placid\n{\n  \"layers\": {\n    \"title\": {\n      \"text\": \"Hello World\",\n      \"text_color\": \"#FF0000\",\n      \"font\": \"Arial\"\n    },\n    \"hero_image\": {\n      \"image\": \"https://example.com/img.jpg\"\n    },\n    \"background\": {\n      \"background_color\": \"#000000\"\n    }\n  },\n  \"modifications\": {\n    \"width\": 1200,\n    \"height\": 630,\n    \"image_format\": \"jpg\"\n  }\n}\n\n// Orshot equivalent\n{\n  \"templateId\": 123,\n  \"modifications\": {\n    \"title\": \"Hello World\",\n    \"title.color\": \"#FF0000\",\n    \"title.fontFamily\": \"Arial\",\n    \"hero_image\": \"https://example.com/img.jpg\",\n    \"canvasBackgroundColor\": \"#000000\"\n  },\n  \"response\": { \"type\": \"url\", \"format\": \"jpg\" }\n}\n```\n\n**Key differences:**\n- Placid separates `layers` (content) from `modifications` (output settings), Orshot combines both in `modifications`\n- Placid uses `text_color`, Orshot uses dot notation `parameterId.color`\n- Placid uses `image` key for image URLs, Orshot uses the parameter ID directly\n- Placid requires polling or webhooks, Orshot returns synchronously\n- Placid renders can expire, Orshot renders persist\n- Placid credits vary by resolution, Orshot is a flat 1 credit per image regardless of size\n\n### Migrating from Creatomate\n\nBlog: `https://orshot.com/blog/creatomate-api-alternative.md`\n\n**Authentication:**\n| Creatomate | Orshot |\n|------------|--------|\n| `Authorization: Bearer CREATOMATE_KEY` | `Authorization: Bearer ORSHOT_API_KEY` |\n\n**Endpoint Mapping:**\n| Action | Creatomate | Orshot |\n|--------|-----------|--------|\n| Generate render | `POST /v1/renders` | `POST /v1/studio/render` |\n| Get render status | `GET /v1/renders/:id` | Not needed (sync response) |\n| List templates | `GET /v1/templates` | `GET /v1/studio/templates/all?page=1&limit=10` |\n| Get template | `GET /v1/templates/:id` | `GET /v1/studio/templates/:id` |\n\n**Request Format Translation:**\n\nCreatomate uses a flat `modifications` object (closest to Orshot's format):\n```json\n// Creatomate\n{\n  \"template_id\": \"TEMPLATE_UUID\",\n  \"modifications\": {\n    \"Title\": \"Hello World\",\n    \"Image-1\": \"https://example.com/img.jpg\"\n  },\n  \"output_format\": \"jpg\",\n  \"render_scale\": 1,\n  \"max_width\": 1080\n}\n\n// Orshot equivalent\n{\n  \"templateId\": 123,\n  \"modifications\": {\n    \"title\": \"Hello World\",\n    \"image_1\": \"https://example.com/img.jpg\"\n  },\n  \"response\": { \"type\": \"url\", \"format\": \"jpg\", \"scale\": 1 }\n}\n```\n\n**Key differences:**\n- Creatomate `template_id` is a UUID string, Orshot `templateId` is an integer\n- Creatomate element names are display names (e.g., \"Title\", \"Image-1\"), Orshot uses snake_case parameterIds\n- Creatomate has `output_format` at root level, Orshot uses `response.format`\n- Creatomate requires polling/webhooks, Orshot returns synchronously\n- Creatomate renders can expire, Orshot renders persist\n- Orshot supports style overrides via dot notation — Creatomate requires modifying the template source JSON\n- Creatomate gates its preview SDK to a higher tier; Orshot's embed is available on all paid plans\n\n### Migrating from RenderForm\n\nBlog: `https://orshot.com/blog/renderform-api-alternative.md`\n\n**Authentication:**\n| RenderForm | Orshot |\n|------------|--------|\n| `X-API-KEY: RENDERFORM_KEY` | `Authorization: Bearer ORSHOT_API_KEY` |\n\n**Endpoint Mapping:**\n| Action | RenderForm | Orshot |\n|--------|-----------|--------|\n| Generate image | `POST /api/v2/render` | `POST /v1/studio/render` |\n| List templates | `GET /api/v2/my-templates?page=1&size=50` | `GET /v1/studio/templates/all?page=1&limit=10` |\n| Get template | `GET /api/v2/my-templates/:id` | `GET /v1/studio/templates/:id` |\n| URL-based render | `GET /img/TEMPLATE.jpg?key=...&param=...` | `GET /v1/studio/dynamic-url/TEMPLATE?param=...` |\n\n**Request Format Translation:**\n\nRenderForm uses dot notation in a `data` object (similar to Orshot's style overrides):\n```json\n// RenderForm\n{\n  \"template\": \"TEMPLATE_ID\",\n  \"data\": {\n    \"title.text\": \"Hello World\",\n    \"hero.src\": \"https://example.com/img.jpg\",\n    \"bg.color\": \"#FF0000\"\n  },\n  \"fileName\": \"output\",\n  \"width\": 1200,\n  \"height\": 630\n}\n\n// Orshot equivalent\n{\n  \"templateId\": 123,\n  \"modifications\": {\n    \"title\": \"Hello World\",\n    \"hero\": \"https://example.com/img.jpg\",\n    \"canvasBackgroundColor\": \"#FF0000\"\n  },\n  \"response\": { \"type\": \"url\", \"format\": \"png\", \"fileName\": \"output\" }\n}\n```\n\n**Key differences:**\n- RenderForm uses `X-API-KEY` header, Orshot uses `Authorization: Bearer`\n- RenderForm uses `data` with `componentId.property` dot notation for everything, Orshot uses `modifications` with parameter IDs for content and dot notation only for style overrides\n- RenderForm `template` is a string ID, Orshot `templateId` is an integer\n- RenderForm images can expire, Orshot renders persist\n- Orshot's free tier has no watermarks\n\n### Migrating from Abyssale\n\nBlog: `https://orshot.com/blog/abyssale-api-alternative.md`\n\n**Authentication:**\n| Abyssale | Orshot |\n|----------|--------|\n| `x-api-key: ABYSSALE_KEY` | `Authorization: Bearer ORSHOT_API_KEY` |\n\n**Endpoint Mapping:**\n| Action | Abyssale | Orshot |\n|--------|---------|--------|\n| Generate image | `POST /async/banner-builder/{designId}/generate` (async) | `POST /v1/studio/render` (sync) |\n| Poll status | `GET /generation-request/{requestId}` | Not needed (sync response) |\n| List designs | `GET /designs` | `GET /v1/studio/templates/all?page=1&limit=10` |\n| Get design | `GET /designs/{designId}` | `GET /v1/studio/templates/:id` |\n| Multi-format render | `template_format_names` param | Loop `POST /v1/studio/render` per format, or use Variant Generation API |\n\n**Request Format Translation:**\n\nAbyssale uses an `elements` object with `payload` for text:\n```json\n// Abyssale\n{\n  \"template_format_names\": [\"facebook-feed\", \"instagram-post\"],\n  \"elements\": {\n    \"title\": { \"payload\": \"Hello World\" },\n    \"hero_image\": { \"image_url\": \"https://example.com/img.jpg\" },\n    \"background\": { \"color\": \"#FF0000\" }\n  },\n  \"callback_url\": \"https://webhook.example.com\"\n}\n\n// Orshot equivalent\n{\n  \"templateId\": 123,\n  \"modifications\": {\n    \"title\": \"Hello World\",\n    \"hero_image\": \"https://example.com/img.jpg\",\n    \"canvasBackgroundColor\": \"#FF0000\"\n  },\n  \"response\": { \"type\": \"url\", \"format\": \"png\" }\n}\n```\n\n**Key differences:**\n- Abyssale uses `payload` for text content, Orshot uses the parameter ID directly\n- Abyssale uses `image_url` for images, Orshot uses the parameter ID with URL value\n- Abyssale supports multi-format in single call via `template_format_names`, Orshot requires separate calls or Variant Generation API\n- Abyssale is async-only (polling/webhook), Orshot returns synchronously\n- Abyssale charges per-seat, Orshot has unlimited team members on all plans\n- Abyssale results can expire, Orshot renders persist\n\n### Migrating from DynaPictures\n\nBlog: `https://orshot.com/blog/dynapictures-api-alternative.md`\n\n**Authentication:**\n| DynaPictures | Orshot |\n|--------------|--------|\n| `Authorization: Bearer DYNA_KEY` | `Authorization: Bearer ORSHOT_API_KEY` |\n\n**Endpoint Mapping:**\n| Action | DynaPictures | Orshot |\n|--------|-------------|--------|\n| Generate image | `POST /designs/{id}` | `POST /v1/studio/render` |\n| List designs | `GET /designs` | `GET /v1/studio/templates/all?page=1&limit=10` |\n\n**Key differences:**\n- DynaPictures uses an outdated editor interface, Orshot has a modern Figma-like editor\n- No multi-page support in DynaPictures\n- No white-label editor option\n- Limited integrations compared to Orshot's 15+\n\n### Migrating from Contentdrips\n\nBlog: `https://orshot.com/blog/contentdrips-api-alternative.md`\n\nContentdrips is a social media design tool with API as a secondary feature. Migration is straightforward since Orshot is API-first.\n\n**Key differences:**\n- Contentdrips API is bolted-on, Orshot is API-first\n- Contentdrips has limited dynamic parameter capabilities\n- No white-label editor, no MCP server, no CLI\n- Per-seat pricing vs. Orshot's credit-based pricing\n\n### Quick Migration Reference\n\n| Feature | BannerBear | Placid | Creatomate | RenderForm | Abyssale | Orshot |\n|---------|-----------|--------|------------|------------|----------|--------|\n| **Auth header** | `Authorization: Bearer` | `Authorization: Bearer` | `Authorization: Bearer` | `X-API-KEY` | `x-api-key` | `Authorization: Bearer` |\n| **Modifications format** | Array of objects | `layers` object | Flat object | `data` with dot notation | `elements` with `payload` | Flat object |\n| **Template ID type** | UID string | UUID string | UUID string | String | UUID string | Integer (studio) |\n| **Response model** | Async (poll) | Async (poll) | Async (poll) | Sync | Async (poll) | Sync |\n| **Render persistence** | Permanent | Can expire | Can expire | Can expire | Can expire | Permanent |\n| **Style overrides** | No | Limited | Via source JSON | Dot notation | No | Dot notation |\n| **Multi-page** | No | No | No | No | No | Yes |\n| **Video support** | Yes (extra cost) | Yes (extra cost) | Yes | No | Animated only | Yes (1 credit/sec) |\n| **White-label editor** | No | No | Higher tier | No | No | All paid plans |\n| **Social publishing** | No | No | No | No | No | 13+ platforms |\n\n### Links\n\n- Documentation: https://orshot.com/docs\n- API Reference: https://orshot.com/docs/api-reference\n- Integrations: https://orshot.com/integrations\n- MCP Server: https://orshot.com/docs/integrations/mcp-server\n- Templates: https://orshot.com/templates\n- Pricing: https://orshot.com/pricing\n- Developer Apps: https://orshot.com/docs/developers\n- Social Publishing: https://orshot.com/docs/publish/introduction\n- Orshot Embed: https://orshot.com/docs/orshot-embed/introduction\n"},"changes":[],"summary":"First saved snapshot. No earlier version is available for comparison.","summary_kind":"deterministic","summary_metadata":{}}