<?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: bitavu</title>
    <description>The latest articles on DEV Community by bitavu (@bitavu).</description>
    <link>https://dev.to/bitavu</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4223152%2Fff55c1fa-1825-403e-85cb-ddd09efd7061.webp</url>
      <title>DEV Community: bitavu</title>
      <link>https://dev.to/bitavu</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/bitavu"/>
    <language>en</language>
    <item>
      <title>Your AI agent built PDF export but who owns the next change</title>
      <dc:creator>bitavu</dc:creator>
      <pubDate>Tue, 03 Nov 2026 23:50:27 +0000</pubDate>
      <link>https://dev.to/bitavu/your-ai-agent-built-pdf-export-but-who-owns-the-next-change-224n</link>
      <guid>https://dev.to/bitavu/your-ai-agent-built-pdf-export-but-who-owns-the-next-change-224n</guid>
      <description>&lt;p&gt;Imagine asking your coding agent to add a client report export.&lt;/p&gt;

&lt;p&gt;It creates a route, adds a button, connects the data, and produces a tidy PDF.&lt;/p&gt;

&lt;p&gt;The agent says &lt;strong&gt;done&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Then another team sends a message:&lt;/p&gt;

&lt;p&gt;"Looks great. Could we add a column, use the new font, and make six client versions?"&lt;/p&gt;

&lt;p&gt;Your agent can write another patch. You can ask it again next week.&lt;/p&gt;

&lt;p&gt;But there is a question hiding behind all that generated code:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Who owns the report after the agent finishes the feature?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I'm building Bitavu, a visual template editor with a JSON-to-PDF API. This is one of the boundaries I care about: letting a coding agent build the integration while giving the team a clear place to maintain the document.&lt;/p&gt;

&lt;h2&gt;
  
  
  The first export is only one part of the job
&lt;/h2&gt;

&lt;p&gt;A task called "add PDF export" leaves plenty open to interpretation.&lt;/p&gt;

&lt;p&gt;Which data belongs in the report? What happens when it spans several pages? Who approves a layout change? Does saving a new design immediately affect production?&lt;/p&gt;

&lt;p&gt;An agent can produce a working implementation without those questions being settled.&lt;/p&gt;

&lt;p&gt;If the acceptance test only checks that a response starts with a PDF signature, a report with missing headings may pass.&lt;/p&gt;

&lt;p&gt;Congratulations. You have successfully tested the existence of a file.&lt;/p&gt;

&lt;p&gt;That check is useful. It just covers much less than the person reading the report will expect.&lt;/p&gt;

&lt;h2&gt;
  
  
  Give the agent a document contract
&lt;/h2&gt;

&lt;p&gt;"Make it professional" leaves the agent to make choices about both presentation and integration.&lt;/p&gt;

&lt;p&gt;A more useful starting point is a reviewed template and an explicit input shape.&lt;/p&gt;

&lt;p&gt;Here is an illustrative request:&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;"templateId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"client-report"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"revision"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;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;"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="nl"&gt;"report"&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;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Delivery summary"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"period"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-Q3"&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;"client"&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;"Harbor Studio"&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;"sections"&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;"heading"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"This quarter"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"body"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"The implementation and review are complete."&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;Those IDs, revision numbers, and fields are examples. The real request must match a template saved in your workspace.&lt;/p&gt;

&lt;p&gt;That gives the coding agent a bounded integration task: gather authorized application data, construct the expected payload, request the selected template revision, and handle the response.&lt;/p&gt;

&lt;p&gt;At runtime, your application sends the JSON. The agent's work lives in the integration code.&lt;/p&gt;

&lt;p&gt;The contract also makes future changes easier to discuss. A wider column is a layout change. A new required field changes what the application needs to supply.&lt;/p&gt;

&lt;p&gt;Both deserve review, but they involve different work.&lt;/p&gt;

&lt;h2&gt;
  
  
  A brief you can give your coding agent
&lt;/h2&gt;

&lt;p&gt;For a Bitavu integration, I would start with something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Implement server-side report export using our existing application
authorization and the provided template ID, tested revision,
JSON field contract, and synthetic example data.

Use POST https://api.bitavu.com/v1/render.

Read the scoped API key from server configuration. Keep it out
of browser code, generated examples, and application logs.

Resolve report data through the authorized application user.
Keep the selected template ID and revision in server configuration.

Generate one idempotency key per logical export operation.
For retries of that operation, preserve the key, body, and revision.

Handle errors explicitly. A schema mismatch needs review;
do not invent a field mapping or switch to a different revision.

Return a successful PDF response as a download. Preserve useful
failure information without exposing credentials or report data.

Add checks for authorization, the expected document content,
multi-page data, schema mismatch, and retry behavior.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is an ordinary HTTP integration. The valuable part of the brief is deciding what the agent may change and what should remain an explicit review decision.&lt;/p&gt;

&lt;p&gt;If a field disappears, "keep trying until a PDF downloads" is a poor acceptance criterion.&lt;/p&gt;

&lt;p&gt;A useful implementation tells you which contract stopped matching.&lt;/p&gt;

&lt;h2&gt;
  
  
  Give layout work a place to happen
&lt;/h2&gt;

&lt;p&gt;In Bitavu Studio, document blocks bind to JSON fields. You maintain the layout visually and save template revisions; the application can request a tested revision through the API.&lt;/p&gt;

&lt;p&gt;That creates a practical split:&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fiyyqa6ykzmg6xw6cim1p.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fiyyqa6ykzmg6xw6cim1p.png" alt=" " width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The application team owns data selection, authorization, and the integration.&lt;/p&gt;

&lt;p&gt;The people responsible for the document own its layout and review.&lt;/p&gt;

&lt;p&gt;A colleague can work on the next template revision while production continues requesting its selected one. When the new layout is approved and tested, you adopt that revision deliberately.&lt;/p&gt;

&lt;p&gt;If the change adds a required data field, the application team still needs to participate.&lt;/p&gt;

&lt;p&gt;A visual editor gives the layout work a home. It does not erase the data contract.&lt;/p&gt;

&lt;h2&gt;
  
  
  A proposed fix still needs an owner
&lt;/h2&gt;

&lt;p&gt;Suppose a generated report has a missing section.&lt;/p&gt;

&lt;p&gt;A tempting fix is to make the section optional, substitute a blank value, or remove the failing check.&lt;/p&gt;

&lt;p&gt;Any of those might make a test green. Whether it makes the report correct depends on why the section was required.&lt;/p&gt;

&lt;p&gt;Give the agent that context before asking it to repair the output.&lt;/p&gt;

&lt;p&gt;The same principle applies when AI helps with template changes. Bitavu's Studio assistant produces structured proposals that are validated and presented for review; applying a proposal is an explicit action.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6y6igw05nyd75jh44goq.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6y6igw05nyd75jh44goq.png" alt=" " width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Conceptual workflow illustration, not a Studio screenshot.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Review the effect on the document and its expected fields. A plausible proposal is a starting point for that review.&lt;/p&gt;

&lt;p&gt;For API input that no longer matches the template, Bitavu reports detected schema drift as &lt;code&gt;422 SCHEMA_DRIFT&lt;/code&gt;. A reviewed, saved mapping can be referenced with &lt;code&gt;mappingId&lt;/code&gt;. That is a deliberate adaptation to a particular template revision.&lt;/p&gt;

&lt;h1&gt;
  
  
  Test the handoff as well as the happy path
&lt;/h1&gt;

&lt;p&gt;The most useful checks challenge the boundaries the agent was asked to preserve:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Situation&lt;/th&gt;
&lt;th&gt;What to verify&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;A user requests another customer's report&lt;/td&gt;
&lt;td&gt;Application authorization rejects the request&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Realistic data spans multiple pages&lt;/td&gt;
&lt;td&gt;Required content remains readable and pagination behaves as intended&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;An upstream field is renamed&lt;/td&gt;
&lt;td&gt;The mismatch is visible rather than silently replaced with a blank&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A render operation is retried&lt;/td&gt;
&lt;td&gt;Its key, body, and selected revision stay consistent&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A colleague edits the next template draft&lt;/td&gt;
&lt;td&gt;The production request still selects its tested revision&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Use synthetic fixtures with names, sections, and row lengths that vary. Inspect the generated pages as well as the response status.&lt;/p&gt;

&lt;p&gt;For a fixed report that rarely changes, an agent-written renderer in your codebase may be enough. A separate template workflow becomes more useful when document changes keep arriving from several teams.&lt;/p&gt;

&lt;p&gt;The next font change is coming either way.&lt;/p&gt;

&lt;p&gt;When you ask an agent to build an export, what goes into your brief beyond "return a PDF"?&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Disclosure: I'm building Bitavu. The three illustrations are AI-generated conceptual scenes using synthetic examples.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>agents</category>
      <category>ai</category>
      <category>webdev</category>
      <category>automation</category>
    </item>
    <item>
      <title>Your JSON is valid and your invoice is wrong</title>
      <dc:creator>bitavu</dc:creator>
      <pubDate>Tue, 03 Nov 2026 19:21:22 +0000</pubDate>
      <link>https://dev.to/bitavu/your-json-is-valid-and-your-invoice-is-wrong-28il</link>
      <guid>https://dev.to/bitavu/your-json-is-valid-and-your-invoice-is-wrong-28il</guid>
      <description>&lt;p&gt;Imagine a perfectly reasonable backend refactor.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;client.name&lt;/code&gt; becomes &lt;code&gt;customer.legalName&lt;/code&gt;. The new shape is clearer. The JSON parses. The application tests are green.&lt;/p&gt;

&lt;p&gt;Then someone opens an invoice.&lt;/p&gt;

&lt;p&gt;Beautiful margins. Correct logo. No customer.&lt;/p&gt;

&lt;p&gt;The document has successfully generated an invoice addressed to absolutely nobody.&lt;/p&gt;

&lt;p&gt;This is an illustrative failure in a loosely checked integration, but the underlying problem is useful to think about: &lt;strong&gt;valid JSON tells you surprisingly little about whether a document is correct.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I'm building Bitavu, a visual template editor with a JSON-to-PDF API. The boundary between incoming data and a saved document template is one of the places where a visible error can be much more useful than a successful download.&lt;/p&gt;

&lt;h2&gt;
  
  
  The customer did not disappear
&lt;/h2&gt;

&lt;p&gt;Here is the data the template originally expected:&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;"client"&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;"Harbor Studio"&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;And here is the new upstream shape:&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;"customer"&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;"legalName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Harbor Studio"&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;Both are valid JSON. Both contain the same company name.&lt;/p&gt;

&lt;p&gt;But a template bound to &lt;code&gt;client.name&lt;/code&gt; does not automatically understand that &lt;code&gt;customer.legalName&lt;/code&gt; is its replacement.&lt;/p&gt;

&lt;p&gt;If the integration quietly converts a missing value into an empty string, the PDF may still render. The mistake has simply moved from an error response into a document someone will send.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ff8kyfr02obiv2y654vxv.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ff8kyfr02obiv2y654vxv.png" alt="one small refactor" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The refactor is reasonable. The unchanged binding is the problem.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Three different checks are involved here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Syntax:&lt;/strong&gt; can the input be parsed as JSON?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Structure:&lt;/strong&gt; does it contain the paths and types the template requires?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Meaning:&lt;/strong&gt; are those values the right ones for this document?&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Passing one does not prove the others.&lt;/p&gt;

&lt;h2&gt;
  
  
  A blank string is not always a fallback
&lt;/h2&gt;

&lt;p&gt;Some fields really are optional. An absent delivery note might reasonably leave no visible text.&lt;/p&gt;

&lt;p&gt;A missing billing recipient deserves a different decision.&lt;/p&gt;

&lt;p&gt;If the document requires a customer name, make that requirement explicit before rendering. Even a small guard is better than quietly substituting a blank:&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;function&lt;/span&gt; &lt;span class="nf"&gt;requireClientName&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="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&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;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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;===&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;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;TypeError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Expected a non-empty client.name&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;return&lt;/span&gt; &lt;span class="nx"&gt;name&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 checks one field. A real document contract needs to cover its other required fields, types, arrays, and allowed values too.&lt;/p&gt;

&lt;p&gt;Notice what happens with the refactored payload: the guard fails.&lt;/p&gt;

&lt;p&gt;That is useful. You now have a specific integration change to review before an invoice leaves the system.&lt;/p&gt;

&lt;p&gt;An empty string would have been quieter. The customer would have discovered the problem for you.&lt;/p&gt;

&lt;h2&gt;
  
  
  The more convincing bug passes the type check
&lt;/h2&gt;

&lt;p&gt;Now suppose the input contains both of these:&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;"billing"&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;"Harbor Studio"&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;"shipping"&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;"Harbor Warehouse"&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;Both names are present. Both are strings. Both look like sensible things to put on a business document.&lt;/p&gt;

&lt;p&gt;Only one belongs in this invoice's &lt;strong&gt;Bill to&lt;/strong&gt; field.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fd5ak1vmzcnbachr2me2x.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fd5ak1vmzcnbachr2me2x.png" alt="same type. different meaning." width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is why a mapping can be technically valid and still be wrong.&lt;/p&gt;

&lt;p&gt;A structural validator can check that a string exists at a declared path. It cannot infer which business entity you intended to bill just because two field names look related.&lt;/p&gt;

&lt;p&gt;When you review a mapping, ask a more useful question than "does the preview have text now?"&lt;/p&gt;

&lt;p&gt;Ask: &lt;strong&gt;"Is this the person or company the document is supposed to name?"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The wrong name often looks more finished than a blank one. That does not make it a better result.&lt;/p&gt;

&lt;h2&gt;
  
  
  Treat a proposed mapping like a code change
&lt;/h2&gt;

&lt;p&gt;For the first example, a deliberate mapping might be:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;customer.legalName → client.name&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;That is a small transformation with a specific meaning: this upstream legal name supplies this template's client name.&lt;/p&gt;

&lt;p&gt;Review it against the document's purpose. If an AI tool proposes the mapping, the same review still applies. A plausible field match is a proposal, not evidence that the two fields mean the same thing.&lt;/p&gt;

&lt;p&gt;Before adopting it, use a few synthetic examples:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Input case&lt;/th&gt;
&lt;th&gt;Expected result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;A normal customer legal name&lt;/td&gt;
&lt;td&gt;The intended name appears in Bill to&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Billing and shipping names that differ&lt;/td&gt;
&lt;td&gt;The billing recipient is used&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A missing or blank required name&lt;/td&gt;
&lt;td&gt;An explicit failure rather than a blank recipient&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A number where the name should be&lt;/td&gt;
&lt;td&gt;A type error rather than silent coercion&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The second case is especially helpful. If every name in your fixture is "Test Company," the wrong mapping can look perfectly correct.&lt;/p&gt;

&lt;p&gt;Give the warehouse a different name. Let your test data disagree with itself in the ways real data does.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make the mismatch visible
&lt;/h2&gt;

&lt;p&gt;In Bitavu, a detected schema mismatch is reported as &lt;code&gt;422 SCHEMA_DRIFT&lt;/code&gt;. After reviewing a transformation, you can save a mapping and reference its &lt;code&gt;mappingId&lt;/code&gt; in render requests.&lt;/p&gt;

&lt;p&gt;Saved mappings target a particular template revision. That gives the reviewed transformation a defined destination instead of treating it as a universal fix for every document.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9kkr638n0cqlczeysfs0.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9kkr638n0cqlczeysfs0.png" alt="make the mismatch visible" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Conceptual workflow illustration, not a Studio screenshot.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;That mechanism helps make the mismatch explicit. It still needs a well-defined template contract and a person who understands what the fields mean.&lt;/p&gt;

&lt;p&gt;The useful outcome is a document whose data and presentation have both been checked.&lt;/p&gt;

&lt;p&gt;A green check beside "valid JSON" cannot do all of that work.&lt;/p&gt;

&lt;p&gt;What is harder to catch in your documents: a missing value, or a completely believable wrong one?&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Disclosure: I'm building Bitavu. All four illustrations are AI-generated conceptual scenes using synthetic examples.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>agents</category>
      <category>ai</category>
      <category>webdev</category>
      <category>automation</category>
    </item>
    <item>
      <title>Your PDF export is becoming a second frontend</title>
      <dc:creator>bitavu</dc:creator>
      <pubDate>Tue, 03 Nov 2026 18:33:39 +0000</pubDate>
      <link>https://dev.to/bitavu/your-pdf-export-is-becoming-a-second-frontend-5b91</link>
      <guid>https://dev.to/bitavu/your-pdf-export-is-becoming-a-second-frontend-5b91</guid>
      <description>&lt;p&gt;The ticket says &lt;strong&gt;"Add PDF export."&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Four words. Very calm words.&lt;/p&gt;

&lt;p&gt;You build an invoice. Three line items. One page. The logo is where logos traditionally live.&lt;/p&gt;

&lt;p&gt;Then someone adds a longer company name, 87 line items, and a payment note.&lt;/p&gt;

&lt;p&gt;The total lands on page three. The footer starts a solo career.&lt;/p&gt;

&lt;p&gt;The file still downloads successfully. Apparently, everything is fine.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A successful render and a usable document are two different acceptance criteria.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I'm building &lt;strong&gt;Bitavu&lt;/strong&gt;, a visual template editor with a JSON-to-PDF API. This is the design problem behind it: how do you let a document evolve without making every layout adjustment another application change?&lt;/p&gt;

&lt;p&gt;The useful answer starts with how you test and release the template.&lt;/p&gt;

&lt;h2&gt;
  
  
  The first PDF is the easy one
&lt;/h2&gt;

&lt;p&gt;A demo invoice is an extremely cooperative customer.&lt;/p&gt;

&lt;p&gt;It has a short name. Its address fits. Its three descriptions are roughly the same length. It would never order enough things to inconvenience your page margins.&lt;/p&gt;

&lt;p&gt;Real documents have less interest in your demo.&lt;/p&gt;

&lt;p&gt;A request like "make the description column wider" can change row heights. Those heights affect page breaks. A page break can separate a total from the lines that explain it.&lt;/p&gt;

&lt;p&gt;That is how a small layout change earns a surprisingly large review.&lt;/p&gt;

&lt;p&gt;Treating the template as something you can test and release gives you a way to handle that review deliberately.&lt;/p&gt;

&lt;h2&gt;
  
  
  Give the layout its own home
&lt;/h2&gt;

&lt;p&gt;There are two different jobs inside an invoice.&lt;/p&gt;

&lt;p&gt;Your application knows what happened: who bought what, which amounts apply, and when payment is due.&lt;/p&gt;

&lt;p&gt;The template decides how to present it: columns, spacing, branding, repeated headings, and where content continues onto another page.&lt;/p&gt;

&lt;p&gt;A useful boundary looks like this:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Application data + tested template revision → PDF&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;That boundary can live entirely in your own codebase. You do not need a separate service to adopt it.&lt;/p&gt;

&lt;p&gt;What matters is being able to change the layout while keeping its expected input clear.&lt;/p&gt;

&lt;p&gt;In Bitavu Studio, that separation is expressed through visual document blocks, JSON bindings, and saved template revisions. A repeating table can take its rows from an &lt;code&gt;items&lt;/code&gt; array; the backend supplies the data.&lt;/p&gt;

&lt;p&gt;The visual editor gives layout work a place to happen. You still need to review the result.&lt;/p&gt;

&lt;h2&gt;
  
  
  Give your invoice some deeply inconvenient data
&lt;/h2&gt;

&lt;p&gt;Before changing the design, save a small set of synthetic fixtures.&lt;/p&gt;

&lt;p&gt;Start with a tidy invoice so you can spot obvious mistakes. Then make the data less polite.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Fixture&lt;/th&gt;
&lt;th&gt;What to check&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;One item, then enough items to span several pages&lt;/td&gt;
&lt;td&gt;Table continuation, repeated headings, and footer placement&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The last row count that fits, then one more row&lt;/td&gt;
&lt;td&gt;The exact transition onto the next page&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A long company name and multi-line address&lt;/td&gt;
&lt;td&gt;Wrapping and the space left above the table&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;One very long item description&lt;/td&gt;
&lt;td&gt;Row splitting and whether its quantity and amount remain easy to associate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Missing optional notes or address lines&lt;/td&gt;
&lt;td&gt;Intended fallbacks and unwanted blank space&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Names with accents and symbols used by your customers&lt;/td&gt;
&lt;td&gt;Font coverage, shaping, and alignment&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The "one more row" case deserves special attention.&lt;/p&gt;

&lt;p&gt;If 18 rows fit and 19 spill over, keep both fixtures. Those numbers belong to that particular template, font, and data; they are not a universal page capacity.&lt;/p&gt;

&lt;p&gt;Also try the same row count with longer descriptions. Counting rows alone will not tell you how tall a table becomes.&lt;/p&gt;

&lt;p&gt;The goal is to find the point where the layout changes behavior, then keep a test on either side of it.&lt;/p&gt;

&lt;p&gt;Your least cooperative fake customer is doing valuable work here.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make the backend request boring
&lt;/h2&gt;

&lt;p&gt;Once the template has a clear input shape, the integration should be straightforward.&lt;/p&gt;

&lt;p&gt;Here is a synthetic Bitavu invoice request. Save it as &lt;code&gt;request.json&lt;/code&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;"templateId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"invoice"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"revision"&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;"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="nl"&gt;"invoice"&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;"number"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"INV-2027-014"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"date"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2027-01-12"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"due"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2027-02-11"&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;"client"&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;"Bitavu Studio"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"address"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"74372 Washington Ave&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;Columbus, 7206"&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;"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="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&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;"Brand strategy workshop"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"quantity"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"unitPrice"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;650&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="nl"&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;"Design system"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"quantity"&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;"unitPrice"&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="p"&gt;{&lt;/span&gt;&lt;span class="nl"&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;"Project support"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"quantity"&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;"unitPrice"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;95&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;"notes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Thank you for working with us."&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;code&gt;invoice&lt;/code&gt; and revision &lt;code&gt;3&lt;/code&gt; identify the example. Use a saved template ID and revision from your own workspace, with data that matches its bindings.&lt;/p&gt;

&lt;p&gt;With your scoped API key in &lt;code&gt;BITAVU_API_KEY&lt;/code&gt;, this Bash example creates a key for one render operation and sends the request:&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;# Create once for this operation. Reuse it for this operation's retries.&lt;/span&gt;
&lt;span class="nv"&gt;BITAVU_RENDER_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;node &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s1"&gt;'console.log(require("node:crypto").randomUUID())'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

curl &lt;span class="nt"&gt;--fail-with-body&lt;/span&gt; &lt;span class="nt"&gt;--retry&lt;/span&gt; 2 &lt;span class="nt"&gt;--retry-delay&lt;/span&gt; 2 &lt;span class="se"&gt;\&lt;/span&gt;
  https://api.bitavu.com/v1/render &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$BITAVU_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;"Idempotency-Key: &lt;/span&gt;&lt;span class="nv"&gt;$BITAVU_RENDER_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;--data&lt;/span&gt; @request.json &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--output&lt;/span&gt; invoice.pdf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The successful response is the PDF itself.&lt;/p&gt;

&lt;p&gt;Keep the API key on your server. For a new operation, generate a new idempotency key. For a retry of the same operation, preserve the key, request body, and template revision.&lt;/p&gt;

&lt;p&gt;If you retry later, do not rerun the key-generation line and accidentally turn the retry into a new operation.&lt;/p&gt;

&lt;h2&gt;
  
  
  A saved design should not surprise production
&lt;/h2&gt;

&lt;p&gt;Suppose production requests revision &lt;code&gt;3&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;You adjust the column widths and save a new revision. Production should keep using &lt;code&gt;3&lt;/code&gt; while you review the change.&lt;/p&gt;

&lt;p&gt;A practical release sequence is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Render the saved fixtures against the candidate revision.&lt;/li&gt;
&lt;li&gt;Check the resulting pages, especially the page-boundary cases.&lt;/li&gt;
&lt;li&gt;Approve the layout and confirm that the application's data still matches.&lt;/li&gt;
&lt;li&gt;Update the revision requested by production.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A revision pin controls which template you request. It is not a promise that every byte of every PDF will be identical: renderer changes, fonts, and referenced assets also matter.&lt;/p&gt;

&lt;p&gt;For the same reason, a PDF binary diff is a poor substitute for checking the document. Metadata can change without changing the page; a misplaced total can matter enormously even though the file opens.&lt;/p&gt;

&lt;p&gt;If a field changes from &lt;code&gt;client.name&lt;/code&gt; to &lt;code&gt;customer.legalName&lt;/code&gt;, treat that as an integration change too. Moving the layout into an editor does not make the data contract disappear.&lt;/p&gt;

&lt;h2&gt;
  
  
  When a visual editor earns its place
&lt;/h2&gt;

&lt;p&gt;For one fixed document that rarely changes, a small rendering function in your application may be all you need.&lt;/p&gt;

&lt;p&gt;The balance shifts when layouts keep changing, several templates share branding, or someone needs to adjust the document without editing application code.&lt;/p&gt;

&lt;p&gt;That is the workflow I'm building Bitavu for: a visual place to maintain the template, with a versioned API for the application that uses it.&lt;/p&gt;

&lt;p&gt;You still choose the data contract. You still test the awkward cases. You get a clearer place to make and review the layout changes.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmx3yjsw3mo1o2eg1ho8h.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmx3yjsw3mo1o2eg1ho8h.webp" alt="your sprint. saved." width="800" height="338"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The paper monster gets a new job.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;What is the most uncooperative thing you have ever had to fit on an invoice?&lt;/p&gt;

</description>
      <category>agents</category>
      <category>ai</category>
      <category>webdev</category>
      <category>automation</category>
    </item>
  </channel>
</rss>
