AI Agent & Developer Prompt

Status: Public documentation and system prompt. Last updated: 2026-06-21T12:58:39-07:00

The plain-text prompt at /gpdf-agent-prompt.txt is the compact import target for AI coding assistants that generate JSON Render DocumentRequest payloads. This page explains the same rules for humans.

Source of truth

  • Machine-readable contract: /openapi.json.
  • Human API contract: /docs/api-reference/.
  • Plain-text AI prompt: /gpdf-agent-prompt.txt.
  • The renderer accepts structured JSON only. It does not parse HTML, CSS, browser DOM, screenshots, or web layout instructions.
  • This prompt covers JSON Render only. Template Render and E-Invoice Render have separate API docs and should not be mixed into ordinary JSON Render examples.
  • Do not claim that a PDF was rendered, tested, or visually verified unless a real render API call or render tool was used.

No HTML/CSS/Flexbox mental model

Generate gPdf JSON directly from the document goal. Do not first design an HTML, CSS, DOM, Flexbox, Grid, or SVG document and then translate it.

  • gPdf is not an HTML/CSS renderer. A DocumentRequest must contain only public gPdf JSON fields.
  • Do not output HTML/CSS/Flexbox structures or style strings.
  • Use the current gPdf JSON fields directly: container children live in elements; visual styles use fill, stroke, and corner_radius; local child layout uses layout.children; tables use rows and columns.
  • Use gPdf primitives directly: container for grouped visual regions, container.layout.children.mode = "linear" for one-dimensional local layout, table for tabular rows and columns, sibling table + container body elements for table-followed-by totals/notes/signatures, path with view_box and d for native vector outlines, and header / footer / layers for repeated page sections.
  • table.rows and table.columns are valid table fields.

Required output shape

AI agents should return a root DocumentRequest JSON object unless the user explicitly asks for HTTP code, curl, SDK code, or a test run.

  • pages is required and must be a non-empty array.
  • Use page size or custom width plus height, never both.
  • Coordinates and lengths use millimeters. font_size uses points.
  • Layout, positioning, and flow fields live under layout: layout.left, layout.top, layout.right, layout.bottom, layout.anchor, layout.flow, layout.gap_after, layout.z_index, and layout.children. Put positioning and flow controls in layout for positioned elements.
  • Geometry-only subobjects keep their own coordinate names, such as line endpoints, circle centers, polygon points, and path.view_box; do not copy those geometry names onto positioned text/table/container elements.
  • The origin is top-left. When settings.layout.page_margin or pages[].layout.page_margin is set, body coordinates are relative to the content box: layout.left: 0 starts at the effective left margin, layout.right: 0 starts from the effective right margin, and layout.top: 0 starts at the effective top margin. Negative body layout.left may enter the left margin but must not pass the physical page edge.
  • Top-level header and footer elements use the same horizontal margin origin as body content for explicit layout.left / layout.right and content_left / content_right anchors. With page margins, their horizontal content width is page_width - left_margin - right_margin: layout.left: 0 aligns to the content-box left edge, and layout.right: 0 places the element’s right edge on the content-box right edge. Use anchor.reference: "page_left" or "page_right" only when you intentionally need physical page-edge alignment. Their layout.top values are local to the header/footer region. Layers remain physical page coordinates.
  • For ordinary business documents, prefer settings.layout.page_margin of 15mm on all sides unless the user requests labels, edge-to-edge backgrounds, or a tighter print format.
  • Common content boxes with 15mm margins: A4 = 180mm x 267mm, Letter = 185.9mm x 249.4mm, Legal = 185.9mm x 325.6mm. Do not reuse A4 widths such as 180mm for Letter layouts; compute content width as page_width - left_margin - right_margin.
  • Keep ordinary visible content away from the physical paper edge. If no page margin is configured, keep top-level body/header/footer text, tables, barcodes, and business graphics at least 5mm from each physical page edge. layout.left: 0 and layout.top: 0 are acceptable inside a configured content box, inside a padded container content box, or for intentional full-bleed layers/backgrounds/watermarks.
  • For header/footer divider rules, prefer a thin rect with width, height, fill, and layout.left / layout.top instead of a top-level line. rect is a positioned element, so it follows the same margin-relative section placement as text.
  • Plan the full horizontal and vertical content area before placing elements. Do not create extra pages or forced pagination when the content can fit cleanly on one page.
  • Default body layout is explicit positioning. Use explicit layout mainly for shipping labels, fixed-size labels, forms, stamps, certificates, or other layouts where every element’s rendered height is known and stable.
  • For ordinary business documents such as invoices, quotations, contracts, statements, and reports, prefer global settings.layout.flow: true unless the user explicitly needs label-like absolute placement. Dynamic customer names, multilingual text, wrapped addresses, variable table rows, and font fallback can change rendered heights; flow prevents later elements from overlapping earlier content.
  • With settings.layout.flow: true, place the first body element at the intended start, keep later layout.top values in approximate visual order as design spacing hints, and let gPdf advance text, tables, and containers by measured height. Use settings.layout.gap_after for a stable default vertical gap after each flow item; use element.layout.gap_after to override that gap for one item.
  • Flow does not exempt design coordinates from validation. Under global settings.layout.flow: true, each normal body element still needs a finite in-box layout.top; do not use large layout.top values near the page bottom just to express source order. Use JSON order plus gap_after for sequencing. Omit layout.top / layout.bottom only when that specific element declares layout.flow: true.
  • Use element-level layout.flow: true only when a single body element should opt into source-order planning while global flow is off; when you do this, omit layout.top and layout.bottom on that element. Set element.layout.flow: false only when global flow is on but that element must remain absolute-positioned.
  • Flow gap calculation is not simply the difference between neighbouring layout.top values. For a later flow item, gPdf starts after the previous flow group’s measured bottom, then adds the larger of the original design gap, the previous item’s layout.gap_after, and any built-in minimum gap such as a section heading after a table. flow is body-only: it does not affect header, footer, layers, or watermark content. Planning follows JSON order, not layout.z_index.
  • For table-followed-by totals, notes, or signatures, use a body table followed by sibling body containers in pages[].elements. With flow enabled, the table advances by its measured rendered height, layout.gap_after creates the gap after it, and the following container moves after the table.
  • Do not set layout.flow: true on containers whose layout.children.overflow is paginate. If global settings.layout.flow is enabled and a page includes a paginated container, set that container’s layout.flow to false.
  • Keep generated elements inside the page or content box unless the schema explicitly allows overflow.
  • Omit settings.profile for ordinary PDFs, ordinary invoices, quotations, reports, and labels unless the user explicitly asks for PDF/A, archival compliance, accessibility conformance, or an e-invoice standard. If the user asks for a European/EU e-invoice or Factur-X/ZUGFeRD, use the separate E-Invoice API documentation.

Defaults and omission rules

  • settings.output is optional. settings.output.mode has two public values: binary and file. Omit settings.output or omit settings.output.mode to use the default binary response. Both modes return the same PDF bytes; binary returns inline Content-Disposition, while file returns attachment Content-Disposition.
  • Do not emit settings.defaults, settings.output, or style fields just to restate defaults. Omitted fields use runtime defaults; use explicit values only when the user asks for a visual choice or when stable brand/layout control matters.
  • Runtime text defaults are font_family: "RobotoMono-Regular", font_size: 8, color: "#000000", line_height: 0.9, and text_align: "left".
  • Runtime primitive stroke defaults are line.stroke: "#000000" / 0.4mm and rect.stroke: "#000000" / 1.0mm; rect.corner_radius defaults to 0.
  • There is no universal fill color default for every element. Set fill only when a visible fill is required.
  • font_family is optional. If it is omitted, gPdf uses the system default family in auto font mode and may fall back through bundled fonts for text coverage, including common CJK text. For polished invoices, quotations, reports, and other business documents, prefer settings.defaults.text.font_family: "NotoSans-Regular" with font_mode: "prefer" unless the user asks for another brand font. If no brand or visual polish is required, omit font_family.
  • Common built-in font family names you may explicitly declare when needed: RobotoMono-Regular, RobotoMono-Bold, NotoSans-Regular, NotoSans-Bold, NotoSansSC-Regular, NotoSansSC-Bold, NotoSansJP-Regular, NotoSansJP-Bold, NotoSansKR-Regular, NotoSansKR-Bold, NotoSansArabic-Regular, NotoSansArabic-Bold, NotoSansThai-Regular, NotoSansHebrew-Regular, NotoSansBengali-Regular, NotoSansTamil-Regular, NotoSans_ru-RU, NotoSans_vi-VN, NotoSansOsage-Regular, and Mansalva-Regular. When unsure, omit font_family and let auto fallback choose.
  • A declared font_family without font_mode: "prefer" is strict. If a declared Latin/default family may need Chinese, Japanese, or Korean fallback, set font_mode: "prefer" in the same style object.

Text alignment

  • Simple string and span text can use style.text_align, but right, center, and justify alignment need a finite text box such as style.width.
  • Do not write style.align in simple text, table-cell text, barcode_text.style, or TextStyle. Use style.text_align there. The field name align belongs only to block ParagraphStyle, such as defaults.paragraph.align or paragraph block style.align.
  • Block text is a text.content shape, not an element type; do not output an element whose type is "block". Block text uses defaults.paragraph.align or per-block paragraph style with frame.width.
  • Plain string variables use {page} and {total_pages}. Do not use ${page} or ${total_pages}; the dollar sign is literal while the variable still resolves separately. Block text uses inline nodes such as { "type": "variable", "name": "page", "scope": "system" }.
  • Table-cell text uses table width, column width, padding, offsets, and text_align; table-cell text style must not include frame-like fields such as width or height.

Two-column business sections

For paired left/right business sections, such as shipper details plus right-flush document metadata, put each pair in one invisible container. The container becomes one flow item, while its child text boxes keep predictable local positions.

  • Column placement and text alignment are separate decisions. Use layout.left: 0 for the left column. Use layout.anchor.reference: "content_right" plus style.width for the right column.
  • A right-side text box is not automatically right-aligned. Use style.text_align: "right" only when the text itself should flush right inside that box, such as metadata numbers. For a right-side address block whose lines should start at the column’s left edge, omit text_align or set it to "left".
  • With settings.layout.page_margin, content_right means the right edge of the content box, not the physical page edge.
  • Keep items that must share the same visual row inside the same container. Do not make the left item and the right item two independent body flow elements, because flow will place them one after another vertically. This applies to top header pairs, invoice/quotation metadata rows, shipper plus document-number rows, and ship-to plus bill-to rows.
{
  "settings": {
    "layout": {
      "flow": true,
      "gap_after": 6,
      "page_margin": {
        "top": 15,
        "right": 15,
        "bottom": 15,
        "left": 15
      }
    },
    "defaults": {
      "text": {
        "font_family": "NotoSans-Regular",
        "font_mode": "prefer",
        "font_size": 9,
        "color": "#4B5563"
      }
    }
  },
  "pages": [
    {
      "size": "a4",
      "elements": [
        {
          "type": "container",
          "width": 180,
          "height": 8,
          "layout": {
            "left": 0,
            "top": 0,
            "gap_after": 10
          },
          "elements": [
            {
              "type": "text",
              "content": "Acme Industrial Solutions LLC",
              "style": {
                "width": 90,
                "font_weight": "bold"
              },
              "layout": {
                "left": 0,
                "top": 0
              }
            },
            {
              "type": "text",
              "content": "DELIVERY NOTE / PACKING SLIP",
              "style": {
                "width": 90,
                "text_align": "right",
                "color": "#9CA3AF"
              },
              "layout": {
                "top": 0,
                "anchor": {
                  "reference": "content_right",
                  "offset": 0
                }
              }
            }
          ]
        },
        {
          "type": "container",
          "width": 180,
          "height": 36,
          "layout": {
            "left": 0,
            "top": 45,
            "gap_after": 6
          },
          "elements": [
            {
              "type": "text",
              "content": "FROM (SHIPPER):\nAcme Industrial Solutions LLC\n100 Enterprise Way, Suite 400\nSilicon Valley, CA 94025\nEmail: logistics@acme-solutions.com",
              "style": {
                "width": 82,
                "line_height": 1.35
              },
              "layout": {
                "left": 0,
                "top": 0
              }
            },
            {
              "type": "text",
              "content": "Packing Slip #: PS-2026-0042\nShip Date: 2026-06-20\nPurchase Order #: PO-2026-0775\nCarrier / Method: FedEx Ground",
              "style": {
                "width": 82,
                "text_align": "right",
                "line_height": 1.35
              },
              "layout": {
                "top": 0,
                "anchor": {
                  "reference": "content_right",
                  "offset": 0
                }
              }
            }
          ]
        },
        {
          "type": "container",
          "width": 180,
          "height": 32,
          "layout": {
            "left": 0,
            "top": 88,
            "gap_after": 6
          },
          "elements": [
            {
              "type": "text",
              "content": "SHIP TO (DELIVERY ADDRESS):\nGlobal Tech Systems Inc.\nReceiving Dock B (Attn: Warehouse)\n500 Innovation Blvd\nBoston, MA 02210",
              "style": {
                "width": 82,
                "line_height": 1.35
              },
              "layout": {
                "left": 0,
                "top": 0
              }
            },
            {
              "type": "text",
              "content": "SOLD TO (BILL TO):\nGlobal Tech Systems Inc.\nAttn: Accounts Payable\n500 Innovation Blvd, Floor 12\nBoston, MA 02210",
              "style": {
                "width": 82,
                "text_align": "left",
                "line_height": 1.35
              },
              "layout": {
                "top": 0,
                "anchor": {
                  "reference": "content_right",
                  "offset": 0
                }
              }
            }
          ]
        }
      ]
    }
  ]
}

Barcode text

  • QR and other 2D matrix codes should usually use equal width and height and omit barcode_text; their encoded content is normally too long to print below the code.
  • 1D linear barcodes may use independent width and height. Add barcode_text only when a short human-readable code should appear with the bars.
  • barcode_text.style and table-cell text styles must not include frame-like layout fields such as width, height, vertical_align, text_overflow, shrink_to_fit, or min_font_size. Use the parent barcode/table size, column width, padding, offsets, and text_align instead.

Path usage

  • Use path for native vector geometry. The d field is SVG path data only, such as M 0 0 L 100 0 L 100 40 Z; it is not a full SVG element.
  • view_box defines the local geometry space for d. Its subfields are geometry-only and must not be used as positioned element fields.
  • Do not put <path>, <defs>, CSS, transform, gradient, or url(#...) into d. Use solid fill / stroke, multiple simple paths, or an image element with svg/png/webp for complex artwork or gradients.

Table + Container Usage

Use a top-level body table followed by sibling body container elements when a table must be followed by totals, payment notes, signature rows, or approval blocks. This keeps the data table as a real table while letting the follow-up content use ordinary grouped layout.

  • Put the table and following container elements directly in pages[].elements.
  • Do not put table inside container; container descendants cannot be tables.
  • Prefer settings.layout.flow: true for table-followed-by-container layouts. Keep layout.top as a design coordinate on the table and following containers, and omit element-level layout.flow unless the element has no layout.top or layout.bottom.
  • Use table.layout.gap_after for the space between the rendered table bottom and the next totals/note/signature container.
  • Use container-local layout.right for amount values that align to the totals container’s right edge.
  • Treat subtotal, tax, shipping, paid, and grand total rows as one visual totals group. Keep one left column line for labels and one right column line for amounts. Bold or color can emphasize the final row, but it should not change the row’s left/right offsets.
  • Totals styles are composable: use borderless horizontal rules for compact receipt-like totals, an open summary plus a filled final total band for stronger branded invoices, or a border-only card when totals should stay grouped without row fill. Do not make every totals block the same framed card by default.

Invoice table followed by totals container

{
  "settings": {
    "layout": {
      "flow": true,
      "gap_after": 6,
      "page_margin": {
        "top": 15,
        "right": 15,
        "bottom": 15,
        "left": 15
      }
    }
  },
  "pages": [
    {
      "size": "a4",
      "elements": [
        {
          "type": "table",
          "width": 180,
          "columns": [
            {
              "key": "description",
              "header": "Description",
              "width": {
                "mode": "fixed",
                "value": 110
              }
            },
            {
              "key": "qty",
              "header": "Qty",
              "width": {
                "mode": "fixed",
                "value": 18
              },
              "cell": {
                "text": {
                  "text_align": "right"
                }
              },
              "header_cell": {
                "text": {
                  "text_align": "right"
                }
              }
            },
            {
              "key": "unit_price",
              "header": "Unit",
              "width": {
                "mode": "fixed",
                "value": 26
              },
              "cell": {
                "text": {
                  "text_align": "right"
                }
              },
              "header_cell": {
                "text": {
                  "text_align": "right"
                }
              }
            },
            {
              "key": "amount",
              "header": "Amount",
              "width": {
                "mode": "fixed",
                "value": 26
              },
              "cell": {
                "text": {
                  "text_align": "right"
                }
              },
              "header_cell": {
                "text": {
                  "text_align": "right"
                }
              }
            }
          ],
          "rows": [
            {
              "description": "Design system setup",
              "qty": 1,
              "unit_price": "$850.00",
              "amount": "$850.00"
            },
            {
              "description": "PDF automation workflow",
              "qty": 1,
              "unit_price": "$400.00",
              "amount": "$400.00"
            }
          ],
          "header": {
            "show": true,
            "repeat_on_page_break": true
          },
          "grid": {
            "horizontal": {
              "color": "#E5E7EB",
              "width": 0.2
            },
            "vertical": false
          },
          "pagination": {
            "row_min_height": 10,
            "header_min_height": 12
          },
          "layout": {
            "left": 0,
            "top": 20,
            "gap_after": 5
          }
        },
        {
          "type": "container",
          "width": 78,
          "height": 27,
          "fill": {
            "color": "#F9FAFB"
          },
          "stroke": {
            "color": "#D1D5DB",
            "width": 0.2
          },
          "corner_radius": 1.5,
          "layout": {
            "left": 102,
            "top": 0
          },
          "elements": [
            {
              "type": "text",
              "content": "Subtotal",
              "style": {
                "width": 40,
                "font_size": 8
              },
              "layout": {
                "left": 2,
                "top": 3
              }
            },
            {
              "type": "text",
              "content": "$1,250.00",
              "style": {
                "width": 32,
                "font_size": 8,
                "text_align": "right"
              },
              "layout": {
                "right": 2,
                "top": 3
              }
            },
            {
              "type": "text",
              "content": "Tax",
              "style": {
                "width": 40,
                "font_size": 8
              },
              "layout": {
                "left": 2,
                "top": 10
              }
            },
            {
              "type": "text",
              "content": "$100.00",
              "style": {
                "width": 32,
                "font_size": 8,
                "text_align": "right"
              },
              "layout": {
                "right": 2,
                "top": 10
              }
            },
            {
              "type": "line",
              "x1": 2,
              "y1": 17,
              "x2": 76,
              "y2": 17,
              "stroke": {
                "color": "#9CA3AF",
                "width": 0.3
              }
            },
            {
              "type": "text",
              "content": "Total due",
              "style": {
                "width": 40,
                "font_size": 9,
                "font_weight": "bold"
              },
              "layout": {
                "left": 2,
                "top": 20
              }
            },
            {
              "type": "text",
              "content": "$1,350.00",
              "style": {
                "width": 32,
                "font_size": 9,
                "font_weight": "bold",
                "text_align": "right"
              },
              "layout": {
                "right": 2,
                "top": 20
              }
            }
          ]
        }
      ]
    }
  ]
}

Table column width modes

Column width and text wrapping are separate decisions.

  • fixed: explicit millimetre width.
  • percent: percentage of table.width.
  • fit_content: measure header and body content first. Text, images, and barcodes participate in the measurement.
  • auto: content-aware flexible width that absorbs remaining table width after fixed, percent, and fit-content allocation.

Rules:

  • table.width is required when any column uses percent, auto, or fit_content.
  • If you need a column to stay on one line, use text wrapping controls such as wrap_policy: "no_wrap" in the appropriate text style. Column width modes are fixed, percent, fit_content, and auto.
  • Do not put frame-like fields such as width, height, vertical_align, text_overflow, shrink_to_fit, or min_font_size inside table-cell text styles. Use table width, column width, padding, offsets, and text_align.
  • Simple row shorthand accepts only scalar cell values: string, number, boolean, or null.
  • Rich block text, images, barcodes, merged cells, per-cell style, and cell links must use a complex cell envelope under the row key. Example: { "sku": { "barcode": { "format": "code128", "content": "SKU-001", "width": 32, "height": 10 }, "style": { "text": { "text_align": "center" } } }, "description": { "content": { "blocks": [{ "type": "paragraph", "content": [{ "type": "text", "text": "Bundle" }] }] }, "col_span": 2 } }. A complex cell may set at most one of content, image, or barcode; omit cells covered by row_span or col_span.
  • Do not put a bare { "blocks": [...] } object directly as a body rows[] cell value; wrap it as { "content": { "blocks": [...] } }.
  • If a table effectively has layout.flow: false, set pagination.row_min_height; when its header is visible, also set pagination.header_min_height.

Container usage

Use container when a group should move, edit, inherit descendant defaults, or clip as one unit. It is the correct primitive for cards, address blocks, QR verification panels, status badges, totals blocks, note boxes, signature boxes, bordered groups, clipped panels, and nested groups.

The problem container solves is visual grouping and local layout management. It gives a group one outer box, one local child coordinate space, optional fill/stroke, optional descendant defaults, optional clipping, and optional body-flow pagination. It is not a table, not a document-flow engine, and not an HTML/CSS div.

Continue using other primitives when they are semantically stronger:

  • Use table for tabular rows, columns, spans, repeated headers, and table pagination.
  • Use a sibling container after a body table for totals, notes, and signatures that should follow the rendered table.
  • Use a standalone rect for a simple decorative bar or page background that does not own child elements.
  • Use text directly for ungrouped labels.

Canonical container fields:

  • Required: type: "container", layout, width, height, and elements.
  • Placement: use layout.top or layout.bottom for vertical placement, and exactly one of layout.left, layout.right, or layout.anchor for horizontal placement.
  • Optional geometry: fill, stroke, corner_radius.
  • Optional child-box controls: layout.children.padding and layout.children.overflow.
  • Optional behavior: layout.children.mode, layout.children.axis, layout.children.gap, layout.children.main_align, layout.children.cross_align, layout.children.wrap, defaults, link, layout.z_index, and comment.
  • Child coordinates are relative to the content box after layout.children.padding.
  • Valid children are text, barcode, image, line, rect, circle, ellipse, polygon, path, link, and container.
  • table is not a valid container child.
  • A container is a fixed-size outer box. Its layout placement plus width and height define the rendered box; layout.children.padding creates the child content box. gPdf does not auto-size a container from its children.
  • In flow planning, a non-paginated container moves as one body element and advances following elements by its own outer height.
  • Estimate fixed container height from the largest child bottom edge plus padding, for example max(child layout.top + child rendered height) + padding.bottom. Leave 2-4mm extra for multiline text and font fallback, but avoid oversized containers in flow because they push following content downward and may create an unnecessary continuation page.

Container layout rules:

  • Omitted layout.children.mode means coordinate mode.
  • Use layout.children.mode: "linear" before using axis, gap, main_align, cross_align, or wrap.
  • For layout.children.mode: "linear", children still use normal gPdf element fields for measurement. In simple linear rows or columns, set child layout.left and layout.top to 0 and let the container layout place them by axis, gap, main_align, cross_align, and wrap.
  • Linear children are measured from their own rendered bounds. A text child without style.width is not automatically stretched to the container width; set explicit child widths when wrapping, alignment, or cross_align: "stretch" must be predictable.
  • layout.children.axis is horizontal or vertical.
  • layout.children.main_align is start, center, end, or space_between.
  • layout.children.cross_align is start, center, end, or stretch.
  • layout.children.wrap is boolean. Horizontal wrap starts a new row when the next child would exceed the content-box width; vertical wrap starts a new column when the next child would exceed the content-box height.
  • layout.children.overflow is visible, clip, or paginate.
  • Use paginate only for body containers that must continue across pages. A paginated container remains a fixed-width outer box; its configured height is the first fragment height, continuation fragments render the remaining children within generated page space, omit the repeated top padding after the first fragment, and draw fill/stroke as page fragments rather than auto-sizing the original container. Descendant text splits only when that text item explicitly uses frame.overflow: "paginate"; otherwise child items move whole to continuation fragments.
  • Do not set layout.flow: true on a paginated container. If global settings.layout.flow is enabled, set the paginated container’s layout.flow to false.
  • If the container itself has link, descendants must not declare element-level links or standalone type: "link" items.

Container-only linear row snippet:

{
  "type": "container",
  "width": 92,
  "height": 18,
  "layout": {
    "left": 0,
    "top": 0,
    "children": {
      "padding": { "top": 2, "right": 2, "bottom": 2, "left": 2 },
      "mode": "linear",
      "axis": "horizontal",
      "gap": 3,
      "main_align": "start",
      "cross_align": "center",
      "wrap": true
    }
  },
  "elements": [
    { "type": "text", "content": "PAID", "style": { "width": 18, "font_size": 8 }, "layout": { "left": 0, "top": 0 } },
    { "type": "text", "content": "Bank transfer", "style": { "width": 36, "font_size": 8 }, "layout": { "left": 0, "top": 0 } },
    { "type": "text", "content": "Due today", "style": { "width": 28, "font_size": 8 }, "layout": { "left": 0, "top": 0 } }
  ]
}

Minimal container example

{
  "pages": [
    {
      "size": "a4",
      "elements": [
        {
          "type": "container",
          "width": 84,
          "height": 34,
          "fill": {
            "color": "#F8FAFC",
            "opacity": 1
          },
          "stroke": {
            "color": "#CBD5E1",
            "width": 0.3
          },
          "corner_radius": 3,
          "elements": [
            {
              "type": "text",
              "style": {
                "width": 72,
                "font_size": 8,
                "color": "#64748B"
              },
              "content": "Payment status",
              "layout": {
                "left": 0,
                "top": 0
              }
            },
            {
              "type": "text",
              "style": {
                "width": 72,
                "font_size": 15,
                "font_weight": "bold",
                "color": "#0F172A"
              },
              "content": "Paid",
              "layout": {
                "left": 0,
                "top": 10
              }
            }
          ],
          "layout": {
            "left": 20,
            "top": 24,
            "children": {
              "padding": {
                "top": 5,
                "right": 6,
                "bottom": 5,
                "left": 6
              }
            }
          }
        }
      ]
    }
  ]
}

Paginated container example

Use overflow: "paginate" only for containers in pages[].elements or nested inside another body container. Do not use it in headers, footers, or layers. This example intentionally contains enough text to render continuation pages.

{
  "pages": [
    {
      "width": 80,
      "height": 28,
      "elements": [
        {
          "type": "container",
          "width": 48,
          "height": 16,
          "fill": {
            "color": "#F8FAFC"
          },
          "stroke": {
            "color": "#2563EB",
            "width": 0.4
          },
          "elements": [
            {
              "type": "text",
              "frame": {
                "width": 42,
                "overflow": "paginate"
              },
              "defaults": {
                "run": {
                  "font_size": 8
                }
              },
              "content": {
                "blocks": [
                  {
                    "type": "paragraph",
                    "inlines": [
                      {
                        "type": "text",
                        "text": "Long container text that must continue across generated pages when the first container fragment fills. Each continuation page keeps the same container width and renders the remaining block text in the next fragment. This sample intentionally includes enough words to demonstrate real pagination instead of a one-page placeholder."
                      }
                    ]
                  }
                ]
              },
              "layout": {
                "left": 0,
                "top": 0
              }
            }
          ],
          "layout": {
            "left": 4,
            "top": 4,
            "children": {
              "padding": {
                "top": 1,
                "right": 1,
                "bottom": 1,
                "left": 1
              },
              "overflow": "paginate"
            }
          }
        }
      ]
    }
  ]
}

Testing boundary

The sandbox endpoint is POST /api/playground?endpoint=pdf-render on the website origin. It does not require an Authorization header and returns application/pdf on success. It is limited to 60 requests per minute per IP address; on sandbox HTTP 429, wait and retry with client-side exponential backoff plus jitter rather than retrying immediately in a tight loop. Production rendering uses POST https://api.gpdf.com/api/v1/pdf/render with a bearer token.

An AI assistant may say a payload is schema-oriented or expected to render only before testing. It may say rendered, verified, or visually checked only after performing the real API/tool call and inspecting the resulting PDF.

AI tool integration guide

The official prompt at https://gpdf.com/gpdf-agent-prompt.txt is the source of truth. Use one of the examples below as a short task prompt: it tells the AI to read the official prompt when URL access is available, follow gPdf syntax, generate a DocumentRequest JSON payload, and produce a PDF only through a real render call or render tool. The official prompt already directs tool-capable assistants to the OpenAPI contract when exact schema lookup is needed.

Ask an AI coding agent or web chat to create a sample bank statement and password-protect the generated PDF.

Please generate a gPdf DocumentRequest JSON payload and a PDF file.

Official gPdf system prompt: https://gpdf.com/gpdf-agent-prompt.txt
Strictly follow that prompt and gPdf syntax.

If the current chat can read URLs, first read the official prompt and use it as the source of truth. It includes schema rules, container usage, the no-auth sandbox endpoint, the sandbox fair-use policy, and render-verification boundaries.
If the URL cannot be read, say so briefly. Do not claim the PDF was rendered or visually checked unless you actually render and inspect it.

Task:
Generate a sample bank statement.
Opening the PDF must require password: 123456.

Use year-month-day-hour-minute-second in the generated file names.

Ask the AI to generate a US-market invoice sample, then return both the JSON payload and rendered PDF artifact.

Please generate a gPdf DocumentRequest JSON payload and a PDF file.

Official gPdf system prompt: https://gpdf.com/gpdf-agent-prompt.txt
Strictly follow that prompt and gPdf syntax.

If the current chat can read URLs, first read the official prompt and use it as the source of truth. It includes schema rules, container usage, the no-auth sandbox endpoint, the sandbox fair-use policy, and render-verification boundaries.
If the URL cannot be read, say so briefly. Do not claim the PDF was rendered or visually checked unless you actually render and inspect it.

Task:
Generate an invoice sample suitable for the US market.
Use a clean business layout, a totals section, and a QR code verification block.

Use year-month-day-hour-minute-second in the generated file names.

Ask the AI to generate a US-market concert ticket and keep barcode contrast high enough for scanning.

Please generate a gPdf DocumentRequest JSON payload and a PDF file.

Official gPdf system prompt: https://gpdf.com/gpdf-agent-prompt.txt
Strictly follow that prompt and gPdf syntax.

If the current chat can read URLs, first read the official prompt and use it as the source of truth. It includes schema rules, container usage, the no-auth sandbox endpoint, the sandbox fair-use policy, and render-verification boundaries.
If the URL cannot be read, say so briefly. Do not claim the PDF was rendered or visually checked unless you actually render and inspect it.

Task:
Generate a concert ticket for the US market.
The barcode color and ticket color must have strong contrast so the barcode is easy to scan.

Use year-month-day-hour-minute-second in the generated file names.