<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Kevin</title>
    <description>The latest articles on DEV Community by Kevin (@kesimo).</description>
    <link>https://dev.to/kesimo</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2333897%2F966ec6dd-6a8a-4a5c-9ccb-c3a08e04ac6c.png</url>
      <title>DEV Community: Kevin</title>
      <link>https://dev.to/kesimo</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/kesimo"/>
    <language>en</language>
    <item>
      <title>Placet: An Open Source Human-in-the-Loop Platform for AI Agents and Automation Workflows</title>
      <dc:creator>Kevin</dc:creator>
      <pubDate>Tue, 31 Mar 2026 20:58:44 +0000</pubDate>
      <link>https://dev.to/kesimo/placet-an-open-source-human-in-the-loop-platform-for-ai-agents-and-automation-workflows-4bf9</link>
      <guid>https://dev.to/kesimo/placet-an-open-source-human-in-the-loop-platform-for-ai-agents-and-automation-workflows-4bf9</guid>
      <description>&lt;p&gt;AI agents are getting better every week. They write code, analyze data, generate reports, moderate content, and propose infrastructure changes. In many cases that output is good enough to act on directly.&lt;/p&gt;

&lt;p&gt;But for most business-critical workflows a human still needs to be in the loop (HITL). Not because the AI is unreliable, but because accountability, context, and final judgment still matter.&lt;/p&gt;

&lt;p&gt;The question is: &lt;em&gt;where&lt;/em&gt; does that human review actually happen?&lt;/p&gt;

&lt;p&gt;In most teams the answer is Slack, Teams, Telegram, or email. Someone builds a bot, it sends a message, attaches some context, asks for a thumbs-up. It works, barely. But these tools were designed for human-to-human communication and they were never built for structured agent-human collaboration.&lt;/p&gt;

&lt;p&gt;I built &lt;a href="https://placet.io" rel="noopener noreferrer"&gt;Placet&lt;/a&gt; to fix that.&lt;/p&gt;




&lt;h2&gt;
  
  
  What is Placet?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frq0f2m6d5rgnumqorydi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frq0f2m6d5rgnumqorydi.png" alt="Placet-screenshot" width="800" height="625"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Placet&lt;/strong&gt; (Latin for "it pleases" or "approved") is a self-hostable, open source inbox purpose-built for human-in-the-loop (HITL) workflows. It provides:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;strong&gt;REST API&lt;/strong&gt; that any agent, script, or automation tool can call via standard HTTP&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;web UI&lt;/strong&gt; where humans review messages, respond to structured requests, and annotate files&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;plugin system&lt;/strong&gt; for rendering custom message types inside sandboxed iframes&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;webhook and long-polling system&lt;/strong&gt; to deliver review responses back to the agent&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The design philosophy is simple: be the cURL of human interaction. If your tool can make an HTTP request, it can integrate with Placet. No SDK required, no framework coupling.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Not Just Use Slack or Telegram?
&lt;/h2&gt;

&lt;p&gt;This question comes up every time. The short answer: an approval button in Slack is a hack, not a feature.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Problem&lt;/th&gt;
&lt;th&gt;Slack / Telegram / Teams&lt;/th&gt;
&lt;th&gt;Placet&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Structured approval with styled buttons&lt;/td&gt;
&lt;td&gt;Button text only, no visual hierarchy&lt;/td&gt;
&lt;td&gt;Primary / Danger / Default button styles&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Multi-field form submission&lt;/td&gt;
&lt;td&gt;Impossible without a custom app&lt;/td&gt;
&lt;td&gt;Native form review type (12 field types)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rich file previews inline&lt;/td&gt;
&lt;td&gt;Images only, limited context&lt;/td&gt;
&lt;td&gt;PDF, DOCX, XLSX, MP4, audio, code, SVG all inline&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Image annotation&lt;/td&gt;
&lt;td&gt;Not possible&lt;/td&gt;
&lt;td&gt;Canvas overlay: pen, arrow, rectangle, text&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Review expiry with webhook callback&lt;/td&gt;
&lt;td&gt;Manual workaround required&lt;/td&gt;
&lt;td&gt;Built-in, configurable per review&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Delivery status tracking&lt;/td&gt;
&lt;td&gt;Not available&lt;/td&gt;
&lt;td&gt;WhatsApp-style: &lt;code&gt;sent&lt;/code&gt; to &lt;code&gt;delivered&lt;/code&gt; to &lt;code&gt;agent_received&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Agent status heartbeat&lt;/td&gt;
&lt;td&gt;Not available&lt;/td&gt;
&lt;td&gt;4 states with full history timeline&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Self-hosted, no cloud dependency&lt;/td&gt;
&lt;td&gt;SaaS only or complex setup&lt;/td&gt;
&lt;td&gt;One &lt;code&gt;docker compose up&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Open source&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;I personally replaced my Telegram-based approval flows with Placet and the difference was immediate. Review context stays in one place, responses are structured JSON instead of freeform text, and I can annotate AI-generated images without switching to another tool.&lt;/p&gt;




&lt;h2&gt;
  
  
  Core Concepts
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Agents and Channels
&lt;/h3&gt;

&lt;p&gt;Every integration is an &lt;strong&gt;agent&lt;/strong&gt;: an entity that holds an API key and has its own chat channel in the UI. You can have as many agents as you want, one per LangChain workflow, one per CI/CD pipeline, one per cron job.&lt;/p&gt;

&lt;p&gt;Each agent gets:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Its own isolated channel (like a dedicated chat thread)&lt;/li&gt;
&lt;li&gt;A configurable webhook URL for receiving review responses&lt;/li&gt;
&lt;li&gt;An optional avatar and description for identification&lt;/li&gt;
&lt;li&gt;A status heartbeat system with full history&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The Five Review Types
&lt;/h3&gt;

&lt;p&gt;Placet ships with five built-in review primitives:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;When to use&lt;/th&gt;
&lt;th&gt;Response shape&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Approval&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Binary or small set of choices (approve/reject)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;{ selectedOption, comment? }&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Selection&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Single or multi-select from a list of items&lt;/td&gt;
&lt;td&gt;&lt;code&gt;{ selectedIds: [...] }&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Form&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Structured data entry with multiple typed fields&lt;/td&gt;
&lt;td&gt;&lt;code&gt;{ fieldName: value, ... }&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Text Input&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Open-ended freeform response with optional markdown preview&lt;/td&gt;
&lt;td&gt;&lt;code&gt;{ text: "..." }&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Freeform&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Custom JSON, rendered and submitted by a plugin&lt;/td&gt;
&lt;td&gt;any JSON&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;All review types support:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;expiresInSeconds&lt;/code&gt; or &lt;code&gt;expiresAt&lt;/code&gt; (default 24 hours, max 36 hours)&lt;/li&gt;
&lt;li&gt;Per-message webhook callbacks&lt;/li&gt;
&lt;li&gt;Long-polling via &lt;code&gt;GET /api/v1/reviews/:id/wait&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;review:expired&lt;/code&gt; webhook callback when the timer runs out&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  How Agents Receive Responses
&lt;/h3&gt;

&lt;p&gt;When a human responds to a review, the agent can receive it via one of three connection types:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Webhook callback&lt;/strong&gt;: Placet POSTs the response to your configured URL&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Long-polling&lt;/strong&gt;: The agent waits on &lt;code&gt;GET /api/v1/reviews/:id/wait&lt;/code&gt; for up to 30 seconds&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;WebSocket&lt;/strong&gt;: Subscribe to real-time events via Socket.io (e.g. &lt;code&gt;review:responded&lt;/code&gt;, &lt;code&gt;review:expired&lt;/code&gt;, &lt;code&gt;message:created&lt;/code&gt;)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The WebSocket connection is particularly useful for agents that want to stay permanently connected and react instantly without the overhead of repeated polling. Here is a minimal example using the Socket.io client:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;io&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;socket.io-client&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;socket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;io&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://your-placet-instance.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hp_your_api_key&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;review:responded&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;messageId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;channelId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Review &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;messageId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; completed:`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;review:expired&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Review &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;messageId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; expired without a response`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Webhook Resolution: Three Layers
&lt;/h3&gt;

&lt;p&gt;When a human responds to a review, Placet resolves where to send the callback in a fixed priority chain:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Priority&lt;/th&gt;
&lt;th&gt;Source&lt;/th&gt;
&lt;th&gt;How to set it&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1 (highest)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Message-level webhook&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Pass &lt;code&gt;webhookUrl&lt;/code&gt; in the &lt;code&gt;POST /api/v1/messages&lt;/code&gt; body&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Agent-level default webhook&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Set once in the agent settings panel&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Legacy inline callback&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;A &lt;code&gt;callback&lt;/code&gt; field inside the review payload (backwards compat)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The message-level override exists because real pipelines are rarely that uniform. A single agent might dispatch review requests from multiple concurrent LangChain runs, each needing its response routed somewhere different: a per-run callback URL, a short-lived ngrok tunnel, a specific Lambda invocation. Passing &lt;code&gt;webhookUrl&lt;/code&gt; per message solves this cleanly without spinning up a new agent for each run.&lt;/p&gt;

&lt;p&gt;WebSocket events (&lt;code&gt;review:responded&lt;/code&gt;, &lt;code&gt;review:expired&lt;/code&gt;) always fire in parallel with the HTTP callback, regardless of which tier is active. When a webhook call fails, the message flips to a &lt;code&gt;webhook_failed&lt;/code&gt; delivery status, visible as a red indicator in the UI. A single click retries delivery without touching the review state.&lt;/p&gt;

&lt;h3&gt;
  
  
  Push-Only vs Bidirectional Channels
&lt;/h3&gt;

&lt;p&gt;Not every workflow needs a free-text chat box. Placet channels support two practical communication patterns, and which one applies is determined by how the agent is configured.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Push-only (watch mode):&lt;/strong&gt; The agent sends messages to the inbox and may request structured responses. Humans respond exclusively through the built-in review UI: clicking an approval button, selecting options, filling a form, drawing annotations. The free-text message input is present in the UI but serves no purpose for the agent, because there is no webhook to receive unstructured user messages. This is the right pattern for automated pipelines where the agent controls the agenda and the human is there to gate-keep specific decision points.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bidirectional (chat mode):&lt;/strong&gt; When the agent has a webhook configured, human-typed messages (stored with &lt;code&gt;senderType: "user"&lt;/code&gt;) are forwarded to that same webhook in real time, alongside review responses and delivery events. The agent can react to free-text input, ask follow-up questions, or run a full conversational loop. The LangChain example in the repository demonstrates this pattern: the agent pauses mid-task to ask a question, the human types an answer in the chat box, and the agent continues with the new context.&lt;/p&gt;

&lt;p&gt;The distinction matters when you are designing a workflow. A production deploy gate needs only structured approval buttons. A research assistant that takes mid-run guidance needs the full chat loop. Placet does not force a choice: configure the webhook and you get both structured reviews and free-text input from the same channel.&lt;/p&gt;

&lt;h3&gt;
  
  
  Upcoming Integrations
&lt;/h3&gt;

&lt;p&gt;Beyond the REST API and WebSocket, two more connection types are actively in development:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Integration&lt;/th&gt;
&lt;th&gt;Status&lt;/th&gt;
&lt;th&gt;What it will enable&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;MCP server&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;In development&lt;/td&gt;
&lt;td&gt;Claude, Cursor, and other MCP-compatible agents can call Placet tools (send message, request approval, wait for response) natively without an HTTP wrapper&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;n8n node&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;In development&lt;/td&gt;
&lt;td&gt;Native Placet node for n8n workflows: trigger on review response, send messages, request approvals directly from the n8n canvas&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Make.com module&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;In development&lt;/td&gt;
&lt;td&gt;Same native integration for Make (formerly Integromat) automation scenarios&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Once the MCP server ships, agents running in Claude or any MCP-compatible runtime will be able to integrate with Placet with zero REST boilerplate. The n8n and Make.com integrations will make Placet accessible to no-code automation builders without writing a single line of code.&lt;/p&gt;




&lt;h2&gt;
  
  
  The API in Practice
&lt;/h2&gt;

&lt;p&gt;Sending a message is a single HTTP call. No SDK, no special client library required:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://your-placet-instance.com/api/v1/messages &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer hp_your-key-here"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"channelId": "your-agent-id", "text": "Analysis complete.", "status": "success"}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Adding a human approval request takes a few more fields:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://your-placet-instance.com/api/v1/messages &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer hp_your-key-here"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
    "channelId": "your-agent-id",
    "text": "Deploy v2.1 to production?",
    "review": {
      "type": "approval",
      "payload": {
        "options": [
          {"id": "deploy", "label": "Deploy", "style": "primary"},
          {"id": "cancel", "label": "Cancel", "style": "danger"}
        ]
      }
    }
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From Python, using &lt;code&gt;requests&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;

&lt;span class="n"&gt;BASE_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://your-placet-instance.com&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;API_KEY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;hp_your-api-key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;CHANNEL_ID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;your-agent-id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;BASE_URL&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/api/v1/messages&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Authorization&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Bearer &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;API_KEY&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;channelId&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;CHANNEL_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Please review the attached report.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;warning&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;review&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;approval&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;payload&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;options&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;approve&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;label&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Approve&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;style&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;primary&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
                    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;reject&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;label&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Reject&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;style&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;danger&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
                &lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# Long-poll for the human response (synchronous, max 30s per call)
&lt;/span&gt;&lt;span class="n"&gt;review&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;BASE_URL&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/api/v1/reviews/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/wait&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Authorization&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Bearer &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;API_KEY&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;review&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;message&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;review&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;response&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;selectedOption&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;  &lt;span class="c1"&gt;# "approve" or "reject"
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The full API reference is available at &lt;a href="https://docs.placet.io" rel="noopener noreferrer"&gt;docs.placet.io&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Agent Status Heartbeat
&lt;/h2&gt;

&lt;p&gt;Beyond sending messages, agents can report their current operational status so humans have a live health dashboard across all running workflows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://localhost:3001/api/v1/status/ping &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer hp_your-key-here"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"status": "busy", "message": "Processing 847 records from the data pipeline"}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The four status values are &lt;code&gt;active&lt;/code&gt;, &lt;code&gt;busy&lt;/code&gt;, &lt;code&gt;error&lt;/code&gt;, and &lt;code&gt;offline&lt;/code&gt;. The UI shows the current badge next to the agent name and maintains a full history timeline, which is useful for debugging why an agent went silent or when a pipeline stalled.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Plugin System
&lt;/h2&gt;

&lt;p&gt;One of Placet's more distinctive features is its plugin system. You can define custom message renderers as static HTML files loaded in sandboxed iframes. All built-in review types use this same system internally; there is no special-casing.&lt;/p&gt;

&lt;p&gt;A plugin is a directory with three files. That is all it takes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;packages/plugins/my-plugin/
  plugin.json   - manifest: name, version, input schema, HTTP permissions
  render.html   - the UI: plain HTML + CSS + JS, no build step required
  icon.svg      - optional icon shown in the Settings UI
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The plugin receives message data from the host via &lt;code&gt;postMessage&lt;/code&gt; and submits responses the same way. Outbound HTTP requests are proxied server-side with a per-plugin domain allowlist, so plugins can call external APIs without exposing credentials to the browser or opening SSRF vectors.&lt;/p&gt;

&lt;p&gt;Plugins are decoupled from the review system. A plugin controls &lt;em&gt;how&lt;/em&gt; a message is rendered; a review controls &lt;em&gt;whether&lt;/em&gt; user input is required. You can use either independently or combine them on the same message.&lt;/p&gt;

&lt;p&gt;Two example plugins are included in the repository to use as a starting point:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Plugin&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;th&gt;Source&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;form-submit&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Renders a dynamic form and POSTs the response to a configurable webhook URL&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/placet-io/placet/tree/main/packages/plugins/form-submit" rel="noopener noreferrer"&gt;packages/plugins/form-submit&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;kroki-diagram&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Renders Mermaid, PlantUML, D2, Graphviz, and more via a Kroki server&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/placet-io/placet/tree/main/packages/plugins/kroki-diagram" rel="noopener noreferrer"&gt;packages/plugins/kroki-diagram&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  File Handling
&lt;/h2&gt;

&lt;p&gt;Placet treats files as first-class citizens of the review workflow. Supported formats are previewed inline without any application switching.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Category&lt;/th&gt;
&lt;th&gt;Formats&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Images&lt;/td&gt;
&lt;td&gt;JPG, PNG, GIF, WebP, SVG&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Video&lt;/td&gt;
&lt;td&gt;MP4, WebM, MOV (inline player)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Audio&lt;/td&gt;
&lt;td&gt;MP3, WAV, OGG, M4A (inline player)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Documents&lt;/td&gt;
&lt;td&gt;PDF, DOCX, ODT&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Spreadsheets&lt;/td&gt;
&lt;td&gt;XLSX, XLS, ODS, CSV&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Presentations&lt;/td&gt;
&lt;td&gt;PPTX&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Code / Text&lt;/td&gt;
&lt;td&gt;40+ languages with Shiki syntax highlighting&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Markdown&lt;/td&gt;
&lt;td&gt;GitHub Flavored Markdown rendered inline&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Image annotation&lt;/strong&gt; is built directly into the review flow. When an agent generates images, diagrams, or screenshots, the human reviewer can open an annotation canvas in-chat and draw with pen, arrows, rectangles, and text labels. The annotated image is saved back into the conversation. No external markup tool needed.&lt;/p&gt;

&lt;p&gt;Additional file features: JWT-based share links (1-hour expiry), bulk ZIP download, full-text search in the file browser, and presigned S3-compatible uploads via MinIO.&lt;/p&gt;




&lt;h2&gt;
  
  
  Self-Hosting in Three Minutes
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Prerequisites:&lt;/strong&gt; Git, Node.js 22+, Docker with Docker Compose v2, 2 GB RAM.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/placet-io/placet.git
&lt;span class="nb"&gt;cd &lt;/span&gt;placet
&lt;span class="nb"&gt;cp&lt;/span&gt; .env.example .env
make setup
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;make setup&lt;/code&gt; installs dependencies, builds all packages, starts the full Docker Compose stack (PostgreSQL + MinIO + backend + frontend), runs database migrations, and creates the initial user. Everything runs locally with zero cloud dependencies.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Services available after setup:&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Service&lt;/th&gt;
&lt;th&gt;URL&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Frontend&lt;/td&gt;
&lt;td&gt;&lt;a href="http://localhost:3000" rel="noopener noreferrer"&gt;http://localhost:3000&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Backend API&lt;/td&gt;
&lt;td&gt;&lt;a href="http://localhost:3001" rel="noopener noreferrer"&gt;http://localhost:3001&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;API docs&lt;/td&gt;
&lt;td&gt;&lt;a href="https://docs.placet.io" rel="noopener noreferrer"&gt;https://docs.placet.io&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MinIO Console&lt;/td&gt;
&lt;td&gt;&lt;a href="http://localhost:9001" rel="noopener noreferrer"&gt;http://localhost:9001&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Default login:&lt;/strong&gt; &lt;code&gt;admin@placet.local&lt;/code&gt; / &lt;code&gt;changeme&lt;/code&gt; (configurable in &lt;code&gt;.env&lt;/code&gt;)&lt;/p&gt;

&lt;p&gt;Once you are in:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to &lt;strong&gt;Settings → API Keys&lt;/strong&gt; and create a key&lt;/li&gt;
&lt;li&gt;Go to &lt;strong&gt;Settings → Agents&lt;/strong&gt; and create an agent&lt;/li&gt;
&lt;li&gt;Send your first message:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://localhost:3001/api/v1/messages &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer hp_your-key-here"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"text": "Hello from my agent!", "status": "success"}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Open the agent channel in the UI and your message appears in real time.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For production deployments, a Traefik overlay (&lt;code&gt;docker-compose.traefik.yml&lt;/code&gt;) is included and handles automatic HTTPS via Let's Encrypt.&lt;/p&gt;




&lt;h2&gt;
  
  
  How I Use Placet in My Own Workflows
&lt;/h2&gt;

&lt;p&gt;I run Placet as the central review layer across several of my personal and business automation workflows. Here are three concrete examples:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Document approval pipeline.&lt;/strong&gt; An AI pipeline processes incoming data, generates a weekly summary PDF, and sends it to Placet with an approval request before distribution. I open the rendered PDF directly in the chat, annotate sections if needed, and click "Approve" or "Hold for Revision." The pipeline receives the structured response and acts on it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CI/CD production gating.&lt;/strong&gt; Pipeline steps that affect production infrastructure gate on a Placet approval before continuing. I review a summary of what is about to happen, approve or reject, and the pipeline proceeds or aborts accordingly. This replaced a fragile Telegram bot that had no audit trail and broke regularly after API changes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;LangChain agents with mid-run questions.&lt;/strong&gt; LangChain agents running multi-step tasks hit decision points where they genuinely need human judgment. They call the Placet API, present the question with full context, and wait. When I respond, they continue. The full conversation history that led to the question is visible in the chat.&lt;/p&gt;

&lt;p&gt;The advantage over Telegram or Slack is not just the UI quality. It is having all reviews in one structured place, with delivery receipts, history, and a consistent response schema that the downstream code can rely on.&lt;/p&gt;




&lt;h2&gt;
  
  
  Open Source: Contributions Are Very Welcome
&lt;/h2&gt;

&lt;p&gt;Placet is open source and contributions of any size are genuinely appreciated.&lt;/p&gt;

&lt;p&gt;Whether you are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fixing a typo in the docs&lt;/li&gt;
&lt;li&gt;Reporting a bug you hit while integrating Placet into your workflow&lt;/li&gt;
&lt;li&gt;Suggesting a missing feature or integration&lt;/li&gt;
&lt;li&gt;Submitting a pull request for a new review type, plugin, or API capability&lt;/li&gt;
&lt;li&gt;Just dropping a GitHub star&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All of it matters. &lt;strong&gt;Bug reports are contributions too&lt;/strong&gt;, and they are often the most valuable ones because they come from real usage.&lt;/p&gt;

&lt;p&gt;The repo is at &lt;strong&gt;&lt;a href="https://github.com/placet-io/placet" rel="noopener noreferrer"&gt;github.com/placet-io/placet&lt;/a&gt;&lt;/strong&gt;. If you are building something on top of Placet or using it in your own workflows, I would love to hear about it. Open an issue, start a discussion, or reach out directly.&lt;/p&gt;




&lt;h2&gt;
  
  
  Closing
&lt;/h2&gt;

&lt;p&gt;Human-in-the-loop workflows are not a temporary workaround before full AI autonomy arrives. As agents become more capable and more autonomous, the seams where humans and agents interact become more important, not less. Those seams deserve better tooling than a Telegram bot.&lt;/p&gt;

&lt;p&gt;Placet is my attempt at building that tooling in the open. It is early, it is opinionated, and it is actively developed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Links:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GitHub: &lt;a href="https://github.com/placet-io/placet" rel="noopener noreferrer"&gt;github.com/placet-io/placet&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Self-host: &lt;code&gt;make setup&lt;/code&gt; (see the &lt;a href="https://github.com/placet-io/placet" rel="noopener noreferrer"&gt;README&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Docs: &lt;a href="https://docs.placet.io" rel="noopener noreferrer"&gt;docs.placet.io&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>opensource</category>
      <category>ai</category>
      <category>agents</category>
      <category>automation</category>
    </item>
    <item>
      <title>Stop Fighting Outdated DOCX Libraries: Modern API-Based Generation for SaaS</title>
      <dc:creator>Kevin</dc:creator>
      <pubDate>Thu, 26 Mar 2026 08:46:46 +0000</pubDate>
      <link>https://dev.to/kesimo/stop-fighting-outdated-docx-libraries-modern-api-based-generation-for-saas-578j</link>
      <guid>https://dev.to/kesimo/stop-fighting-outdated-docx-libraries-modern-api-based-generation-for-saas-578j</guid>
      <description>&lt;p&gt;Every document library starts with promise. You install &lt;code&gt;docxtemplater&lt;/code&gt;, configure &lt;code&gt;python-docx&lt;/code&gt;, or wrap &lt;code&gt;docx4j&lt;/code&gt;, and for simple templates it works. Then the edge cases pile up. Nested tables break layout. Images refuse to align. Bullet lists lose formatting. You spend days debugging XML interpolation instead of shipping features.&lt;/p&gt;

&lt;p&gt;The maintenance burden compounds. Each Microsoft Word update risks breaking your carefully crafted templates. Support tickets roll in about corrupted files and missing fonts. What should be a simple "generate contract" feature becomes a multi-week project.&lt;/p&gt;

&lt;p&gt;There is a cleaner path. Modern REST APIs for document generation eliminate library maintenance entirely. Your code sends structured data, the API returns a finished PDF or DOCX. No XML wrangling. No dependency conflicts.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Library Problem
&lt;/h2&gt;

&lt;p&gt;Traditional DOCX libraries manipulate Office Open XML directly. This format has thousands of elements and complex relationships. A simple paragraph with bold text requires understanding &lt;code&gt;w:p&lt;/code&gt;, &lt;code&gt;w:r&lt;/code&gt;, &lt;code&gt;w:rPr&lt;/code&gt;, and &lt;code&gt;w:b&lt;/code&gt; elements. Tables involve nested &lt;code&gt;w:tbl&lt;/code&gt;, &lt;code&gt;w:tr&lt;/code&gt;, and &lt;code&gt;w:tc&lt;/code&gt; structures.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Common failure points:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Edge cases everywhere&lt;/strong&gt;: Libraries like &lt;a href="https://github.com/dolanmiu/docx" rel="noopener noreferrer"&gt;docx&lt;/a&gt; or &lt;a href="https://github.com/python-openxml/python-docx" rel="noopener noreferrer"&gt;python-docx&lt;/a&gt; cover the basics but break on complex formatting. Nested tables? Often unsupported. Cross-references? Manual work. Automatic indices like table of contents? You are building them yourself.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Template fragility&lt;/strong&gt;: A user editing the template in Word can break your code by changing a style or moving a placeholder.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No native PDF output&lt;/strong&gt;: You need additional tools for conversion. Projects like &lt;a href="https://github.com/Flavour/libreoffice-headless" rel="noopener noreferrer"&gt;LibreOffice headless&lt;/a&gt; or &lt;a href="https://github.com/gotenberg/gotenberg" rel="noopener noreferrer"&gt;Gotenberg&lt;/a&gt; add deployment complexity.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To be clear: no solution handles everything perfectly. Autype also has limitations, nested tables for example are not supported. But the difference is that an API-based approach centralizes the complexity. Your application does not carry it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Template-Based Generation with Variables
&lt;/h2&gt;

&lt;p&gt;The core pattern: define a template once, inject data repeatedly. Autype uses &lt;code&gt;{{variable}}&lt;/code&gt; syntax directly in your content.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcms.autype.com%2Fassets%2F9cc194de-c631-4746-b554-890d51bf9a80" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcms.autype.com%2Fassets%2F9cc194de-c631-4746-b554-890d51bf9a80" alt="API Document Generation Flow" width="7208" height="2208"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Basic Example: Contract Generation
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;AUTYPE_API_KEY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AUTYPE_API_KEY&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;BASE_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://api.autype.com/api/v1/dev&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;generateContract&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;clientData&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;BASE_URL&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/render/markdown`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;X-API-Key&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;AUTYPE_API_KEY&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`
# Service Agreement

**Client:** {{clientName}}  
**Date:** {{agreementDate}}  
**Project:** {{projectTitle}}

## Deliverables

{{deliverables}}

**Total Value:** {{projectValue}}
      `&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;document&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pdf&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;A4&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;variables&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;clientName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;clientData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;agreementDate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toLocaleDateString&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="na"&gt;projectTitle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;clientData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;project&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;deliverables&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;clientData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;deliverables&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;`- &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="na"&gt;projectValue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;clientData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The API returns a job ID immediately. Rendering happens asynchronously. Poll the status endpoint or register a webhook for completion.&lt;/p&gt;

&lt;h3&gt;
  
  
  Python Example: Invoice Generation
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;

&lt;span class="n"&gt;AUTYPE_API_KEY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;AUTYPE_API_KEY&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;BASE_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;https://api.autype.com/api/v1/dev&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;generate_invoice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;invoice_data&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;BASE_URL&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/render/markdown&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;X-API-Key&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;AUTYPE_API_KEY&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
# INVOICE

**Invoice #:** {{invoiceNumber}}  
**Date:** {{invoiceDate}}

## Items

| Description | Quantity | Price | Total |
|-------------|----------|-------|-------|
{{invoiceRows}}

**Total:** {{total}}
            &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;document&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;pdf&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;size&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;A4&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;variables&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;invoice_data&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Batch Processing for Scale
&lt;/h2&gt;

&lt;p&gt;SaaS applications often generate documents in batches: monthly invoices, personalized certificates, client reports. The bulk render endpoint handles this with parallel processing.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;generateClientReports&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;templateDocumentId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;clientData&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;BASE_URL&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/bulk-render`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;X-API-Key&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;AUTYPE_API_KEY&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;documentId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;templateDocumentId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;PDF&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;clientData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;clientName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;reportPeriod&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;period&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;metrics&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;metrics&lt;/span&gt;
      &lt;span class="p"&gt;}))&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The bulk endpoint accepts up to 100 variable sets per job. Each set produces a unique document. Processing happens in parallel.&lt;/p&gt;

&lt;h2&gt;
  
  
  Webhook Integration
&lt;/h2&gt;

&lt;p&gt;For production systems, register a webhook URL with your render request. The API POSTs when the job completes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Webhook receiver (Express.js)&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/webhooks/autype-complete&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;jobId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;downloadUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;COMPLETED&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;saveDocumentUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;jobId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;downloadUrl&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;FAILED&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;alertTeam&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;jobId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;OK&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The webhook payload includes job ID, status, download URL, and any error message.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Beats Library Maintenance
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Concern&lt;/th&gt;
&lt;th&gt;DOCX Libraries&lt;/th&gt;
&lt;th&gt;API-Based Generation&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Setup&lt;/td&gt;
&lt;td&gt;XML parsers, rendering tools&lt;/td&gt;
&lt;td&gt;One HTTP client&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Deployment&lt;/td&gt;
&lt;td&gt;Additional dependencies&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PDF output&lt;/td&gt;
&lt;td&gt;Requires conversion tool&lt;/td&gt;
&lt;td&gt;Native&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Batch processing&lt;/td&gt;
&lt;td&gt;Build your own queue&lt;/td&gt;
&lt;td&gt;Built-in bulk endpoint&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Error handling&lt;/td&gt;
&lt;td&gt;Debug XML errors&lt;/td&gt;
&lt;td&gt;HTTP status codes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Automatic indices&lt;/td&gt;
&lt;td&gt;Build yourself&lt;/td&gt;
&lt;td&gt;Built-in support&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cross-references&lt;/td&gt;
&lt;td&gt;Manual implementation&lt;/td&gt;
&lt;td&gt;Native support&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The time investment shifts from maintaining fragile document code to building your application logic.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Create an account at &lt;a href="https://autype.com" rel="noopener noreferrer"&gt;autype.com&lt;/a&gt; (free tier available)&lt;/li&gt;
&lt;li&gt;Generate an API key in Dashboard → Settings → API Keys&lt;/li&gt;
&lt;li&gt;Build your first template in the visual editor or define it in JSON&lt;/li&gt;
&lt;li&gt;Start with single renders, then move to bulk as needed&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Document generation should not consume your development cycles. With a modern API approach, you ship features in hours instead of weeks.&lt;/p&gt;

</description>
      <category>api</category>
      <category>automation</category>
      <category>saas</category>
      <category>backend</category>
    </item>
    <item>
      <title>When HTML to PDF Works (And When It Doesn't): A Developer's Guide to PDF Generation</title>
      <dc:creator>Kevin</dc:creator>
      <pubDate>Sun, 22 Mar 2026 15:45:02 +0000</pubDate>
      <link>https://dev.to/kesimo/when-html-to-pdf-works-and-when-it-doesnt-a-developers-guide-to-pdf-generation-3fh2</link>
      <guid>https://dev.to/kesimo/when-html-to-pdf-works-and-when-it-doesnt-a-developers-guide-to-pdf-generation-3fh2</guid>
      <description>&lt;p&gt;You have a simple task: generate a PDF from your web app. The instinct is obvious: render HTML, print to PDF, done. After all, you already know HTML and CSS, and your content is probably already in some templated HTML format.&lt;/p&gt;

&lt;p&gt;This works fine until it doesn't. Page breaks split tables in half. Fonts render differently on your server versus your laptop. Headers and footers need manual positioning. And don't get started on multi-column layouts or page numbers in table of contents.&lt;/p&gt;

&lt;p&gt;Here's when HTML-to-PDF makes sense, when it falls apart, and what to use instead.&lt;/p&gt;

&lt;h2&gt;
  
  
  The HTML-to-PDF Illusion
&lt;/h2&gt;

&lt;p&gt;HTML was designed for screens, not paper. When you convert HTML to PDF, you're forcing a screen layout engine to think in pages. This works reasonably well for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Simple reports&lt;/strong&gt; with linear content flow&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Existing HTML content&lt;/strong&gt; you can't easily restructure&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Quick prototypes&lt;/strong&gt; where pixel-perfect output isn't critical&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The common tools in this space all share the same underlying approach: spin up a headless browser, render your HTML, capture the output.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Puppeteer example&lt;/span&gt;
npx puppeteer print ./report.html ./report.pdf

&lt;span class="c"&gt;# wkhtmltopdf&lt;/span&gt;
wkhtmltopdf &lt;span class="nt"&gt;--page-size&lt;/span&gt; A4 report.html report.pdf

&lt;span class="c"&gt;# Gotenberg (self-hosted API)&lt;/span&gt;
curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://localhost:3000/convert/html &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-F&lt;/span&gt; &lt;span class="s2"&gt;"file=@report.html"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-o&lt;/span&gt; report.pdf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The problem isn't that these tools don't work. They work fine for simple cases. The problem is what happens when your requirements grow beyond "simple case."&lt;/p&gt;

&lt;h2&gt;
  
  
  Where HTML-to-PDF Breaks Down
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Page Breaks and Layout Control
&lt;/h3&gt;

&lt;p&gt;CSS has &lt;code&gt;page-break-before&lt;/code&gt;, &lt;code&gt;page-break-after&lt;/code&gt;, and &lt;code&gt;break-inside: avoid&lt;/code&gt;. In theory, these give you control. In practice, browser rendering engines optimize for screens first. Complex layouts with multi-column sections, fixed headers, and footers often produce unpredictable breaks.&lt;/p&gt;

&lt;p&gt;Your CSS says "don't break inside this table," but the browser engine has already calculated the page height differently than expected. Now your table header sits alone on page 7 while the data spills to page 8.&lt;/p&gt;

&lt;h3&gt;
  
  
  Consistency Across Environments
&lt;/h3&gt;

&lt;p&gt;Your local Chrome produces a perfect PDF. Your CI pipeline running Chromium produces something almost identical, but the line spacing is slightly different and one image is 2 pixels lower. Same HTML, same CSS, different output.&lt;/p&gt;

&lt;p&gt;This isn't a bug in the tools. It's the nature of browser engines. They're designed for interactive rendering with font substitution, sub-pixel positioning, and GPU acceleration. None of these optimize for deterministic document output.&lt;/p&gt;

&lt;h3&gt;
  
  
  Complex Document Features
&lt;/h3&gt;

&lt;p&gt;Try implementing these in pure HTML/CSS:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Automatic table of contents&lt;/strong&gt; with page numbers&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cross-references&lt;/strong&gt; like "see Figure 3 on page 12"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Academic citations&lt;/strong&gt; with auto-generated bibliography&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi-column layouts&lt;/strong&gt; that reflow correctly across page boundaries&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Headers and footers&lt;/strong&gt; with page numbers, section titles, and total page count&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each is possible with enough JavaScript and post-processing. But you're now building a document engine on top of a layout engine that was never meant for documents.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcms.autype.com%2Fassets%2F53fe089e-e74a-409c-b3df-281144eadfc2" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcms.autype.com%2Fassets%2F53fe089e-e74a-409c-b3df-281144eadfc2" alt="PDF Generation Approaches Comparison" width="6752" height="3804"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Decision Framework: Which Approach to Use
&lt;/h2&gt;

&lt;p&gt;Use this mental model:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Your Need&lt;/th&gt;
&lt;th&gt;Best Approach&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Simple invoice from existing HTML&lt;/td&gt;
&lt;td&gt;HTML-to-PDF&lt;/td&gt;
&lt;td&gt;One-time conversion, no complex layout&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Report with charts and TOC&lt;/td&gt;
&lt;td&gt;Native document engine&lt;/td&gt;
&lt;td&gt;Automatic indices, deterministic output&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;500 personalized contracts&lt;/td&gt;
&lt;td&gt;Template fill + bulk render&lt;/td&gt;
&lt;td&gt;Reuse template, fill variables at scale&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Real-time document from app data&lt;/td&gt;
&lt;td&gt;Markdown/JSON via API&lt;/td&gt;
&lt;td&gt;Schema validation, AI-friendly generation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Complex academic paper&lt;/td&gt;
&lt;td&gt;Native with citations&lt;/td&gt;
&lt;td&gt;Bibliography, cross-references, math&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Let's look at each approach with code examples.&lt;/p&gt;

&lt;h2&gt;
  
  
  Approach 1: Native Document Generation
&lt;/h2&gt;

&lt;p&gt;Instead of HTML, you define documents in a structured format designed for paper. Markdown extended with document primitives, or a JSON schema that describes every element explicitly.&lt;/p&gt;

&lt;p&gt;The key difference: the rendering engine thinks in pages from the start, not after the fact.&lt;/p&gt;

&lt;p&gt;Here's generating a report with a chart and table of contents using the Autype API:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://api.autype.com/api/v1/dev/render/markdown &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"X-API-Key: YOUR_API_KEY"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
    "content": "# Quarterly Report\n\n:::toc\n:::\n\n## Executive Summary\n\nRevenue increased by 23% compared to the previous quarter.\n\n## Sales Data\n\n:::chart{\n  type: \"bar\",\n  width: 400,\n  height: 200\n}\nlabels: [\"Q1\", \"Q2\", \"Q3\", \"Q4\"]\ndatasets:\n  - label: \"Revenue (M$)\"\n    data: [12.4, 15.2, 18.1, 22.3]\n:::\n\n### Breakdown by Region\n\n| Region | Sales | Growth |\n|--------|-------|--------|\n| Europe | 8.2M  | +18%   |\n| NA     | 9.1M  | +25%   |\n| APAC   | 5.0M  | +31%   |\n",
    "document": {
      "type": "pdf",
      "size": "A4"
    },
    "defaults": {
      "fontFamily": "Helvetica",
      "fontSize": 11,
      "header": {
        "left": "Quarterly Report",
        "right": "{{pageNumber}}/{{totalPages}}"
      }
    }
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Response:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"jobId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"r_8f3a2b1c4d5e"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PROCESSING"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"format"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PDF"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"creditCost"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"createdAt"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2024-12-15T10:30:00Z"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The engine handles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Page breaks that respect content boundaries&lt;/li&gt;
&lt;li&gt;Automatic table of contents with page numbers&lt;/li&gt;
&lt;li&gt;Charts rendered inline without external libraries&lt;/li&gt;
&lt;li&gt;Consistent typography across all output&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can also use JSON for more granular control:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;documentJson&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;document&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;pdf&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;A4&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;orientation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;portrait&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;defaults&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;fontFamily&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Helvetica&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;fontSize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;header&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;left&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Quarterly Report&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;right&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;{{pageNumber}}/{{totalPages}}&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;sections&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;main&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;flow&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;h1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Quarterly Report&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;toc&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Contents&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;h2&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Executive Summary&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Revenue increased by 23% compared to the previous quarter.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;chart&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;bar&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Q1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Q2&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Q3&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Q4&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
              &lt;span class="na"&gt;datasets&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Revenue (M$)&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;12.4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;15.2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;18.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;22.3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;}]&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
          &lt;span class="p"&gt;},&lt;/span&gt;
          &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// Validate before rendering&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://api.autype.com/api/v1/dev/render/validate&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;X-API-Key&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AUTYPE_API_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;documentJson&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This validates the schema before you spend credits on rendering. If the structure is invalid, you get specific error paths.&lt;/p&gt;

&lt;h2&gt;
  
  
  Approach 2: Template-Based Bulk Generation
&lt;/h2&gt;

&lt;p&gt;When you have a contract template and need 500 personalized versions, you don't generate each from scratch. You define a template once, mark the variable placeholders, and fill them programmatically.&lt;/p&gt;

&lt;p&gt;The template can be defined in the Autype editor with visual tools, then rendered via API:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Bulk render from a saved template&lt;/span&gt;
curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://api.autype.com/api/v1/dev/bulk-render &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"X-API-Key: YOUR_API_KEY"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
    "documentId": "d_contract_template_2024",
    "format": "PDF",
    "items": [
      {
        "clientName": "Acme Corporation",
        "contractDate": "2024-12-15",
        "amount": "€50,000",
        "projectDescription": "Annual maintenance agreement"
      },
      {
        "clientName": "Beta Industries",
        "contractDate": "2024-12-16",
        "amount": "€125,000",
        "projectDescription": "Platform development phase 2"
      }
    ]
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can also upload a CSV or Excel file with hundreds of rows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://api.autype.com/api/v1/dev/bulk-render/file &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"X-API-Key: YOUR_API_KEY"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-F&lt;/span&gt; &lt;span class="s2"&gt;"file=@contracts.csv"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-F&lt;/span&gt; &lt;span class="s2"&gt;"documentId=d_contract_template_2024"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-F&lt;/span&gt; &lt;span class="s2"&gt;"format=PDF"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each row generates one PDF. All documents render in parallel, and you get a ZIP download when complete.&lt;/p&gt;

&lt;h2&gt;
  
  
  Integrating with Automation Tools
&lt;/h2&gt;

&lt;h3&gt;
  
  
  n8n Workflow
&lt;/h3&gt;

&lt;p&gt;You can connect document generation to any data source using n8n. The Autype n8n node provides 40+ operations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# n8n workflow concept&lt;/span&gt;
&lt;span class="na"&gt;Workflow&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;Trigger&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Webhook receives form submission&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;Node 1&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Extract form data (client name, service, amount)&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;Node 2&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Autype → Render from Markdown template&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;Node 3&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Google Drive → Upload PDF&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;Node 4&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Gmail → Send PDF to client&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The node handles async job polling automatically. You submit the render job, n8n waits for completion, then passes the download URL to the next node.&lt;/p&gt;

&lt;h3&gt;
  
  
  Make.com Integration
&lt;/h3&gt;

&lt;p&gt;For no-code automation, the Make.com integration uses a slightly different variable syntax to avoid conflicts with Make's own templating:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Use ${varName} instead of {{varName}} in your templates
Dear ${clientName},

Your invoice for ${amount} is attached.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This lets Make process its own variables while passing the correct values to Autype.&lt;/p&gt;

&lt;h2&gt;
  
  
  When to Stick with HTML-to-PDF
&lt;/h2&gt;

&lt;p&gt;None of this means HTML-to-PDF is always wrong. Use it when:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Your content is already HTML&lt;/strong&gt; and restructuring would be expensive&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Layout requirements are simple&lt;/strong&gt; (linear flow, no complex tables or multi-column)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You're generating a few documents&lt;/strong&gt; and can manually verify output&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You need self-hosting&lt;/strong&gt; and already have the infrastructure for headless browsers&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The tools are mature and well-documented. Just know their limits.&lt;/p&gt;

&lt;h2&gt;
  
  
  Performance Comparison
&lt;/h2&gt;

&lt;p&gt;For a 50-page document with charts, tables, and headers:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Method&lt;/th&gt;
&lt;th&gt;Render Time&lt;/th&gt;
&lt;th&gt;Output Consistency&lt;/th&gt;
&lt;th&gt;Complex Features&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Puppeteer (HTML)&lt;/td&gt;
&lt;td&gt;8-15 seconds&lt;/td&gt;
&lt;td&gt;Varies by environment&lt;/td&gt;
&lt;td&gt;Manual implementation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;wkhtmltopdf&lt;/td&gt;
&lt;td&gt;5-12 seconds&lt;/td&gt;
&lt;td&gt;Varies by environment&lt;/td&gt;
&lt;td&gt;Manual implementation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Native engine (Autype)&lt;/td&gt;
&lt;td&gt;&amp;lt; 8 seconds&lt;/td&gt;
&lt;td&gt;Deterministic&lt;/td&gt;
&lt;td&gt;Built-in&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Native engines render faster because they don't spin up a browser context. The output is deterministic because the renderer is purpose-built for documents.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Real Question
&lt;/h2&gt;

&lt;p&gt;When choosing a PDF generation approach, the question isn't "which tool is best." The question is "what kind of documents do you actually need to generate?"&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Simple, one-off documents from HTML&lt;/strong&gt;: HTML-to-PDF tools work fine&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Professional documents with TOC, charts, citations&lt;/strong&gt;: Use a native document engine&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;High-volume personalized documents&lt;/strong&gt;: Template fill with bulk rendering&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI-generated documents&lt;/strong&gt;: Markdown/JSON input with schema validation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;HTML is excellent for web pages. Documents are not web pages. Using the right tool for the medium saves hours of debugging layout issues that shouldn't exist in the first place.&lt;/p&gt;

</description>
      <category>automation</category>
      <category>pdf</category>
      <category>api</category>
      <category>learning</category>
    </item>
    <item>
      <title>OCR vs VLM: Why You Need Both (And How Hybrid Approaches Win)</title>
      <dc:creator>Kevin</dc:creator>
      <pubDate>Thu, 19 Mar 2026 21:39:09 +0000</pubDate>
      <link>https://dev.to/kesimo/ocr-vs-vlm-why-you-need-both-and-how-hybrid-approaches-win-5bo4</link>
      <guid>https://dev.to/kesimo/ocr-vs-vlm-why-you-need-both-and-how-hybrid-approaches-win-5bo4</guid>
      <description>&lt;p&gt;Document processing has been stuck in a binary choice for years: use traditional OCR for speed and reliability, or use AI vision models for understanding. The industry treated these as competing approaches. That framing was wrong.&lt;/p&gt;

&lt;p&gt;The best document processing systems today combine both. Traditional OCR handles what it excels at: extracting raw text with high accuracy and minimal computational cost. Vision Language Models (VLMs) handle what OCR cannot: understanding layout, detecting styles, reconstructing document structure.&lt;/p&gt;

&lt;p&gt;This is not a competition. It is a stack.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Traditional OCR Actually Does Well
&lt;/h2&gt;

&lt;p&gt;Optical Character Recognition has been around since the 1950s. Modern OCR engines like Tesseract or cloud-based APIs are remarkably good at one specific task: converting pixels to characters.&lt;/p&gt;

&lt;p&gt;When you throw a scanned document at a traditional OCR engine, it performs several steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Binarization&lt;/strong&gt; — Convert the image to black and white to isolate text&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Layout analysis&lt;/strong&gt; — Identify text regions vs. image regions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Line and word segmentation&lt;/strong&gt; — Break text into processable units&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Character recognition&lt;/strong&gt; — Match glyphs to characters using trained models&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Post-processing&lt;/strong&gt; — Apply language models to fix recognition errors&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The output is a stream of text. Sometimes with bounding boxes. Sometimes with basic formatting hints.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Typical OCR output structure
&lt;/span&gt;&lt;span class="n"&gt;ocr_result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Invoice #12345&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;Date: 2024-01-15&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;Total: $1,250.00&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;confidence&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.94&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;blocks&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Invoice #12345&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bbox&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;]},&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Date: 2024-01-15&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bbox&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;90&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;280&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;120&lt;/span&gt;&lt;span class="p"&gt;]},&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Total: $1,250.00&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bbox&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;130&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;280&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;160&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This works well for straightforward documents. Clean scans. Simple layouts. Text-heavy content.&lt;/p&gt;

&lt;p&gt;But traditional OCR has a fundamental blind spot: it sees characters, not documents.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where Traditional OCR Fails
&lt;/h2&gt;

&lt;p&gt;Consider what OCR loses when processing a professional document:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Typography and styling&lt;/strong&gt; — OCR extracts "Introduction" but does not capture that it is a 24pt bold heading in the corporate font, colored in brand blue.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Spatial relationships&lt;/strong&gt; — OCR reads columns sequentially, often mangling multi-column layouts where text flows left-to-right, then continues in the next column.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tables&lt;/strong&gt; — OCR extracts cell contents as a linear text stream. The structure of rows and columns must be reconstructed through heuristics, often incorrectly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Headers and footers&lt;/strong&gt; — OCR treats repeated page headers as content, duplicating text across every page.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Images and figures&lt;/strong&gt; — OCR either ignores images entirely or provides no context about their position, captions, or relationship to surrounding text.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Section hierarchy&lt;/strong&gt; — OCR cannot distinguish between a chapter heading, a section heading, and a paragraph. The document's outline is lost.&lt;/p&gt;

&lt;p&gt;The result is a flat text file where all document semantics have been stripped away. For search indexing, this might be sufficient. For document reconstruction, it is useless.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Vision Language Models Bring
&lt;/h2&gt;

&lt;p&gt;Vision Language Models take a fundamentally different approach. Instead of processing text as a sequence of characters, they process the entire page as an image and generate structured output based on visual understanding.&lt;/p&gt;

&lt;p&gt;A VLM sees the document the way a human does. It recognizes that large bold text at the top is a title. It understands that text arranged in a grid with borders is a table. It notices that a page number in the footer should not be part of the content.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# VLM-style structured output
&lt;/span&gt;&lt;span class="n"&gt;vlm_result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;title&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Q4 Financial Report&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sections&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;heading&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Executive Summary&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;level&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Revenue increased by 23% year-over-year...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;heading&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Regional Breakdown&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;level&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;table&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;headers&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Region&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Revenue&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Growth&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rows&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;North America&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;$2.1M&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;+18%&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Europe&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;$1.8M&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;+27%&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Asia Pacific&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;$0.9M&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;+31%&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
                &lt;span class="p"&gt;]&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;metadata&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;page_count&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;has_cover_page&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;contains_charts&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;true&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;VLMs excel at understanding document structure. They can identify:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Document type (invoice, contract, report, letter)&lt;/li&gt;
&lt;li&gt;Section hierarchy and nesting&lt;/li&gt;
&lt;li&gt;Tables with proper cell relationships&lt;/li&gt;
&lt;li&gt;Figures, charts, and their captions&lt;/li&gt;
&lt;li&gt;Reading order in complex layouts&lt;/li&gt;
&lt;li&gt;Styling patterns (headings, body text, emphasis)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But VLMs have their own weaknesses.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where VLMs Struggle
&lt;/h2&gt;

&lt;p&gt;Vision Language Models are computationally expensive. Processing a single page through a capable VLM takes significantly longer than traditional OCR. For bulk document processing, this adds up quickly.&lt;/p&gt;

&lt;p&gt;More importantly, VLMs can hallucinate. They might:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Invent text that does not exist in the document&lt;/li&gt;
&lt;li&gt;Misread specific numbers or proper nouns&lt;/li&gt;
&lt;li&gt;Transcribe similar-looking characters incorrectly (O vs 0, l vs 1)&lt;/li&gt;
&lt;li&gt;Generate plausible but incorrect structural assumptions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For a legal contract or financial statement, hallucinated text is unacceptable. A missing zero in a dollar amount changes the meaning entirely.&lt;/p&gt;

&lt;p&gt;Traditional OCR, despite its limitations, is deterministic. The same input produces the same output. It does not invent content. Its confidence scores are calibrated and reliable.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Hybrid Approach: Using Both Together
&lt;/h2&gt;

&lt;p&gt;The solution is not to choose one or the other. The solution is to use each for what it does best.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Architecture pattern: OCR for extraction, VLM for structure&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcms.autype.com%2Fassets%2Ff45c7094-0e8d-46a5-b653-1cc377082c66" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcms.autype.com%2Fassets%2Ff45c7094-0e8d-46a5-b653-1cc377082c66" alt="Hybrid OCR and VLM Pipeline Diagram" width="4776" height="1704"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here is how a hybrid pipeline works in practice:&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: OCR extracts text and bounding boxes
&lt;/h3&gt;

&lt;p&gt;The OCR engine processes each page and returns character-level extraction with coordinates. This provides the authoritative text content.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: VLM analyzes layout and structure
&lt;/h3&gt;

&lt;p&gt;The same page image goes to a VLM with a prompt focused on structure, not transcription:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Analyze this document page. Identify:
1. Document type and structure
2. Section headings and their hierarchy (h1-h6)
3. Table locations and dimensions
4. Image and figure positions
5. Reading order for multi-column layouts
6. Header/footer regions
7. Styling patterns (fonts, colors, emphasis)

Do not transcribe text. Describe structure only.

Note: This is a simplified example prompt. In production, 
you would include more specific instructions about output 
format, error handling, and edge cases for your document types.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 3: Merge OCR text with VLM structure
&lt;/h3&gt;

&lt;p&gt;The structured output from the VLM is populated with text from the OCR extraction. Where the VLM identified a heading, insert the OCR text from that region. Where the VLM detected a table, use OCR results to fill cells accurately.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4: Validation and confidence scoring
&lt;/h3&gt;

&lt;p&gt;Cross-reference the OCR text against the VLM's transcription. Flag discrepancies for human review. High-confidence OCR text takes precedence over VLM transcription for critical fields like numbers and proper nouns.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real-World Applications
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Invoice Processing
&lt;/h3&gt;

&lt;p&gt;Traditional OCR extracts line items and totals but often mangles table structures. A VLM identifies the table grid, maps columns to their headers, and understands that the total at the bottom is the sum. Combined, you get accurate financial data extraction.&lt;/p&gt;

&lt;h3&gt;
  
  
  Contract Analysis
&lt;/h3&gt;

&lt;p&gt;OCR provides verbatim text for compliance checking. VLM identifies clauses, obligations, dates, and parties. Together, they enable automated contract review where both accuracy and structure matter.&lt;/p&gt;

&lt;h3&gt;
  
  
  Document Digitization
&lt;/h3&gt;

&lt;p&gt;Converting scanned archives to editable formats requires more than text. Page layouts, fonts, section breaks, and styling must be preserved. Hybrid processing reconstructs the document as it originally appeared, not just its text content.&lt;/p&gt;

&lt;p&gt;This is exactly what Autype Lens does. It uses a hybrid approach where OCR handles reliable text extraction while AI vision models analyze layout, detect styling patterns, and reconstruct document structure. The output is not flat text but a fully styled, editable document. You can try it at &lt;a href="https://autype.com/lens" rel="noopener noreferrer"&gt;autype.com/lens&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Performance Considerations
&lt;/h2&gt;

&lt;p&gt;Hybrid processing is not free. You are running two models per page. But the tradeoff is worth it for most document processing workflows:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Approach&lt;/th&gt;
&lt;th&gt;Accuracy&lt;/th&gt;
&lt;th&gt;Structure&lt;/th&gt;
&lt;th&gt;Speed&lt;/th&gt;
&lt;th&gt;Cost&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;OCR only&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;Fast&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;VLM only&lt;/td&gt;
&lt;td&gt;Medium*&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;Slow&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hybrid&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;*VLM accuracy varies significantly based on document complexity and model capability.&lt;/p&gt;

&lt;p&gt;For high-volume processing where structure does not matter (search indexing, archival), stick with OCR. For document reconstruction, analysis, or conversion, hybrid is the right choice.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Future Is Not Either-Or
&lt;/h2&gt;

&lt;p&gt;The document processing industry spent years debating OCR versus AI. The debate missed the point. These are layers in a stack, not alternatives in a menu.&lt;/p&gt;

&lt;p&gt;Traditional OCR provides the foundation: reliable, deterministic text extraction. Vision Language Models provide the understanding: structure, semantics, layout. Together, they transform flat text back into documents.&lt;/p&gt;

&lt;p&gt;If you are building document processing pipelines, do not choose. Use both.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>architecture</category>
      <category>llm</category>
      <category>machinelearning</category>
    </item>
    <item>
      <title>5 Business Automations That Save 20+ Hours Per Week</title>
      <dc:creator>Kevin</dc:creator>
      <pubDate>Wed, 18 Mar 2026 10:16:50 +0000</pubDate>
      <link>https://dev.to/kesimo/5-business-automations-that-save-20-hours-per-week-lao</link>
      <guid>https://dev.to/kesimo/5-business-automations-that-save-20-hours-per-week-lao</guid>
      <description>&lt;p&gt;Document automation has evolved far beyond simple templates. In 2026, businesses are building intelligent pipelines that generate contracts, invoices, and marketing assets directly from structured data. This reduces manual work, eliminates errors, and scales effortlessly.&lt;/p&gt;

&lt;p&gt;This article covers five proven document automation and media generation workflows. Each includes an architecture diagram, implementation details, and real-world benefits you can apply today.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use Case 1: Automated Contract Generation from CRM Data
&lt;/h2&gt;

&lt;p&gt;After a deal closes, sales teams waste hours populating contracts manually. Document automation solves this by generating contracts directly from CRM data using &lt;a href="https://autype.com" rel="noopener noreferrer"&gt;Autype&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Architecture
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcms.autype.com%2Fassets%2F18bb1b01-3da9-40fd-9033-5e5c84afd7e0" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcms.autype.com%2Fassets%2F18bb1b01-3da9-40fd-9033-5e5c84afd7e0" alt="Contract Generation Architecture" width="1584" height="5720"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Implementation
&lt;/h3&gt;

&lt;p&gt;When a deal stage changes to "Closed Won" in your CRM, a webhook triggers an n8n workflow. The workflow extracts client details, deal terms, and pricing, then calls the Autype API:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://api.autype.com/api/v1/dev/render &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"X-API-Key: your-api-key"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
    "config": {
      "document": {
        "type": "pdf",
        "size": "A4"
      },
      "variables": {
        "clientName": "Acme Corporation",
        "dealValue": "€45,000",
        "startDate": "2026-04-01",
        "contractDuration": "12 months"
      },
      "sections": [
        {
          "id": "contract-body",
          "type": "flow",
          "content": [
            {
              "type": "h1",
              "text": "Service Agreement"
            },
            {
              "type": "text",
              "text": "This agreement is between **{{clientName}}** and YourCompany..."
            }
          ]
        }
      ]
    }
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Poll for completion, download the PDF, and send it via email or DocuSign.&lt;/p&gt;

&lt;h3&gt;
  
  
  Benefits
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;15-20 minutes saved per contract&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Zero copy-paste errors&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Complete audit trail&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Use Case 2: Bulk Invoice Generation from CSV Data
&lt;/h2&gt;

&lt;p&gt;Month-end invoice runs bottleneck finance teams. Bulk document automation on &lt;a href="https://autype.com" rel="noopener noreferrer"&gt;Autype&lt;/a&gt; generates hundreds of invoices in minutes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Architecture
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcms.autype.com%2Fassets%2F577f9ea3-fa98-4fe2-bb95-1d2381fd8a8d" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcms.autype.com%2Fassets%2F577f9ea3-fa98-4fe2-bb95-1d2381fd8a8d" alt="Bulk Invoice Generation Architecture" width="2636" height="5720"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Implementation
&lt;/h3&gt;

&lt;p&gt;Create an invoice template with variables for client name, invoice number, and line items. Upload your CSV and trigger bulk rendering:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://api.autype.com/api/v1/dev/bulk-render/file &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"X-API-Key: your-api-key"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-F&lt;/span&gt; &lt;span class="s2"&gt;"file=@invoices-march-2026.csv"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-F&lt;/span&gt; &lt;span class="s2"&gt;"documentId=550e8400-e29b-41d4-a716-446655440000"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-F&lt;/span&gt; &lt;span class="s2"&gt;"format=PDF"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The API processes up to 100 invoices in parallel. Download the ZIP when complete.&lt;/p&gt;

&lt;h3&gt;
  
  
  Benefits
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;100 invoices in under 2 minutes&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Consistent branding across all documents&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Works with any ERP or accounting system&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Use Case 3: Social Media Video Generation Pipeline
&lt;/h2&gt;

&lt;p&gt;Content teams waste hours creating video variants for different platforms. Automated media pipelines from &lt;a href="https://jsoncut.com" rel="noopener noreferrer"&gt;JsonCut&lt;/a&gt; generate multiple formats from a single configuration.&lt;/p&gt;

&lt;h3&gt;
  
  
  Architecture
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcms.autype.com%2Fassets%2Fd5c77c47-0b1a-4689-8917-00c25e525db7" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcms.autype.com%2Fassets%2Fd5c77c47-0b1a-4689-8917-00c25e525db7" alt="Social Media Video Pipeline Architecture" width="3488" height="3064"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Implementation
&lt;/h3&gt;

&lt;p&gt;Define videos as JSON configurations with template variables:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"video"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"config"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"width"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1080&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"height"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1920&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"defaults"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"duration"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"transition"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"fade"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"duration"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"clips"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"layers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"video"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"path"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/uploads/product-demo.mp4"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"{{productName}}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"position"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"bottom"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Change dimensions per platform, swap variables, and render variants automatically.&lt;/p&gt;

&lt;h3&gt;
  
  
  Benefits
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;30-90 seconds per video&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;No video editing skills required&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Integrates with n8n and Make&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Use Case 4: Customer Onboarding Document Package
&lt;/h2&gt;

&lt;p&gt;New customers need welcome letters, agreements, and guides. Document automation on &lt;a href="https://autype.com" rel="noopener noreferrer"&gt;Autype&lt;/a&gt; creates complete packages on demand.&lt;/p&gt;

&lt;h3&gt;
  
  
  Architecture
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcms.autype.com%2Fassets%2Fd653b0bf-2021-40f0-8549-8781eb01a5e2" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcms.autype.com%2Fassets%2Fd653b0bf-2021-40f0-8549-8781eb01a5e2" alt="Customer Onboarding Package Architecture" width="4580" height="5056"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Implementation
&lt;/h3&gt;

&lt;p&gt;Render each document with customer data, then merge them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://api.autype.com/api/v1/dev/tools/pdf/merge &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"X-API-Key: your-api-key"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
    "fileIds": [
      "welcome-letter-file-id",
      "service-agreement-file-id",
      "sla-document-file-id"
    ]
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Email the merged PDF package automatically.&lt;/p&gt;

&lt;h3&gt;
  
  
  Benefits
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Professional first impression&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Reduced support tickets&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Scales without manual work&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Use Case 5: Marketing Image Generation for E-commerce
&lt;/h2&gt;

&lt;p&gt;Product teams need hundreds of marketing images. Automated image generation on &lt;a href="https://jsoncut.com" rel="noopener noreferrer"&gt;JsonCut&lt;/a&gt; creates them from product feeds.&lt;/p&gt;

&lt;h3&gt;
  
  
  Architecture
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcms.autype.com%2Fassets%2F8c768c9d-3cf2-4c89-838b-2b5fe8216771" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcms.autype.com%2Fassets%2F8c768c9d-3cf2-4c89-838b-2b5fe8216771" alt="Marketing Image Generation Architecture" width="3912" height="3728"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Implementation
&lt;/h3&gt;

&lt;p&gt;Define an image template once, then loop through your product catalog:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"image"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"config"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"width"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"height"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;630&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"layers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"gradient"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"gradient"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"colors"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"#667eea"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"#764ba2"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"direction"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"diagonal"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"image"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"path"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"{{productImage}}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"x"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"y"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"width"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"height"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"borderRadius"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"{{productName}}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"x"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;280&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"y"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"fontSize"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;36&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"color"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"#ffffff"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"{{price}}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"x"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;280&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"y"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;140&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"fontSize"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"color"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"#f0f0f0"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Generate each image in 2-10 seconds.&lt;/p&gt;

&lt;h3&gt;
  
  
  Benefits
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Consistent brand guidelines&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Full catalog in hours, not weeks&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Easy updates for pricing changes&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why Document Automation Works
&lt;/h2&gt;

&lt;p&gt;All five patterns follow the same principle: structured data in, professional output out.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Use Case&lt;/th&gt;
&lt;th&gt;Input&lt;/th&gt;
&lt;th&gt;Output&lt;/th&gt;
&lt;th&gt;Time Saved&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Contract generation&lt;/td&gt;
&lt;td&gt;CRM data&lt;/td&gt;
&lt;td&gt;PDF contract&lt;/td&gt;
&lt;td&gt;15-20 min/contract&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bulk invoices&lt;/td&gt;
&lt;td&gt;CSV data&lt;/td&gt;
&lt;td&gt;ZIP of PDFs&lt;/td&gt;
&lt;td&gt;Hours per month&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Social videos&lt;/td&gt;
&lt;td&gt;JSON config&lt;/td&gt;
&lt;td&gt;MP4 files&lt;/td&gt;
&lt;td&gt;Hours per video&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Onboarding package&lt;/td&gt;
&lt;td&gt;Customer data&lt;/td&gt;
&lt;td&gt;Merged PDF&lt;/td&gt;
&lt;td&gt;30 min/customer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Marketing images&lt;/td&gt;
&lt;td&gt;Product feed&lt;/td&gt;
&lt;td&gt;PNG/JPEG files&lt;/td&gt;
&lt;td&gt;Minutes per image&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Both &lt;a href="https://autype.com" rel="noopener noreferrer"&gt;Autype&lt;/a&gt; (documents) and &lt;a href="https://jsoncut.com" rel="noopener noreferrer"&gt;JsonCut&lt;/a&gt; (media) use JSON configurations. This enables programmatic generation without template files or manual intervention.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;p&gt;Both platforms offer free tiers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://autype.com" rel="noopener noreferrer"&gt;Autype&lt;/a&gt;&lt;/strong&gt;: 50 AI credits/month for document automation testing&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://jsoncut.com" rel="noopener noreferrer"&gt;JsonCut&lt;/a&gt;&lt;/strong&gt;: 2,000 tokens/month for media generation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Connect these tools to your CRM, ERP, or product database. Webhooks and CSV exports transform manual document creation into fully automated workflows.&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>automation</category>
      <category>productivity</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Integrating Autype with LLM Agents (MCP): Templates, Schema Validation and Feedback Loops</title>
      <dc:creator>Kevin</dc:creator>
      <pubDate>Mon, 16 Mar 2026 09:06:54 +0000</pubDate>
      <link>https://dev.to/kesimo/integrating-autype-with-llm-agents-mcp-templates-schema-validation-and-feedback-loops-128k</link>
      <guid>https://dev.to/kesimo/integrating-autype-with-llm-agents-mcp-templates-schema-validation-and-feedback-loops-128k</guid>
      <description>&lt;p&gt;When you connect an LLM agent to a document generation system, you quickly hit a wall. The agent produces output, but the output needs to fit a strict schema. One missing field, one wrong type, and the document fails to render. Then there's the feedback problem. How does the agent know what went wrong? How does it fix errors without human intervention?&lt;/p&gt;

&lt;p&gt;Autype solves this through the Model Context Protocol (MCP). Instead of hoping your LLM generates valid documents, you give it tools that enforce validation, provide structured error messages, and enable iterative refinement. This article shows how to build robust LLM-powered document workflows with Autype's MCP server.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcms.autype.com%2Fassets%2F07331ed2-ec9c-4f48-86e4-1906ed691e0d" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcms.autype.com%2Fassets%2F07331ed2-ec9c-4f48-86e4-1906ed691e0d" alt="LLM Agent MCP Integration Flow" width="10164" height="2396"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The MCP Architecture
&lt;/h2&gt;

&lt;p&gt;Autype's MCP server lives at &lt;code&gt;https://mcp.autype.com/mcp/&lt;/code&gt;. It implements MCP 2025-03-26 over Streamable HTTP, which means no installation. Point any MCP-compatible client at the URL and you're connected.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Supported clients:&lt;/strong&gt; Claude Desktop, Claude Code (CLI), Cursor, Windsurf, VS Code with GitHub Copilot, and the MCP Inspector.&lt;/p&gt;

&lt;p&gt;The server exposes two categories of resources:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Tools&lt;/strong&gt; — Actions the LLM can take (render documents, create sessions, manage sections)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Schema Resources&lt;/strong&gt; — Machine-readable schemas the LLM reads to understand valid document structure&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Schema resources are particularly important. Instead of hardcoding document templates, your LLM fetches the current schema from:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;autype://schemas/document&lt;/code&gt; — Complete document JSON schema&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;autype://schemas/defaults&lt;/code&gt; — Styling defaults&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;autype://schemas/variables&lt;/code&gt; — Template variable definitions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The LLM reads these schemas and constructs valid documents from first principles.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building a Template with Schema-First Validation
&lt;/h2&gt;

&lt;p&gt;The key to reliable LLM document generation is validating before rendering. Autype provides a validation endpoint that returns structured errors the LLM can parse and fix.&lt;/p&gt;

&lt;p&gt;Here's how the flow works:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;httpx&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;

&lt;span class="n"&gt;AUTYPE_API_KEY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;your-api-key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;BASE_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://api.autype.com/api/v1/dev&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="c1"&gt;# Step 1: LLM generates document JSON based on schema
&lt;/span&gt;&lt;span class="n"&gt;document_json&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;document&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pdf&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;size&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;A4&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;title&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Q4 Financial Report&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;variables&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;companyName&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Acme Industries&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;reportDate&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2024-12-15&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;totalRevenue&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;number&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;value&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2847500&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;defaults&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fontFamily&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Helvetica&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fontSize&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;color&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;#1a1a1a&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sections&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cover&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;page&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;align&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;center&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;h1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{{companyName}}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Quarterly Financial Report&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{{reportDate}}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fontSize&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;summary&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;flow&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;h2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Executive Summary&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Total revenue for Q4: ${{totalRevenue}}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Step 2: Validate without consuming credits
&lt;/span&gt;&lt;span class="n"&gt;validate_response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;httpx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;BASE_URL&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/render/validate?strict=true&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;X-API-Key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;AUTYPE_API_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Content-Type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;application/json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;config&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;document_json&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;validate_response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;valid&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="c1"&gt;# LLM parses errors and fixes them
&lt;/span&gt;    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;errors&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[]):&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Error at &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;path&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;message&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# Feed errors back to LLM for correction...
&lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Document is valid, ready to render&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The validation endpoint returns structured error objects with a &lt;code&gt;path&lt;/code&gt; field pointing to the exact location in the JSON. This makes it trivial for an LLM to understand what went wrong and apply a fix.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example validation error response:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"valid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"errors"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"path"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sections.0.content.2.type"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Invalid element type 'paragraph'. Did you mean 'text'?"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"code"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"INVALID_ELEMENT_TYPE"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"path"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"variables.totalRevenue"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Number variables require 'type' and 'value' properties"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"code"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"SCHEMA_VIOLATION"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"warnings"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"path"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"defaults.fontFamily"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Font 'Helvetica' not found, falling back to 'Roboto'"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"code"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"FONT_FALLBACK"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Interactive Document Builder Pattern
&lt;/h2&gt;

&lt;p&gt;For complex documents, the MCP server provides an iterative builder workflow. Instead of generating the entire document at once, the LLM creates a session and adds sections incrementally.&lt;/p&gt;

&lt;p&gt;This pattern works well for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Long documents where you want to validate each section&lt;/li&gt;
&lt;li&gt;Documents that require multiple rounds of human feedback&lt;/li&gt;
&lt;li&gt;Templates where structure is fixed but content varies&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here's the MCP tool sequence:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Create session:&lt;/strong&gt; &lt;code&gt;builder_create_session&lt;/code&gt; returns a session ID&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Set defaults:&lt;/strong&gt; &lt;code&gt;builder_update_defaults&lt;/code&gt; defines fonts, colors, spacing&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Set variables:&lt;/strong&gt; &lt;code&gt;builder_set_variables&lt;/code&gt; defines template placeholders&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Add sections:&lt;/strong&gt; &lt;code&gt;builder_add_section&lt;/code&gt; adds content section by section&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Render:&lt;/strong&gt; &lt;code&gt;builder_render&lt;/code&gt; produces the final output&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cleanup:&lt;/strong&gt; &lt;code&gt;builder_delete_session&lt;/code&gt; removes temporary state&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The LLM can call &lt;code&gt;builder_update_section&lt;/code&gt; at any point to fix issues without starting over.&lt;/p&gt;

&lt;h3&gt;
  
  
  Agent Prompt Template for Document Generation
&lt;/h3&gt;

&lt;p&gt;Here's a prompt template you can use with Claude or other LLMs connected to Autype's MCP server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;You are a document generation agent connected to Autype via MCP.

WORKFLOW:
1. Fetch the document schema from autype://schemas/document
2. Create a builder session using builder_create_session
3. Set document defaults (fonts, colors) using builder_update_defaults
4. Define template variables using builder_set_variables
5. Add sections one at a time using builder_add_section
6. After each section, validate the session state
7. If validation fails, fix errors and retry
8. When complete, render using builder_render

VALIDATION RULES:
- Every section must have: id, type ("flow" or "page"), content array
- Every content element must have a valid "type": text, h1-h6, image, table, list, code, math, chart
- Variable names must match pattern: ^[a-zA-Z][a-zA-Z0-9_]*$
- Variable placeholders in text: {{varName}} or ${varName}

ERROR HANDLING:
- Parse validation errors carefully
- Fix the specific path mentioned in the error
- Re-validate before attempting to render
- Never guess schema structure — always reference the schema

OUTPUT:
- Return the download URL from the render response
- Summarize what was generated
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Variable Types and Data Binding
&lt;/h2&gt;

&lt;p&gt;Autype supports multiple variable types beyond simple text. This matters when your LLM agent needs to inject structured data like tables or images.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Variable type schema:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"companyName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Acme Industries"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"logo"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"image"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"src"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://cdn.example.com/logo.png"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"width"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"align"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"center"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"features"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"list"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"items"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"Real-time collaboration"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Version control"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"API access"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"ordered"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"pricingTable"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"table"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"columns"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"Plan"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Price"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Credits"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"Free"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"€0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"50/month"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"Writer"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"€9"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"400/month"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"Pro"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"€25"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1,500/month"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"annualRevenue"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"number"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1250000&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When the LLM generates a document with these variables, placeholders like &lt;code&gt;{{companyName}}&lt;/code&gt; get replaced, while structured types (image, list, table) render as full elements.&lt;/p&gt;

&lt;h2&gt;
  
  
  Human-in-the-Loop Feedback
&lt;/h2&gt;

&lt;p&gt;The real power of MCP integration emerges when you add human feedback to the loop. Here's a typical workflow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;LLM generates initial document&lt;/li&gt;
&lt;li&gt;Human reviews in Autype's web editor or downloads the PDF&lt;/li&gt;
&lt;li&gt;Human provides feedback: "Add a third column to the pricing table" or "Change the accent color to blue"&lt;/li&gt;
&lt;li&gt;LLM receives feedback and calls &lt;code&gt;builder_update_section&lt;/code&gt; or &lt;code&gt;builder_update_defaults&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Document re-renders with changes applied&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Because MCP maintains session state, the LLM doesn't need to regenerate the entire document. It modifies specific sections or defaults and re-renders.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example: Iterative Style Refinement
&lt;/h3&gt;

&lt;p&gt;Let's say the human wants to change the document's visual style after reviewing the first draft:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# LLM receives feedback: "Make headings dark blue and increase font size"
&lt;/span&gt;
&lt;span class="n"&gt;style_update&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;defaults&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fontFamily&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Roboto&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fontSize&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;styles&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;h1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fontSize&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;28&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fontWeight&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bold&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;color&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;#1e3a5f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;h2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fontSize&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;22&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fontWeight&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bold&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;color&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;#2c5282&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# MCP call: builder_update_defaults
# Then: builder_render to get updated PDF
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The document structure stays the same, but styling updates cascade through all sections.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bulk Generation for Scale
&lt;/h2&gt;

&lt;p&gt;Once your LLM agent has a validated template, you can scale to hundreds of documents via the bulk render API. This is useful for personalized reports, contracts, or invoices.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Bulk render with variable sets
&lt;/span&gt;&lt;span class="n"&gt;bulk_job&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;documentId&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;template-doc-uuid&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# Saved template from builder
&lt;/span&gt;    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;format&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;PDF&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;items&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;companyName&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Acme Corp&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;totalRevenue&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;number&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;value&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1200000&lt;/span&gt;&lt;span class="p"&gt;}},&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;companyName&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Beta LLC&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;totalRevenue&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;number&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;value&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;850000&lt;/span&gt;&lt;span class="p"&gt;}},&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;companyName&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Gamma Inc&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;totalRevenue&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;number&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;value&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2100000&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;webhook&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;url&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://your-app.com/webhook/autype-complete&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;headers&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;X-Webhook-Secret&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;your-secret&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;httpx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;BASE_URL&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/bulk-render&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;X-API-Key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;AUTYPE_API_KEY&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;bulk_job&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Returns: {"bulkJobId": "job-uuid", "status": "PENDING", "totalItems": 3}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each item in the array produces one document. The webhook fires when all documents are ready, or you can poll the status endpoint.&lt;/p&gt;

&lt;h2&gt;
  
  
  Error Recovery Patterns
&lt;/h2&gt;

&lt;p&gt;Robust LLM workflows need error recovery. Here's a pattern for handling common failure modes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;render_with_retry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;document_json&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_attempts&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;attempt&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_attempts&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="c1"&gt;# Validate first
&lt;/span&gt;        &lt;span class="n"&gt;validation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;validate_document&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;document_json&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;validation&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;valid&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
            &lt;span class="c1"&gt;# Feed errors back to LLM for correction
&lt;/span&gt;            &lt;span class="n"&gt;corrected&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;llm_fix_errors&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;document_json&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;validation&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;errors&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
            &lt;span class="n"&gt;document_json&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;corrected&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt;

        &lt;span class="c1"&gt;# Attempt render
&lt;/span&gt;        &lt;span class="n"&gt;render_result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;render_document&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;document_json&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;render_result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;FAILED&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="c1"&gt;# Parse error and determine if retryable
&lt;/span&gt;            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;insufficient credits&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;render_result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;CreditsExhaustedError&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

            &lt;span class="c1"&gt;# Re-validate in case of runtime errors
&lt;/span&gt;            &lt;span class="k"&gt;continue&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;render_result&lt;/span&gt;

    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;MaxRetriesExceededError&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;llm_fix_errors&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;document_json&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# Construct prompt with error details
&lt;/span&gt;    &lt;span class="n"&gt;error_context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;- &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;path&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;message&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; (code: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;code&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;
    &lt;span class="p"&gt;])&lt;/span&gt;

    &lt;span class="n"&gt;prompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    The document JSON failed validation with these errors:
    &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;error_context&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;

    Return corrected JSON that fixes all issues.
    Reference the schema at autype://schemas/document for valid structure.
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

    &lt;span class="c1"&gt;# LLM call to fix the document
&lt;/span&gt;    &lt;span class="n"&gt;corrected&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;call_llm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;corrected&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This pattern creates a self-healing loop where validation errors become context for the LLM to apply fixes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practical Integration with Claude Code
&lt;/h2&gt;

&lt;p&gt;If you're using Claude Code (the CLI), connecting to Autype is straightforward:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# In your Claude Code config (claude_desktop_config.json or .claude/config.json)&lt;/span&gt;
&lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="s2"&gt;"mcpServers"&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;"autype"&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;
      &lt;span class="s2"&gt;"url"&lt;/span&gt;: &lt;span class="s2"&gt;"https://mcp.autype.com/mcp/"&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once connected, Claude has access to all Autype tools. You can say things like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"Create a quarterly report template with variables for company name, date, and revenue"&lt;/li&gt;
&lt;li&gt;"Validate this document JSON before rendering"&lt;/li&gt;
&lt;li&gt;"Add a two-column layout section to the existing document"&lt;/li&gt;
&lt;li&gt;"Generate 50 personalized contracts from this template using the data in contracts.csv"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Claude reads the schemas, constructs valid JSON, validates, and renders.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cost Considerations
&lt;/h2&gt;

&lt;p&gt;Each operation has a credit cost. Understanding this helps you design efficient workflows:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Operation&lt;/th&gt;
&lt;th&gt;Credits&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Validate JSON&lt;/td&gt;
&lt;td&gt;Free&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Single render&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bulk render (per document)&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AI document generation&lt;/td&gt;
&lt;td&gt;80-100&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Validation is free, so always validate before rendering. The bulk render discount (4 vs 5 credits) makes it more economical for batch operations.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Integrating LLM agents with document generation requires three things: a schema the LLM can read, validation that returns structured errors, and tools for iterative refinement. Autype's MCP server provides all three.&lt;/p&gt;

&lt;p&gt;The key patterns:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Schema-first generation:&lt;/strong&gt; LLM fetches schemas, generates valid JSON from the start&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Validation loops:&lt;/strong&gt; Free validation endpoint catches errors before credits are consumed&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Builder sessions:&lt;/strong&gt; Incremental document construction with mid-process corrections&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Human feedback integration:&lt;/strong&gt; MCP sessions persist state, enabling real-time refinement&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bulk scaling:&lt;/strong&gt; Validated templates feed bulk render for personalized documents at scale&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The combination of MCP tools and REST API gives you flexibility. Use MCP for interactive agent workflows, REST for programmatic automation. Both produce the same output formats: PDF, DOCX, ODT.&lt;/p&gt;

&lt;p&gt;For production deployments, wrap the MCP calls in error recovery logic that feeds validation errors back to the LLM. This turns brittle document generation into a robust, self-correcting process.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Resources:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;MCP Server: &lt;a href="https://mcp.autype.com/mcp/" rel="noopener noreferrer"&gt;https://mcp.autype.com/mcp/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;LLM Schemas: &lt;a href="https://autype.com/llm-resources/" rel="noopener noreferrer"&gt;https://autype.com/llm-resources/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;API Docs: &lt;a href="https://docs.autype.com/api-reference" rel="noopener noreferrer"&gt;https://docs.autype.com/api-reference&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;n8n Node: &lt;code&gt;n8n-nodes-autype&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>agents</category>
      <category>llm</category>
      <category>mcp</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Building Scalable Document Pipelines: Mastering Autype API for Bulk Rendering and Webhooks</title>
      <dc:creator>Kevin</dc:creator>
      <pubDate>Sun, 15 Mar 2026 19:04:19 +0000</pubDate>
      <link>https://dev.to/kesimo/building-scalable-document-pipelines-mastering-autype-api-for-bulk-rendering-and-webhooks-4b6a</link>
      <guid>https://dev.to/kesimo/building-scalable-document-pipelines-mastering-autype-api-for-bulk-rendering-and-webhooks-4b6a</guid>
      <description>&lt;p&gt;Enterprise document workflows share a common challenge: generating hundreds or thousands of personalized documents without creating a maintenance nightmare. Invoice batches, quarterly reports, personalized contracts, compliance certificates, each requires unique data merged into a consistent template.&lt;/p&gt;

&lt;p&gt;The Autype API solves this with a dedicated bulk rendering endpoint that processes up to 100 documents per job in parallel, with webhook callbacks for real-time completion notifications. This guide shows you how to architect production-ready document pipelines that scale.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Architecture: How Bulk Rendering Works
&lt;/h2&gt;

&lt;p&gt;Autype separates job submission from rendering. When you submit a bulk render job, it enters a queue and processes asynchronously. Each document in the batch renders independently, and the API collects all outputs into a single ZIP file.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcms.autype.com%2Fassets%2F83ac7a25-88ce-4a73-870c-23db1f1c1d52" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcms.autype.com%2Fassets%2F83ac7a25-88ce-4a73-870c-23db1f1c1d52" alt="Bulk Rendering Workflow" width="9804" height="2180"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This architecture means your application never blocks waiting for renders. You submit the job, receive a job ID, and continue processing other tasks. When rendering completes, Autype either calls your webhook or you poll for status.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting Up Your Environment
&lt;/h2&gt;

&lt;p&gt;Before making API calls, you need an API key from your Autype dashboard (Settings → API Keys). All requests use the &lt;code&gt;X-API-Key&lt;/code&gt; header for authentication.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Store your API key as an environment variable&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;AUTYPE_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"your-api-key-here"&lt;/span&gt;

&lt;span class="c"&gt;# Base URL for all API calls&lt;/span&gt;
&lt;span class="nv"&gt;BASE_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"https://api.autype.com/api/v1/dev"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Understanding Template Variables
&lt;/h2&gt;

&lt;p&gt;The bulk rendering system relies on template variables defined in your document. Variables are placeholders that get replaced with actual data at render time. Autype supports several variable types.&lt;/p&gt;

&lt;h3&gt;
  
  
  Simple Text Variables
&lt;/h3&gt;

&lt;p&gt;The most common use case is text substitution. In your document template, use &lt;code&gt;{{variableName}}&lt;/code&gt; syntax:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# Invoice for {{clientName}}&lt;/span&gt;

Date: {{invoiceDate}}
Due: {{dueDate}}

Total: {{currency}}{{totalAmount}}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When rendering, you provide the values:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"clientName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Acme Corporation"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"invoiceDate"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2024-01-15"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"dueDate"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2024-02-15"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"currency"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"€"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"totalAmount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"12,500.00"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Complex Variable Types
&lt;/h3&gt;

&lt;p&gt;For more sophisticated templates, Autype supports structured variable types:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;List Variables&lt;/strong&gt; for dynamic bullet points:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"services"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"list"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"items"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"Consulting"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Implementation"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Training"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"ordered"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Table Variables&lt;/strong&gt; for invoice line items or product catalogs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"lineItems"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"table"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"columns"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"Description"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Quantity"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Unit Price"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Total"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"Consulting Hours"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"40"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"€150"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"€6,000"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"Software License"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"€5,000"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"€5,000"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"Support Package"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"12"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"€125"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"€1,500"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Image Variables&lt;/strong&gt; for logos and signatures:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"companyLogo"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"image"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"src"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://your-cdn.com/logo.png"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"width"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"align"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"center"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Creating a Bulk Render Job
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;POST /bulk-render&lt;/code&gt; endpoint accepts a JSON payload with your template document ID, output format, and an array of variable sets.&lt;/p&gt;

&lt;h3&gt;
  
  
  Basic Bulk Render Request
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="s2"&gt;"https://api.autype.com/api/v1/dev/bulk-render"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"X-API-Key: &lt;/span&gt;&lt;span class="nv"&gt;$AUTYPE_API_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
    "documentId": "550e8400-e29b-41d4-a716-446655440000",
    "format": "PDF",
    "items": [
      {
        "clientName": "Acme Corporation",
        "invoiceDate": "2024-01-15",
        "totalAmount": "€12,500.00"
      },
      {
        "clientName": "Beta Industries",
        "invoiceDate": "2024-01-15",
        "totalAmount": "€8,750.00"
      },
      {
        "clientName": "Gamma Solutions",
        "invoiceDate": "2024-01-15",
        "totalAmount": "€15,200.00"
      }
    ]
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The response includes a &lt;code&gt;bulkJobId&lt;/code&gt; for tracking:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"bulkJobId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"bulk_abc123def456"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PENDING"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"format"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PDF"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"totalItems"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"completedItems"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"failedItems"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"createdAt"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2024-01-15T10:30:00.000Z"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Using Files for Bulk Data
&lt;/h3&gt;

&lt;p&gt;For large batches, uploading a CSV or Excel file is more practical than embedding data in JSON. The &lt;code&gt;POST /bulk-render/file&lt;/code&gt; endpoint accepts multipart form data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="s2"&gt;"https://api.autype.com/api/v1/dev/bulk-render/file"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"X-API-Key: &lt;/span&gt;&lt;span class="nv"&gt;$AUTYPE_API_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-F&lt;/span&gt; &lt;span class="s2"&gt;"file=@invoices.csv"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-F&lt;/span&gt; &lt;span class="s2"&gt;"documentId=550e8400-e29b-41d4-a716-446655440000"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-F&lt;/span&gt; &lt;span class="s2"&gt;"format=PDF"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your CSV file should have column headers matching your variable names:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;clientName,invoiceDate,totalAmount
Acme Corporation,2024-01-15,€12,500.00
Beta Industries,2024-01-15,€8,750.00
Gamma Solutions,2024-01-15,€15,200.00
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Implementing Webhook Callbacks
&lt;/h2&gt;

&lt;p&gt;Polling works for small batches, but webhooks are essential for production systems. When you include a webhook configuration, Autype sends a POST request to your URL when the job completes or fails.&lt;/p&gt;

&lt;h3&gt;
  
  
  Webhook Configuration
&lt;/h3&gt;

&lt;p&gt;Add the &lt;code&gt;webhook&lt;/code&gt; object to your bulk render request:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="s2"&gt;"https://api.autype.com/api/v1/dev/bulk-render"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"X-API-Key: &lt;/span&gt;&lt;span class="nv"&gt;$AUTYPE_API_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
    "documentId": "550e8400-e29b-41d4-a716-446655440000",
    "format": "PDF",
    "items": [...],
    "webhook": {
      "url": "https://your-app.com/webhooks/autype/bulk-complete",
      "headers": {
        "X-Webhook-Secret": "your-webhook-secret"
      }
    }
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Handling Webhook Payloads
&lt;/h3&gt;

&lt;p&gt;Your webhook endpoint receives a POST with the job status:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"bulkJobId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"bulk_abc123def456"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"COMPLETED"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"format"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PDF"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"totalItems"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"completedItems"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;98&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"failedItems"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"downloadUrl"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://api.autype.com/api/v1/dev/bulk-render/bulk_abc123def456/download?token=signed-token"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"completedAt"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2024-01-15T10:35:22.000Z"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A typical webhook handler in Node.js:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Express.js webhook handler&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/webhooks/autype/bulk-complete&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;bulkJobId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;downloadUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;completedItems&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;failedItems&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// Verify webhook secret&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;secret&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;x-webhook-secret&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;secret&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;WEBHOOK_SECRET&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;401&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Invalid secret&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;COMPLETED&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Bulk job &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;bulkJobId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; completed: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;completedItems&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; successful, &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;failedItems&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; failed`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Download the ZIP file&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;downloadUrl&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;buffer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="c1"&gt;// Process the ZIP (store in S3, send to users, etc.)&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;storeDocuments&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bulkJobId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Trigger next workflow step&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;notifyComplete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bulkJobId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;completedItems&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;failedItems&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;FAILED&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Bulk job &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;bulkJobId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; failed`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;alertTeam&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bulkJobId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;OK&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Polling for Job Status
&lt;/h2&gt;

&lt;p&gt;If webhooks are not an option, poll the status endpoint:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="s2"&gt;"https://api.autype.com/api/v1/dev/bulk-render/bulk_abc123def456"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"X-API-Key: &lt;/span&gt;&lt;span class="nv"&gt;$AUTYPE_API_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Response when processing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"bulkJobId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"bulk_abc123def456"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PROCESSING"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"format"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PDF"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"totalItems"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"completedItems"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;45&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"failedItems"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"createdAt"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2024-01-15T10:30:00.000Z"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Response when complete:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"bulkJobId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"bulk_abc123def456"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"COMPLETED"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"format"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PDF"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"totalItems"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"completedItems"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"failedItems"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"downloadUrl"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://api.autype.com/api/v1/dev/bulk-render/bulk_abc123def456/download?token=..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"completedAt"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2024-01-15T10:35:22.000Z"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Implementing Exponential Backoff Polling
&lt;/h3&gt;

&lt;p&gt;For production systems, implement exponential backoff:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;wait_for_bulk_job&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bulk_job_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_wait_seconds&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;600&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;base_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://api.autype.com/api/v1/dev&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;X-API-Key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;wait_time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;  &lt;span class="c1"&gt;# Start with 2 seconds
&lt;/span&gt;    &lt;span class="n"&gt;elapsed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;elapsed&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;max_wait_seconds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;base_url&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/bulk-render/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;bulk_job_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;COMPLETED&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;downloadUrl&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;FAILED&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Bulk job failed: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# Still processing, wait and retry
&lt;/span&gt;        &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;wait_time&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;elapsed&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;wait_time&lt;/span&gt;
        &lt;span class="n"&gt;wait_time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;wait_time&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;1.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Cap at 30 seconds
&lt;/span&gt;
    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;TimeoutError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Bulk job did not complete within &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;max_wait_seconds&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; seconds&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Downloading the Results
&lt;/h2&gt;

&lt;p&gt;When a bulk job completes, download the ZIP file containing all rendered documents:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Using the signed download URL (no API key needed)&lt;/span&gt;
curl &lt;span class="nt"&gt;-o&lt;/span&gt; batch.zip &lt;span class="s2"&gt;"https://api.autype.com/api/v1/dev/bulk-render/bulk_abc123def456/download?token=..."&lt;/span&gt;

&lt;span class="c"&gt;# Or with API key authentication&lt;/span&gt;
curl &lt;span class="nt"&gt;-o&lt;/span&gt; batch.zip &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"X-API-Key: &lt;/span&gt;&lt;span class="nv"&gt;$AUTYPE_API_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"https://api.autype.com/api/v1/dev/bulk-render/bulk_abc123def456/download"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The ZIP file structure preserves order, with documents named by their index:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;batch.zip
├── document_001.pdf
├── document_002.pdf
├── document_003.pdf
└── ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Error Handling and Retry Logic
&lt;/h2&gt;

&lt;p&gt;Production pipelines need robust error handling. Bulk jobs can have partial failures where some documents succeed and others fail.&lt;/p&gt;

&lt;h3&gt;
  
  
  Handling Partial Failures
&lt;/h3&gt;

&lt;p&gt;Check the &lt;code&gt;failedItems&lt;/code&gt; count in the webhook payload or status response:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;failedItems&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Log which items failed&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;warn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;failedItems&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; documents failed to render`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Your retry logic here&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;failedIndices&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;identifyFailedItems&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bulkJobId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;retryFailedItems&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;failedIndices&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Validation Before Submission
&lt;/h3&gt;

&lt;p&gt;Use the validation endpoint to catch errors before submitting a bulk job:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="s2"&gt;"https://api.autype.com/api/v1/dev/render/validate"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"X-API-Key: &lt;/span&gt;&lt;span class="nv"&gt;$AUTYPE_API_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
    "config": {
      "document": { "type": "pdf" },
      "variables": { "testVar": "value" },
      "sections": [...]
    }
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Response for invalid documents:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"valid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"errors"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"path"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sections.0.content.2.text"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Variable {{undefinedVar}} not found in variables object"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"code"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"UNDEFINED_VARIABLE"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Performance Considerations
&lt;/h2&gt;

&lt;p&gt;Bulk rendering performance depends on document complexity. For typical business documents:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Document Size&lt;/th&gt;
&lt;th&gt;Batch of 10&lt;/th&gt;
&lt;th&gt;Batch of 50&lt;/th&gt;
&lt;th&gt;Batch of 100&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;5 pages&lt;/td&gt;
&lt;td&gt;4 seconds&lt;/td&gt;
&lt;td&gt;15 seconds&lt;/td&gt;
&lt;td&gt;28 seconds&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;20 pages&lt;/td&gt;
&lt;td&gt;12 seconds&lt;/td&gt;
&lt;td&gt;45 seconds&lt;/td&gt;
&lt;td&gt;85 seconds&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;50 pages&lt;/td&gt;
&lt;td&gt;25 seconds&lt;/td&gt;
&lt;td&gt;90 seconds&lt;/td&gt;
&lt;td&gt;180 seconds&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Each bulk render item costs 4 credits (vs. 5 for single renders), making bulk rendering both faster and more cost-effective for batches.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real-World Example: Monthly Invoice Generation
&lt;/h2&gt;

&lt;p&gt;Here is a complete example generating monthly invoices from a CRM export:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;fetch&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;node-fetch&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;API_KEY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AUTYPE_API_KEY&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;BASE_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://api.autype.com/api/v1/dev&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;generateMonthlyInvoices&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;invoiceData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;templateId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Submit bulk job&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;BASE_URL&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/bulk-render`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;X-API-Key&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;API_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;documentId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;templateId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;PDF&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;invoiceData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;webhook&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://api.mycompany.com/webhooks/invoices/complete&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;X-Internal-Secret&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;INTERNAL_SECRET&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;job&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Started bulk job &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;job&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bulkJobId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; with &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;job&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;totalItems&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; invoices`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;job&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bulkJobId&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Load invoice data from CSV export&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;csvData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./monthly_invoices.csv&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;utf8&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;invoices&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parseCsv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;csvData&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Generate all invoices&lt;/span&gt;
&lt;span class="nf"&gt;generateMonthlyInvoices&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;invoices&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;550e8400-e29b-41d4-a716-446655440000&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Autype's bulk rendering API provides the foundation for scalable document automation. Key takeaways:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Submit once, render in parallel&lt;/strong&gt; - Jobs process asynchronously with up to 100 documents per batch&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use webhooks&lt;/strong&gt; - Eliminate polling in production; let Autype notify you when work completes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Validate early&lt;/strong&gt; - Catch template errors before submitting large batches&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Handle partial failures&lt;/strong&gt; - Monitor &lt;code&gt;failedItems&lt;/code&gt; and implement retry logic for failed documents&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Leverage file uploads&lt;/strong&gt; - CSV and Excel support simplifies large batch submission&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The combination of parallel processing, webhook notifications, and flexible variable types makes this API suitable for everything from monthly invoice runs to real-time contract generation in your sales pipeline.&lt;/p&gt;




&lt;p&gt;For more details on the document JSON schema and variable types, see the &lt;a href="https://docs.autype.com/api-reference" rel="noopener noreferrer"&gt;Autype API documentation&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>api</category>
      <category>architecture</category>
      <category>automation</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Word Is for Writers. LaTeX Is for Academics. Neither Is for Developers.</title>
      <dc:creator>Kevin</dc:creator>
      <pubDate>Sat, 07 Mar 2026 10:17:56 +0000</pubDate>
      <link>https://dev.to/kesimo/word-is-for-writers-latex-is-for-academics-neither-is-for-developers-3n9h</link>
      <guid>https://dev.to/kesimo/word-is-for-writers-latex-is-for-academics-neither-is-for-developers-3n9h</guid>
      <description>&lt;h2&gt;
  
  
  There's a third option, and it lives in the editor you already have open.
&lt;/h2&gt;

&lt;p&gt;Pick any developer who regularly writes documents longer than a README. Ask them what they use. You'll get one of three answers: Word (reluctantly), Google Docs (with apologetic hand-waving about version control), or LaTeX (with the haunted look of someone who has debugged one too many &lt;code&gt;undefined control sequence&lt;/code&gt; errors at midnight).&lt;/p&gt;

&lt;p&gt;None of them will say they enjoy it. The reason is simple: all three tools were built for someone else.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Wrong Tools for the Job
&lt;/h2&gt;

&lt;p&gt;Word was designed for typing letters. Its fundamental model (WYSIWYG editing where formatting is baked directly into the document) hasn't changed since the early nineties. That works fine if you need to print a letter. It falls apart when you need to version-control a contract, keep styling consistent across a 30-page document touched by six people, or reproduce the same report next quarter without reformatting it from scratch.&lt;/p&gt;

&lt;p&gt;LaTeX is the other extreme. Structurally brilliant, genuinely painful to use. The syntax is verbose and unforgiving. Error messages are famously useless. There is no live preview: you compile, wait, check what broke, fix it, compile again. Nobody who isn't forced to use it chooses it voluntarily in 2026.&lt;/p&gt;

&lt;p&gt;The gap between these two is where most developers are stuck.&lt;/p&gt;




&lt;h2&gt;
  
  
  Markdown Was Already the Answer (Partly)
&lt;/h2&gt;

&lt;p&gt;Developers already solved the documentation problem with Markdown. It's plain text, version-controllable, readable without rendering, writable in any editor. READMEs, changelogs, docs sites, pull request descriptions: all Markdown. No proprietary format, no binary blobs, no formatting drift when someone else opens the file.&lt;/p&gt;

&lt;p&gt;The limitation is that standard Markdown was never designed for professional output. No page layouts, no headers and footers, no auto-numbered figures, no equations, no styled tables, no citations. For a README it's enough. For a quarterly report, a scientific paper, or a client-facing specification it isn't.&lt;/p&gt;

&lt;p&gt;That's the gap Autype closes.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Not Just Use LaTeX for Serious Documents?
&lt;/h2&gt;

&lt;p&gt;This is the reasonable counterargument. LaTeX does have proper content-layout separation. Its output quality is excellent. Cross-references and bibliographies are handled correctly by default. There's a reason it's still the standard in academic publishing.&lt;/p&gt;

&lt;p&gt;The problem is the entry cost and the daily friction. Setting up a working LaTeX environment (distribution, packages, compiler, editor) is its own project before you've written a single word. The workflow is a loop of write-compile-debug that never fully goes away. And when someone outside your team needs to edit or comment on the document, you're converting to DOCX via Pandoc and hoping for the best.&lt;/p&gt;

&lt;p&gt;With Autype, equations use the same LaTeX syntax (&lt;code&gt;$$E = mc^2$$&lt;/code&gt;). The output quality is comparable. But the rest of the document is normal Markdown, the setup is an extension install and an API key, and exporting to DOCX is a single click. You keep the math without the ceremony.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Extended Markdown Actually Looks Like
&lt;/h2&gt;

&lt;p&gt;Autype's syntax is a strict superset of standard Markdown. Everything you already write keeps working. What's added is the layer that turns a text file into a print-ready document.&lt;/p&gt;

&lt;p&gt;This is a single excerpt from the business report example that ships with the extension:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gu"&gt;## Revenue Overview {#revenue-overview}&lt;/span&gt;

Total revenue for Q4 {{fiscalYear}} was &lt;span class="gs"&gt;**$4,215,000**&lt;/span&gt;, driven by strong
performance across all product lines.

:::chart{type="bar" title="Quarterly Revenue ($K)" caption="Revenue by Quarter" anchor="fig-revenue"}
labels: Q1, Q2, Q3, Q4
dataset: Subscription | 1850, 2100, 2450, 3035 | #3b82f6
dataset: Services     |  720,  810,  980, 1180 | #22c55e
:::

As shown in &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;Figure {num}&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="sx"&gt;#fig-revenue&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;, subscription revenue grew
consistently throughout the year. The quarter-over-quarter growth rate:

$$
&lt;span class="se"&gt;\t&lt;/span&gt;ext{QoQ Growth} = &lt;span class="se"&gt;\f&lt;/span&gt;rac{R_{Q4} - R_{Q3}}{R_{Q3}} &lt;span class="se"&gt;\t&lt;/span&gt;imes 100&lt;span class="se"&gt;\%&lt;/span&gt; = 22.9&lt;span class="se"&gt;\%&lt;/span&gt;
$$

:::table{caption="Financial Summary" anchor="tab-financial"
         headerBg="#1e293b" headerColor="#ffffff" rowAltBg="#f8fafc"}
| Metric            | Q3 2024    | Q4 2024    | Change  |
|-------------------|------------|------------|---------|
| Total Revenue     | $3,430,000 | $4,215,000 | +22.9%  |
| Gross Margin      | 75.0%      | 78.0%      | +3.0 pp |
| Operating Margin  | 15.2%      | 18.5%      | +3.3 pp |
:::

See &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;Table {num}&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="sx"&gt;#tab-financial&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; for the full breakdown.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;{{fiscalYear}}&lt;/code&gt; is a variable substituted at render time. The bar chart is defined inline (no Excel, no screenshot). The equation is rendered as proper print-quality typography. The styled table has a dark header, alternating rows, a caption, and an anchor. The cross-references (&lt;code&gt;[Figure {num}]&lt;/code&gt;, &lt;code&gt;[Table {num}]&lt;/code&gt;) resolve to sequential numbers in the rendered document, the same way LaTeX's &lt;code&gt;\ref{}&lt;/code&gt; works, without the setup cost.&lt;/p&gt;

&lt;p&gt;The full set of supported elements: bar, line, pie, doughnut, radar, scatter, bubble, and polar area charts; LaTeX math; styled tables with captions and anchors; QR codes (URL, WiFi, vCard); auto-generated table of contents, list of figures, list of tables, list of abbreviations, bibliography; multi-column layouts; page breaks with optional orientation changes; cross-references; headers and footers with logos and page numbers. All plain text.&lt;/p&gt;




&lt;h2&gt;
  
  
  The VS Code Extension
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://marketplace.visualstudio.com/items?itemName=centerbit.autype" rel="noopener noreferrer"&gt;Autype extension&lt;/a&gt; makes this workflow native to VS Code. Open a Markdown file, click Render, and the PDF appears in a side panel next to your editor.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fautype.com%2Fvscode%2Fvscode-demo.gif" class="article-body-image-wrapper"&gt;&lt;img alt="Demo: Markdown editor on the left, live PDF preview on the right — charts, tables, and styled headings visible in the output" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fautype.com%2Fvscode%2Fvscode-demo.gif" width="760" height="453"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A project is a folder with an &lt;code&gt;autype.json&lt;/code&gt; config alongside your content files:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;my-report/
├── autype.json      ← page size, margins, fonts, headers, footers, variables
├── report.md        ← main document
├── appendix.mdd     ← additional document in the same project
└── images/          ← local images, uploaded and cached automatically
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A folder can contain any number of &lt;code&gt;.md&lt;/code&gt; and &lt;code&gt;.mdd&lt;/code&gt; files. Each renders as an independent document, but all share the same &lt;code&gt;autype.json&lt;/code&gt;. A project folder can hold a main report, an executive summary, and an appendix, all with identical styling and zero config duplication.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;autype.json&lt;/code&gt; is where the design lives: font family, sizes, colors, heading hierarchy, header (logo and document title), footer (page numbers), variables. Content and design are completely separate. Change the font or update the logo without touching the Markdown. Commit both to git, and the exact output is reproducible by anyone with the extension installed.&lt;/p&gt;

&lt;h3&gt;
  
  
  The insert toolbar
&lt;/h3&gt;

&lt;p&gt;The preview panel has a vertical toolbar with one-click inserts for every extended element: headings, bold, italic, images, charts, styled tables, math equations, QR codes, page breaks, column layouts, variables, table of contents, list of figures, list of tables, and more. Every insert drops a ready-to-edit block into the document at the cursor position. You don't need to memorize the syntax to get started.&lt;/p&gt;

&lt;h3&gt;
  
  
  Local images
&lt;/h3&gt;

&lt;p&gt;Drop an image into the &lt;code&gt;images/&lt;/code&gt; folder and reference it with a standard Markdown path: &lt;code&gt;![caption](images/logo.png)&lt;/code&gt;. On the first render, the extension uploads it to the rendering API and caches it by content hash. Unchanged images aren't re-uploaded on subsequent renders. The path in the Markdown stays local and relative; the file is part of the repository.&lt;/p&gt;

&lt;h3&gt;
  
  
  Automatic indices and cross-references
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;::toc&lt;/code&gt; generates a table of contents from the actual headings. &lt;code&gt;::listOfFigures&lt;/code&gt; and &lt;code&gt;::listOfTables&lt;/code&gt; produce numbered lists of every captioned figure and table. Every chart, captioned image, and styled table gets an auto-incremented number you can reference with &lt;code&gt;[Figure {num}](#anchor)&lt;/code&gt; or &lt;code&gt;[Table {num}](#anchor)&lt;/code&gt;. The numbers stay correct regardless of what gets added or reordered before them. Bibliography citations work the same way: define sources in &lt;code&gt;autype.json&lt;/code&gt;, cite inline with &lt;code&gt;@[id]&lt;/code&gt;, and the reference list is generated automatically.&lt;/p&gt;

&lt;h3&gt;
  
  
  Getting started
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Install &lt;a href="https://marketplace.visualstudio.com/items?itemName=centerbit.autype" rel="noopener noreferrer"&gt;Autype for VS Code&lt;/a&gt; from the Marketplace&lt;/li&gt;
&lt;li&gt;Create a free account at &lt;a href="https://app.autype.com" rel="noopener noreferrer"&gt;app.autype.com&lt;/a&gt; and copy your API key&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;Autype: Set API Key&lt;/code&gt; from the command palette (&lt;code&gt;Cmd+Shift+P&lt;/code&gt;) and paste it in&lt;/li&gt;
&lt;li&gt;Right-click any folder in the Explorer and choose &lt;strong&gt;Autype: Create New Document&lt;/strong&gt; to scaffold the project (creates &lt;code&gt;autype.json&lt;/code&gt;, a &lt;code&gt;.mdd&lt;/code&gt; file, and an &lt;code&gt;images/&lt;/code&gt; folder)&lt;/li&gt;
&lt;li&gt;Open the &lt;code&gt;.mdd&lt;/code&gt; file, click the Autype icon in the editor title bar, and hit Render&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8blwz85pn85zr7pcwcqy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8blwz85pn85zr7pcwcqy.png" alt="Click the Autype icon in the editor title bar to open the live PDF preview panel" width="800" height="205"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The extension connects to Autype's rendering API. The free plan covers unlimited preview renders (watermarked) and rate-limited PDF exports. No credit card required.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdu4xj4cv0a3s3ac30nas.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdu4xj4cv0a3s3ac30nas.png" alt="The Autype status bar button opens the command menu — Set API Key stores your key in VS Code's secure storage" width="419" height="218"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Export: PDF, DOCX, and ODT
&lt;/h2&gt;

&lt;p&gt;The most common objection to Markdown-based documents is: "My colleagues work in Word." Fair point. It's not a problem.&lt;/p&gt;

&lt;p&gt;Autype exports the same document as PDF, DOCX, and ODT. The DOCX output is a proper Word document: it opens in Microsoft Word, LibreOffice, and Google Docs, can be edited further, and preserves formatting. The developer writes in Markdown in VS Code, the colleague in the business team gets a DOCX they can annotate and return, the client gets a PDF. All three come from the same source file.&lt;/p&gt;

&lt;p&gt;This is also the practical advantage over LaTeX for most teams. Converting a LaTeX document to an editable DOCX is a project in itself. In Autype it's a dropdown in the export panel.&lt;/p&gt;




&lt;h2&gt;
  
  
  Documents Belong in the Repository
&lt;/h2&gt;

&lt;p&gt;Because the document is plain text, it fits naturally into a git workflow. The report lives in the same repo as the code it describes. Changes are commits. Drafts are branches. Reviews are pull requests, with real diffs, inline comments, and approvals. &lt;code&gt;git blame&lt;/code&gt; shows who changed the revenue figures and when. The &lt;code&gt;autype.json&lt;/code&gt; that defines the visual design is committed alongside the content, so the output is reproducible by anyone with the extension.&lt;/p&gt;

&lt;p&gt;A &lt;code&gt;.docx&lt;/code&gt; file can't do any of this. Word documents are binary blobs with no meaningful diff, no merge strategy, and no version history that doesn't require SharePoint.&lt;/p&gt;




&lt;p&gt;Word and LaTeX aren't going anywhere. If you write one document a quarter and don't care about reproducibility or version control, switching tools isn't worth the effort.&lt;/p&gt;

&lt;p&gt;But if you write documents regularly as part of a development workflow (reports, specs, API references, papers, anything that needs to look professional and survive review cycles), the friction you've been absorbing isn't inherent to the problem. It's just a consequence of using tools built for different people.&lt;/p&gt;

&lt;p&gt;The documentation is at &lt;a href="https://docs.autype.com" rel="noopener noreferrer"&gt;docs.autype.com&lt;/a&gt;. The full syntax reference is at &lt;a href="https://docs.autype.com/markup-reference/overview" rel="noopener noreferrer"&gt;https://docs.autype.com/markup-reference/overview&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>markdown</category>
      <category>ai</category>
      <category>documentation</category>
      <category>vscode</category>
    </item>
    <item>
      <title>Save Hours of Document Work with n8n and Autype: Automatic Watermarking and PDF Protection</title>
      <dc:creator>Kevin</dc:creator>
      <pubDate>Thu, 05 Mar 2026 12:33:13 +0000</pubDate>
      <link>https://dev.to/kesimo/save-hours-of-document-work-with-n8n-and-autype-automatic-watermarking-and-pdf-protection-1f31</link>
      <guid>https://dev.to/kesimo/save-hours-of-document-work-with-n8n-and-autype-automatic-watermarking-and-pdf-protection-1f31</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;tl;dr:&lt;/strong&gt; Every time someone uploads a PDF to a Google Drive folder, this n8n workflow automatically creates two secured versions: one with a diagonal "CONFIDENTIAL" watermark, one password-protected. Both are saved back to Drive. No code, no manual steps, no re-uploading. It runs in the background, every minute, forever.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;Most teams have a document problem they don't even recognize as one yet.&lt;/p&gt;

&lt;p&gt;Someone exports a contract as PDF. Sends it to a colleague to review. That colleague pastes it into an email without a watermark. Someone forwards it externally. Now your internal draft is sitting in a client's inbox with no indication that it was never meant to leave the building.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The fix isn't a policy memo. It's automation.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In this post I'll walk you through a concrete n8n workflow that watches a Google Drive folder and automatically produces a watermarked and a password-protected version of every new PDF that lands there. Using &lt;a href="https://autype.com" rel="noopener noreferrer"&gt;Autype&lt;/a&gt; for the document processing and the n8n Google Drive Trigger, the whole thing takes about 10 minutes to set up and then runs completely hands-free.&lt;/p&gt;

&lt;p&gt;This is part of an ongoing series on document automation with n8n and Autype. More workflows are coming.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem with Manual Document Security
&lt;/h2&gt;

&lt;p&gt;Watermarking a PDF manually is a solved problem in the same way that manually backing up files is a solved problem: technically feasible, practically never done consistently.&lt;/p&gt;

&lt;p&gt;You need a PDF editor. You open the file. You add the watermark. You export again. You remember to also add a password. You do this for every document. Every time. Or you don't, and you hope nothing goes wrong.&lt;/p&gt;

&lt;p&gt;The real cost isn't the 3 minutes per document. It's the 100% probability that someone on your team skips it eventually because they were in a hurry, because they forgot, or because nobody told them the process changed.&lt;/p&gt;

&lt;p&gt;Automation removes the human variable. If the workflow runs, the file is secured. No exceptions.&lt;/p&gt;




&lt;h2&gt;
  
  
  What the Workflow Does
&lt;/h2&gt;

&lt;p&gt;The workflow triggers automatically whenever a new PDF appears in a watched Google Drive folder. From there:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The PDF is downloaded and uploaded to &lt;a href="https://autype.com" rel="noopener noreferrer"&gt;Autype´s Document API&lt;/a&gt; (a single upload, the file ID is reused by both branches)&lt;/li&gt;
&lt;li&gt;In parallel, two operations run:

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Watermark branch:&lt;/strong&gt; a diagonal "CONFIDENTIAL" stamp is applied and the result is saved back to Drive as &lt;code&gt;filename-watermark.pdf&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Protection branch:&lt;/strong&gt; the PDF is encrypted with a user password and an owner password, then saved as &lt;code&gt;filename-protected.pdf&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Both outputs land in the same Google Drive folder within seconds. The original file stays untouched.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F003xfq3o0lp5mrteeloq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F003xfq3o0lp5mrteeloq.png" alt="Full n8n Workflow Document Automation" width="800" height="413"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The parallel structure matters here: instead of watermarking first and then re-uploading the watermarked file for protection (which adds a full round-trip), both operations work from the same uploaded file ID simultaneously. The result is faster and cleaner.&lt;/p&gt;




&lt;h2&gt;
  
  
  Setting It Up
&lt;/h2&gt;

&lt;p&gt;You need three things before you start:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A self-hosted n8n instance&lt;/li&gt;
&lt;li&gt;An &lt;a href="https://app.autype.com" rel="noopener noreferrer"&gt;Autype account&lt;/a&gt; with an API key (free tier works)&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;n8n-nodes-autype&lt;/code&gt; community node installed via &lt;strong&gt;Settings &amp;gt; Community Nodes&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step 1: Google Drive Trigger
&lt;/h3&gt;

&lt;p&gt;Set the trigger to watch a specific folder for the &lt;code&gt;fileCreated&lt;/code&gt; event. Replace &lt;code&gt;YOUR_FOLDER_ID&lt;/code&gt; with the ID from your folder URL (&lt;code&gt;https://drive.google.com/drive/folders/YOUR_FOLDER_ID&lt;/code&gt;). The trigger polls every minute by default.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Download and Upload to Autype
&lt;/h3&gt;

&lt;p&gt;After the trigger fires, a Google Drive node downloads the file binary. That binary goes straight into the Autype File Upload node. The upload returns a file ID that both parallel branches use.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Watermark Branch
&lt;/h3&gt;

&lt;p&gt;The Add Watermark node takes the file ID and stamps "CONFIDENTIAL" diagonally across every page at 30% opacity, 45-degree rotation, font size 50. All of this is configurable.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1i7qxfaq1t6wg8b3ox76.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1i7qxfaq1t6wg8b3ox76.png" alt="Add Watermark Autype Node" width="800" height="345"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The output binary goes directly to a Google Drive upload node. The filename is derived from the original using an n8n expression:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{{ $('Download PDF from Drive').item.json.name.replace(/\.pdf$/i, '') }}-watermark.pdf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No Code node needed. Just an expression in the filename field.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4: Protection Branch (runs in parallel)
&lt;/h3&gt;

&lt;p&gt;The Password-Protect node uses the same Autype file ID from the upload. It encrypts the PDF with a user password (required to open) and an owner password (required to change permissions or print).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6uwxt0nmy8ruio0hq4g4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6uwxt0nmy8ruio0hq4g4.png" alt="Autype Password-Protect node configuration" width="800" height="422"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Same filename expression, different suffix:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{{ $('Download PDF from Drive').item.json.name.replace(/\.pdf$/i, '') }}-protected.pdf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both branches save their outputs to the same Google Drive folder as the original. Three files in, three files out.&lt;/p&gt;

&lt;p&gt;You can find the full workflow JSON here: &lt;a href="https://pastebin.com/raw/Pq9vkp9G" rel="noopener noreferrer"&gt;Get Workflow JSON&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  What You Can Customize
&lt;/h2&gt;

&lt;p&gt;This workflow is deliberately minimal. Here's where to extend it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Change the watermark text&lt;/strong&gt; to your company name, "DRAFT", "INTERNAL USE ONLY", or anything else&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Adjust watermark style&lt;/strong&gt; (opacity, rotation, font size) directly in the Autype node parameters&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use separate input and output folders&lt;/strong&gt; if you want to keep the originals in one place and the secured versions somewhere else&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Add a compression step&lt;/strong&gt; before the Drive upload to keep file sizes manageable for email workflows&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Replace the Google Drive Trigger&lt;/strong&gt; with a webhook, an email trigger, or a Slack event if your documents arrive through a different channel&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  More Document Automation Ideas (This Is Just the Start)
&lt;/h2&gt;

&lt;p&gt;Document automation goes much further than watermarking and password protection. Here's a quick list of what's possible once you have a setup like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Merge multiple PDFs&lt;/strong&gt; into one consolidated report, strip cover pages, and compress the result&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Replace placeholders in Word templates&lt;/strong&gt; with data from a form or database, convert to PDF, and send via email&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Auto file conversions&lt;/strong&gt; (DOCX to PDF, PDF to images, images to PDF) triggered by uploads to a folder&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Split PDFs by page range&lt;/strong&gt; and distribute sections to different recipients automatically&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Convert PDF pages to images&lt;/strong&gt; and embed them in a generated summary document&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bulk-generate personalized documents&lt;/strong&gt; from a JSON dataset (invoices, certificates, contracts) in a single workflow run&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Trigger document generation from an AI agent&lt;/strong&gt; that decides format and content based on user input&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Backups and redundancy&lt;/strong&gt; (copy new uploads to S3, a second Drive, or a write-only archive folder)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Versioning&lt;/strong&gt; (write back files with timestamps, keep a changelog file, or automatically move old versions into an archive)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Archiving&lt;/strong&gt; (auto move secured PDFs to an archive folder after X days, or after a signature step completes)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All of this is doable with n8n and Autype today. I'll be publishing document Automation workflows in this series.&lt;/p&gt;




&lt;p&gt;Automation doesn't have to be a big infrastructure project. A single workflow that runs in the background and handles one repetitive task correctly, every time, is already a significant win. Start with the thing that's most likely to slip through the cracks, secure your documents automatically, and build from there.&lt;/p&gt;

&lt;p&gt;The barrier is lower than you think. The habit is what takes time to build.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Have questions about the workflow or want to share what you built? Drop a comment below.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>automation</category>
      <category>nocode</category>
      <category>pdf</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Google Docs Is a Toy. Here's What Professionals Actually Need.</title>
      <dc:creator>Kevin</dc:creator>
      <pubDate>Wed, 04 Mar 2026 20:13:06 +0000</pubDate>
      <link>https://dev.to/kesimo/google-docs-is-a-toy-heres-what-professionals-actually-need-iho</link>
      <guid>https://dev.to/kesimo/google-docs-is-a-toy-heres-what-professionals-actually-need-iho</guid>
      <description>&lt;h2&gt;
  
  
  Google Docs works great for drafts, meeting notes, and quick sharing. The moment your work actually matters, its limitations become impossible to ignore.
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; Google Docs is excellent at what it was designed for: lightweight, shareable text editing in a browser. But version control is primitive, citation management does not exist, custom styling breaks constantly, and automation requires a workaround on top of a workaround. If you are producing documents that represent your work to the outside world, you need a platform that was built for that purpose. &lt;a href="https://autype.com" rel="noopener noreferrer"&gt;Autype&lt;/a&gt; is one option. This article explains why the gap exists.&lt;/p&gt;




&lt;p&gt;Google Docs has 3 billion users. It is free, it runs in a browser, and it syncs in real time. For a large share of those 3 billion people, those three things are all they need.&lt;/p&gt;

&lt;p&gt;But there is a category of documents where "good enough for a browser app" is not the bar. Client reports. Research papers. Technical specifications. Legal contracts. Investor-facing materials. These documents carry weight. They reflect directly on the person or organization that produced them. And Google Docs, for all its convenience, consistently fails them in specific and predictable ways.&lt;/p&gt;

&lt;p&gt;Not because Google Docs is badly built. Because it was built for a different purpose.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Version History Problem
&lt;/h2&gt;

&lt;p&gt;Google Docs has version history. You can see it, restore from it, and even name specific versions. So what is the problem?&lt;/p&gt;

&lt;p&gt;The problem is granularity and control. Google Docs creates automatic saves constantly, but the version list quickly becomes hundreds of unnamed entries with timestamps like "April 3, 10:47 AM." There is no way to create a structured checkpoint before a major rewrite. No way to leave a note explaining why a version matters. No diff view that shows you what actually changed between two states.&lt;/p&gt;

&lt;p&gt;If you share a document for client review, make revisions based on their feedback, then receive more feedback two weeks later that contradicts the first round, recovering the exact state of the document from before either round of changes requires scrolling through a timeline of dozens of unlabeled saves and hoping you can identify the right one visually.&lt;/p&gt;

&lt;p&gt;Professional version control means named snapshots with optional notes, a clear diff view showing exactly what changed, and one-click rollback to any point. Autype's version history works this way: automatic versions are created in the background, manual named versions can be saved at any time (before sending to a client, after a major revision, before a meeting), and the diff view compares any two versions side by side in Markdown or JSON format.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7ylkbh9gh2xpkwvkvihk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7ylkbh9gh2xpkwvkvihk.png" alt="Version Controll Diff-View in Autype" width="800" height="453"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is not a premium feature. It is how version control should work for documents, in the same way it works for code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Citations Do Not Exist in Google Docs
&lt;/h2&gt;

&lt;p&gt;If you are writing anything that requires references, Google Docs' built-in citation support is so limited it is almost satirical. The native citation tool supports a handful of styles, requires manual entry for every source, and produces no bibliography that updates automatically when you remove a reference.&lt;/p&gt;

&lt;p&gt;The real-world workflow for most academics and researchers using Google Docs: write in Google Docs, manage citations in Zotero or Mendeley, use a Google Docs plugin that connects to the reference manager, hope the plugin still works after the last Google Docs update, export to Word, fix whatever the plugin broke, submit. This is not a workflow. It is a series of workarounds duct-taped together.&lt;/p&gt;

&lt;p&gt;A purpose-built document platform handles the full citation lifecycle natively. In Autype: import your bibliography from BibTeX, RIS, or Zotero export in one step. Cite inline with &lt;code&gt;@[smith2023]&lt;/code&gt;. Add page locators with &lt;code&gt;@[smith2023, p. 42]&lt;/code&gt;. Change the citation style globally from APA to IEEE or Chicago with a single dropdown. The bibliography generates automatically and only includes sources that are actually cited. Broken citations are flagged in real time before you export.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;This result aligns with prior findings @[jones2022, pp. 14-16].

...

::bibliography{title="References"}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the entire workflow. One syntax, one place, no plugins. For Management of your sources you can import bibtex or CSL-Json entries and lists or just add a source manually:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fydp35bhbci3lvohvdvmy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fydp35bhbci3lvohvdvmy.png" alt="Add a new Source Modal in Autype" width="606" height="581"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Styling Is a Constant Battle
&lt;/h2&gt;

&lt;p&gt;Open a Google Doc that three people have edited over three months. Count the different font sizes in the body text. Count the different heading styles. Count the tables that each look slightly different because someone pasted content from outside.&lt;/p&gt;

&lt;p&gt;Google Docs has "styles" in the sense that you can define Heading 1, Heading 2, and Normal Text. But applying them consistently across a document that multiple people have touched is a manual and ongoing task. There is no concept of a global style preset that enforces consistent spacing, fonts, colors, and heading behavior across all documents in an organization.&lt;/p&gt;

&lt;p&gt;The result is that every "professional" document produced in Google Docs carries the visual fingerprint of having been edited by multiple people with different habits. The solution in most organizations is a PDF export that someone manually reformats before it goes to a client.&lt;/p&gt;

&lt;p&gt;With Autype, the style is defined once as a JSON configuration: font family, font size, heading styles, line height, colors, headers with logos, footers with page numbers. That style is applied consistently across every document in the organization. No individual can accidentally break it by pasting in content with different formatting. The output always looks like it came from the same place.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"defaults"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"fontFamily"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Inter"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"fontSize"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"lineHeight"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1.4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"header"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"left"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"image"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"src"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"logo.png"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"width"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"right"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"{{documentTitle}}"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"footer"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"right"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Page {{pageNumber}} of {{totalPages}}"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Define it once. Every document looks right, automatically. You can simply use the Style-Editor (With AI integration to generate a style in seconds)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjud6gsiatv8dx2v1auv3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjud6gsiatv8dx2v1auv3.png" alt="Autype´s Style Editor" width="800" height="621"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Automation Requires a Workaround Stack
&lt;/h2&gt;

&lt;p&gt;If you need to generate documents from data, Google Docs has Google Apps Script. Which means you are writing JavaScript that calls Google's API to manipulate document structure, managing authentication tokens, dealing with rate limits, and debugging a scripting environment that exists largely outside of standard developer tooling.&lt;/p&gt;

&lt;p&gt;For anything beyond basic mail merge, this approach breaks down quickly. Dynamic charts from live data: not supported without a plugin. Multi-format output from one template: not supported. Bulk generation of 100 personalized documents from a CSV: not supported natively. Webhook-triggered document generation when a form is submitted: requires a chain of Google services.&lt;/p&gt;

&lt;p&gt;An API-first document platform handles all of these as first-class capabilities. With Autype's REST API, you send a JSON payload and receive a rendered PDF, DOCX, or ODT. Template variables fill dynamically. Bulk jobs generate up to 100 personalized documents per run from CSV or spreadsheet data. Webhooks fire when jobs complete. No script environments, no authentication workarounds.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fern6rvwoipb6rdsrv3bh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fern6rvwoipb6rdsrv3bh.png" alt="Workflow for Document Automation" width="800" height="62"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The integration with n8n, Make.com, or any HTTP-capable platform takes minutes, not days.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real-Time Collaboration That Does Not Create Chaos
&lt;/h2&gt;

&lt;p&gt;Google Docs' real-time collaboration is genuinely excellent for its original use case: multiple people writing a shared document together. But it has no mechanism for structured review workflows. Comments exist, but there is no way to formally request a review, track which comments have been addressed, or manage an approval flow.&lt;/p&gt;

&lt;p&gt;In practice, teams using Google Docs for professional document workflows end up adding process on top: a Slack message saying "I've left comments in the doc," a spreadsheet tracking which feedback was addressed, a manual email when the document is ready for the next round.&lt;/p&gt;

&lt;p&gt;Autype's collaboration model includes line-anchored threaded comments with resolve and deleted states and real-time sync built on CRDT technology (the same approach used in the most advanced collaborative editors) that means no merge conflicts even with many concurrent users.&lt;/p&gt;

&lt;p&gt;The difference is not the technology. It is whether collaboration was designed into the document model from the start or added on top of a single-user writing tool.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Google Docs Is Actually Good For
&lt;/h2&gt;

&lt;p&gt;None of this means Google Docs is a bad product. It is an outstanding product for what it was designed to do: quick documents, meeting notes, shared drafts, lightweight collaboration without any setup. For a significant portion of document work, it is the right tool.&lt;/p&gt;

&lt;p&gt;The mistake is using it for documents where it was never designed to perform. When formatting consistency matters. When citations need to be correct. When the output represents you to a client or a committee. When the document needs to be generated from data, not typed by hand. When version history needs to mean something.&lt;/p&gt;

&lt;p&gt;That is where the gap between a convenient browser app and a purpose-built document platform becomes unavoidable.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;&lt;a href="https://autype.com" rel="noopener noreferrer"&gt;Autype&lt;/a&gt; is a professional document editor and generation API with real-time collaboration, version history, citation management, and automation. Start free at &lt;a href="https://app.autype.com" rel="noopener noreferrer"&gt;app.autype.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>productivity</category>
      <category>agents</category>
      <category>automation</category>
      <category>documentation</category>
    </item>
    <item>
      <title>The Next Generation of Actually Useful Micro-SaaS Ideas (2026 Edition)</title>
      <dc:creator>Kevin</dc:creator>
      <pubDate>Tue, 03 Mar 2026 21:40:47 +0000</pubDate>
      <link>https://dev.to/kesimo/the-next-generation-of-actually-useful-micro-saas-ideas-2026-edition-j23</link>
      <guid>https://dev.to/kesimo/the-next-generation-of-actually-useful-micro-saas-ideas-2026-edition-j23</guid>
      <description>&lt;h2&gt;
  
  
  Document automation is the infrastructure layer most indie hackers are ignoring. Here is why that is a mistake, and five ideas you can build this weekend.
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; Most micro-SaaS ideas die because the hard part (generating a polished, client-ready PDF or DOCX output) takes months to build. Document automation APIs like &lt;a href="https://autype.com" rel="noopener noreferrer"&gt;Autype&lt;/a&gt; eliminate that entirely. On the input side, tools like Mistral OCR and &lt;a href="https://github.com/microsoft/markitdown" rel="noopener noreferrer"&gt;markitdown&lt;/a&gt; handle converting messy real-world documents into clean, LLM-readable Markdown. But the real differentiator is not the renderer. It is combining your personal domain expertise with the right data sources (Google Trends, Perplexity, industry APIs) and LLMs to build something that actually earns its price tag.&lt;/p&gt;




&lt;p&gt;There is a pattern in the micro-SaaS graveyard that nobody talks about enough. The idea is fine. The landing page looks great. The founder validated demand in three Reddit threads. And then they spend four months building a custom PDF renderer, fighting Puppeteer's page break behavior, and debugging font embedding across operating systems. By the time they ship, they are burnt out and the market has moved.&lt;/p&gt;

&lt;p&gt;The problem is not the idea. It is choosing the wrong infrastructure layer to fight on.&lt;/p&gt;

&lt;p&gt;Document generation is one of those problems that looks simple from the outside ("just export a PDF, how hard can it be?") and turns into a full-time engineering project the moment you try to do it properly. Professional layout, consistent fonts, page numbering, headers and footers, dynamic charts, template variables, bulk rendering for hundreds of clients at once. Each of these is a solved problem, just not one you should be solving yourself.&lt;/p&gt;

&lt;p&gt;But there is a second, more interesting problem worth unpacking before we get to the ideas.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Real Differentiator Is Not the Output Format
&lt;/h2&gt;

&lt;p&gt;Every one of the micro-SaaS tools listed below has one thing in common with its competitors: they all produce a PDF at the end. The rendering is table stakes. What actually separates a $5,000 MRR product from a dead side project is what goes into the document before it gets rendered.&lt;/p&gt;

&lt;p&gt;The blueprint that consistently works looks like this:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Your domain expertise&lt;/strong&gt; tells you what information belongs in the document and what structure makes it credible. A business plan written by someone who has pitched investors looks different from one produced by a generic template engine. A market analysis report written by someone who understands how to read Google Trends data is worth something. A compliance document built by someone who has actually navigated GDPR is trusted more than one generated from a boilerplate.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Real data sources&lt;/strong&gt; make the output dynamic instead of static. Instead of asking the user to fill in a market size field, you pull it from a live API. Instead of a generic competitive landscape section, you run a Perplexity deep research query against the user's niche and summarize the results. Instead of a blank financial projections table, you pre-populate it with industry benchmarks from a data provider. And if the user has existing documents to bring in (old contracts, prior reports, scanned forms), tools like Mistral OCR or Microsoft's markitdown convert them into clean, structured Markdown that an LLM can actually work with. The user provides direction. The tool provides research and context.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;LLMs do the heavy lifting on content generation.&lt;/strong&gt; The user provides a few key inputs. An LLM (GPT, Claude, Gemini) drafts the sections, in a structured JSON format that maps directly to your document template. You review, adjust, and export. The LLM is not writing a Word document. It is filling variables in a structured schema that Autype renders into a professional PDF.&lt;/p&gt;

&lt;p&gt;Put these three things together and you have a product that is genuinely hard to replicate. Not because the technology is complex, but because the expertise behind the prompts, the data connections, and the document structure is yours.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzw7lzp2xwrvew0n3cszq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzw7lzp2xwrvew0n3cszq.png" alt="Diagram of Example Flow" width="800" height="197"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here are five concrete ideas that apply this blueprint, each built on document automation infrastructure you do not have to build yourself.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Business Plan Generator (with Live Market Data)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The pitch:&lt;/strong&gt; A founder answers ten questions about their business. The tool pulls current market size data, runs a Perplexity query on the competitive landscape, and generates a structured, investor-ready business plan PDF in under two minutes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why it works:&lt;/strong&gt; Business plans have a well-defined structure. But the generic versions all look identical because they contain only what the user typed in. The interesting version enriches each section automatically: market size from Statista or SimilarWeb, competitor analysis from a Perplexity deep research call, trend data from Google Trends API, and financial projections built from industry benchmark databases.&lt;/p&gt;

&lt;p&gt;The LLM takes these inputs and generates the prose for each section, structured as JSON variables. Autype renders the final document with consistent formatting, a cover page, financial tables with alternating row styling, and a chart showing projected revenue growth.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"variables"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"companyName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Acme Inc"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"targetMarket"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"SMB accounting teams"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"marketSizeData"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"table"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"columns"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"Region"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"TAM"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"CAGR"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"Europe"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"$4.2B"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"14%"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"North America"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"$6.8B"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"11%"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"competitorSummary"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Based on current search trends and market activity, the three primary competitors are..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"projectedRevenue"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"table"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"columns"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"Year"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ARR"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Customers"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"2025"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"$120k"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"200"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"2026"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"$480k"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"800"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"2027"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"$1.2M"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2000"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What you charge:&lt;/strong&gt; $29 one-time per plan, or $49/month for unlimited plans with live data refreshes. The cost per render is a few cents. The research automation is worth hours to the user.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Niche Market Research Reports
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The pitch:&lt;/strong&gt; A user enters a product niche or industry vertical. The tool automatically compiles a structured market research report: Google Trends data for search volume over time, a Perplexity deep research summary of the competitive landscape, Reddit and forum sentiment analysis, and a curated list of potential customers or distribution channels.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why it works:&lt;/strong&gt; This one requires almost no user input at all. The entire content pipeline is automated. Google Trends API provides the search interest chart. Perplexity handles competitor discovery and summarization. An LLM structures the findings into a coherent narrative. Autype renders a multi-section PDF report with embedded charts, a table of contents, and a one-page executive summary at the front.&lt;/p&gt;

&lt;p&gt;The product is essentially a research assistant that delivers a formatted report instead of a wall of text. Analysts, consultants, and indie hackers pay for this because the alternative is three hours of manual research and then formatting it all in Word.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What you charge:&lt;/strong&gt; $19 per report, or $79/month for unlimited reports. The data API costs are negligible at this volume.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Proposal Builder with Smart Scope Suggestions
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The pitch:&lt;/strong&gt; A freelancer enters a client name, project type, and a few bullet points about what they want to build. An LLM expands the scope into detailed deliverables, suggests a timeline, and generates a pricing table based on the project type and market rates. The result is a polished PDF proposal ready to send.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why it works:&lt;/strong&gt; Every freelancer sends proposals. Most of them are either embarrassingly basic (a Google Doc with inconsistent formatting) or take forever to produce (a carefully crafted Canva template that breaks every time the scope changes). The interesting version here is not a blank template with fields to fill in. It is a tool that actually understands what a web development project or a brand design project typically includes, and pre-populates the scope accordingly.&lt;/p&gt;

&lt;p&gt;This is where domain expertise matters. The prompts that generate realistic deliverable lists, sensible timelines, and market-appropriate pricing need to be written by someone who has actually done the work. That is your moat. It cannot be replicated by someone who just spins up the same tech stack.&lt;/p&gt;

&lt;p&gt;Autype handles the rendering: text variables for client details, table variables for the itemized pricing breakdown, list variables for the deliverables, and a structured layout with the freelancer's logo in the header.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What you charge:&lt;/strong&gt; $19/month for unlimited proposals. Freelancers bill hundreds per hour. If your tool saves them two hours a month and makes their proposals look more credible, the math is obvious.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Automated Monthly Client Reports
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The pitch:&lt;/strong&gt; Connect to a client's data source (Google Analytics, Stripe, a database query, or even a simple CSV upload). Once a month, the tool automatically generates a branded PDF report with charts, key metrics, and an LLM-written narrative summary. The client gets a polished report delivered by email without the agency touching it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why it works:&lt;/strong&gt; Agencies and consultants send monthly reports to every client. Most of them are manually assembled in PowerPoint or Google Slides, which takes two to four hours per client per month. At ten clients, that is a significant recurring cost that has no business still being manual in 2026.&lt;/p&gt;

&lt;p&gt;The automation stack here is straightforward: a cron job pulls the data, an LLM writes a one-paragraph commentary on each key metric (with context about whether the numbers are good or bad for this specific industry), and Autype bulk-renders all ten client reports in a single job with different variable sets. Each report has the correct client logo, correct branding colors, and correct data.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What you charge:&lt;/strong&gt; $49/month per workspace (covering multiple client reports). Agencies pay this without blinking because it saves them days of work per month.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Compliance Document Generator with Regulatory Monitoring
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The pitch:&lt;/strong&gt; Small businesses enter their company details and jurisdictions. The tool generates privacy policies, terms of service, and data processing agreements. When regulations change (GDPR updates, new US state privacy laws), users get notified and can regenerate updated documents with one click.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why it works:&lt;/strong&gt; This space has incumbents (Termly, iubenda, Docracy), but most of them produce documents that look like they were designed in 2009. The market is not won by having the best legal templates (none of us are lawyers anyway, and users understand this). It is won by having the cleanest output, the fastest time-to-document, and a monitoring layer that makes the subscription feel essential rather than optional.&lt;/p&gt;

&lt;p&gt;The monitoring angle is what justifies the recurring revenue. A one-time PDF generator is a commodity. A service that watches regulatory changes via a legal news API or a Perplexity scheduled search, flags when your document templates need updating, and lets you regenerate with one click is a subscription product with genuine retention.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What you charge:&lt;/strong&gt; $29/month for up to ten active documents with auto-update notifications.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Blueprint
&lt;/h2&gt;

&lt;p&gt;Strip away the specific niches and every one of these products follows the same structure:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Domain expertise&lt;/strong&gt; defines what makes the output actually useful (not just formatted)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Data sources&lt;/strong&gt; replace manual user input with live, enriched information (Google Trends, Perplexity, industry APIs, CRM integrations)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;An LLM&lt;/strong&gt; transforms the raw inputs into structured content, mapped to document variables&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Autype&lt;/strong&gt; renders the variables into a professional, consistently formatted PDF or DOCX via a single API call&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Delivery&lt;/strong&gt; happens via download link, email, or webhook to whatever the user needs&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Steps 2, 3, and 5 are where your product differentiation lives. Step 4 is infrastructure. Autype handles step 4 with a single POST request, supports up to 100 documents per bulk job, delivers results via webhook when jobs complete, and works with n8n, Make.com, or any HTTP-capable automation tool if you want to skip writing backend code entirely.&lt;/p&gt;

&lt;p&gt;The render cost per document is a few cents. The margin on a $49/month subscription is significant. The engineering time you save by not building a custom renderer is measured in months.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Actual Moat
&lt;/h2&gt;

&lt;p&gt;The business plan generator, the research report tool, the proposal builder: none of these are new ideas. Plenty of tools already do each of them. The ones that win are the ones built by people who understand the domain deeply enough to know what makes the output genuinely useful rather than just formatted.&lt;/p&gt;

&lt;p&gt;Your advantage is not access to Autype's API or an LLM. Everyone has those. Your advantage is the ten years of freelancing experience that makes your proposal templates actually credible, the finance background that makes your projections tables structurally sound, or the legal familiarity that makes your compliance documents trustworthy.&lt;/p&gt;

&lt;p&gt;The technology handles the output. The expertise makes it worth paying for.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;&lt;a href="https://autype.com" rel="noopener noreferrer"&gt;Autype&lt;/a&gt; is a document generation API and editor for teams and developers. Render PDFs, DOCX, and ODT from JSON or Markdown with variables, charts, and bulk rendering built in. The API is free to try.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>saas</category>
      <category>startup</category>
      <category>documentation</category>
      <category>automation</category>
    </item>
  </channel>
</rss>
