xPdf API Specification
Version: Unified API / V2 Engine Updated: 2026-03-10
This document describes the current public schema. The only canonical route is
POST /api/v1/label.prod/testare distinguished solely by environment and token.
1. API Overview
| Route | Authentication | Purpose |
|---|---|---|
POST /api/v1/label | Authorization: Bearer <token> | The only public request endpoint |
Content-Type:application/json- Response:
application/pdf(binary stream) - Default output mode:
binary - When
settings.output.mode = "file", the response includesContent-Disposition
2. Request Structure
{
"settings": {
"defaults": {
"text": {
"font_family": "NotoSans-Regular",
"font_size": 11,
"color": "#111111"
},
"stroke": {
"color": "#000000",
"width": 0.4
},
"fill": {
"color": "#FFFFFF",
"opacity": 1.0
},
"shape": {
"corner_radius": 0
}
},
"metadata": {
"title": "xPdf Document",
"author": "xPdf"
},
"output": {
"mode": "file",
"file_name": "invoice-20260310.pdf"
},
"profile": "pdfa-2b"
},
"header": {
"height": 12,
"elements": []
},
"footer": {
"height": 10,
"elements": []
},
"pages": [
{
"size": "label_100_150",
"elements": []
}
]
}3. Top-Level Objects
3.1 DocumentRequest
| Field | Type | Required | Description |
|---|---|---|---|
settings | Settings | No | Global settings |
pages | Page[] | Yes | Page array |
header | Section | No | Global header |
footer | Section | No | Global footer |
3.1.1 Page Sizing
Each page must explicitly define its size using one of two approaches:
sizewidth+height
Rules:
sizeandwidth/heightare mutually exclusive- When
sizeis not provided, bothwidthandheightmust be specified sizematching is case-insensitive
Supported size presets:
a4=210 x 297 mma6=105 x 148 mmletter=215.9 x 279.4 mmlegal=215.9 x 355.6 mmlabel_100_100=100 x 100 mmlabel_100_150=100 x 150 mmlabel_4_6_in=101.6 x 152.4 mm
Example:
{
"pages": [
{ "size": "A4", "elements": [] },
{ "width": 100, "height": 150, "elements": [] }
]
}3.1.2 Page Margin / Content Box
Optional configuration:
settings.page_marginpages[].margin
Example:
{
"settings": {
"page_margin": { "top": 10, "right": 12, "bottom": 10, "left": 12 }
},
"pages": [
{
"size": "letter",
"margin": { "top": 8, "right": 10, "bottom": 12, "left": 10 },
"elements": []
}
]
}Rules:
- Without
page_margin, body elements use absolute page coordinates. - Once
page_marginis configured,pages[].elementsx/ybecome relative to thecontent boxtop-left corner. - Body elements that exceed the
content boxtrigger a validation error (no automatic clipping). header/footerstill use page coordinates and are not affected bypage_margin.- When body content paginates to subsequent pages, it continues from the next page's
content boxtop.
3.2 Section (header / footer)
header and footer share the same structure:
{
"height": 12,
"elements": [
{ "type": "text", "x": 5, "y": 8, "content": "Page Header" }
]
}| Field | Type | Required | Description |
|---|---|---|---|
height | number | Yes | Section height in mm |
elements | Element[] | No | Elements to render in this section |
Semantics:
headeris a global page header, applied to every page.footeris a global page footer, applied to every page.- An empty
elementsarray does not eliminate the section; do not rely on "empty array ignores the section."
Coordinates:
header.elementsrender in absolute page coordinates, typically withiny = 0 .. header.height.footer.elementsare automatically offset to the page bottom bypage.height - footer.height. Footer elements should use coordinates relative to the footer area, e.g.y = 0 .. footer.height.
Relationship with body:
headerdoes not automatically push body elements down. Body elements still render in absolute page coordinates.- If using a header, position body elements to avoid the header area, e.g. starting from
y >= header.height. footer.heightaffects the available body height calculation; body pagination avoids the footer area.- With
page_marginconfigured, body pagination to subsequent pages starts from the next page'scontent boxtop.
Best practices:
- Set
header.height/footer.heightclose to the actual content height; avoid excessively large values. - For fixed decorative regions, use global
header/footerinstead of duplicating content on every page. - For per-page varying header/footer content, place them in
pages[].elementsinstead.
3.3 Settings
| Field | Type | Required | Description |
|---|---|---|---|
defaults | Defaults | No | Global default styles |
metadata | Metadata | No | PDF metadata |
output | OutputSettings | No | Response output mode and filename |
profile | string | No | PDF/A profile |
page_margin | PageMargin | No | Body content area page margins |
Supported profile values: pdfa-1b, pdfa-2b, pdfa-3b, pdfa-4, pdfa-2u, pdfa-3u, pdfa-ua1, etc.
3.3.1 OutputSettings
| Field | Type | Required | Description |
|---|---|---|---|
mode | binary | file | No | Output mode, default binary |
file_name | string | No | Custom filename, only effective when mode = "file" |
Rules:
binaryor omitted: returns only the PDF binary.file: returns withContent-Dispositionfor file download.- If
file_nameis omitted: falls back togPdf-MMDDHHmmssSSS.pdf. file_nameis sanitized and automatically appended with.pdf.
3.4 Defaults
| Field | Type | Description |
|---|---|---|
text | TextStyle | Default text style |
stroke | StrokeStyle | Default stroke style |
fill | FillStyle | Default fill style |
shape | ShapeDefaults | Default shape style |
ShapeDefaults:
| Field | Type | Description |
|---|---|---|
corner_radius | number | Default corner radius for rectangles (mm) |
Note:
settings.defaultsonly accepts the structured format:text / stroke / fill / shape
3.5 Metadata
| Field | Type | Description |
|---|---|---|
title | string | Title |
author | string | Author |
subject | string | Subject |
creator | string | Creator tool |
producer | string | Producer |
language | string | Language code |
4. Element Types
Every item in elements[] must include a type field:
textbarcodelinerectcircleellipsepolygonlinkimagetablestack
Common fields:
- Most elements support
z_index(default0) - Most elements support
comment(annotation only, not rendered) - Elements supporting rotation:
text,barcode,rect,ellipse,image(0/90/180/270) - Hyperlinks are supported in two modes:
- Attaching a
linkfield to elements (text/barcode/line/rect/circle/ellipse/polygon/image) - Independent hotspot element
type: "link"
- Attaching a
4.1 x_anchor (Horizontal Anchor Positioning)
x_anchor automatically calculates an element's final x based on a reference boundary.
Currently supported elements:
textbarcoderectimagelink
Not supported:
line/circle/ellipse/polygon/table/stack/block
Rules:
xandx_anchorare mutually exclusive; providing both triggers an error- Without
x_anchor, elements use the originalxabsolute positioning - When
textusesx_anchor,style.widthis required - When
barcode / rect / image / linkusex_anchor, the element's ownwidthis used table_left / table_rightare only allowed insidestack -> block
Reference values:
page_leftpage_rightcontent_leftcontent_righttable_lefttable_right
Calculation rules:
- Left reference:
resolved_x = reference + offset - Right reference:
resolved_x = reference - offset - width
Example:
{
"type": "text",
"x_anchor": { "reference": "content_right", "offset": 8 },
"y": 12,
"content": "$1,235.85",
"style": {
"width": 24,
"text_align": "right"
}
}5. Style Objects
5.1 StrokeStyle
{
"color": "#111111",
"width": 0.5,
"opacity": 1.0,
"cap": "butt",
"join": "miter",
"miter_limit": 10,
"dash": {
"preset": "dashed",
"pattern": [3, 2],
"phase": 0
},
"compound": {
"kind": "double",
"gap": 0.3
}
}Fields:
cap:butt/round/squarejoin:miter/round/beveldash.preset:solid/dashed/dotted/customdash.patternshould only be provided whenpreset=customcompound.kind: currently onlydoubleis supportedcompound.gap: net gap between the two lines in mm
Notes:
- Without
compound, treated as a single line. compoundis shared byline,table.grid, andtable.cell.borders.double + dashis an invalid combination.double + markeris an invalid combination.rect/circle/ellipse/polygonwill report an error ifcompoundis provided.
5.2 FillStyle
{
"color": "#F5F5F5",
"opacity": 1.0,
"rule": "nonzero"
}rule: nonzero / even_odd
5.3 MarkerStyle (Line)
{
"start": "none",
"end": "arrow",
"size": 2.5
}start/end: none / arrow / open_arrow / circle / bar
5.4 LinkSpec (Hyperlink)
{
"target": { "type": "url", "url": "https://example.com" },
"alt": "open official site",
"padding": 1.0,
"border": { "color": "#1A202C", "width": 0.3 }
}LinkTarget:
- URL:
{ "type": "url", "url": "https://..." } - Page destination:
{ "type": "page", "page": 2, "x": 10, "y": 20 }
LinkBorderStyle:
color: hex colorwidth: line width (mm),0means no border drawn
Constraints:
- URL supports only
http://,https://,mailto:,tel: - URL whitespace is trimmed before writing
pagestarts from1and cannot exceed the number of explicitpagesin the requestpaddingmust be a finite number>= 0border.widthmust be a finite number>= 0border.color(if provided) must be a valid hex color- Any invalid
linkfield triggers a ValidationError (never silently skipped)
6. Shape Elements
line/rect/circle/ellipse/polygon all support optional link: LinkSpec.
6.1 Line
{
"type": "line",
"x1": 4,
"y1": 99,
"x2": 96,
"y2": 99,
"stroke": {
"color": "#000000",
"width": 0.4,
"dash": { "preset": "solid" }
},
"link": {
"target": { "type": "url", "url": "https://example.com/spec" }
}
}When stroke is omitted, default values are applied (see Section 9).
6.2 Rect
{
"type": "rect",
"x_anchor": { "reference": "content_right", "offset": 6 },
"y": 20,
"width": 60,
"height": 20,
"fill": { "color": "#FFFFFF" },
"stroke": { "color": "#222222", "width": 0.6 },
"corner_radius": 2
}6.3 Circle
{
"type": "circle",
"cx": 40,
"cy": 40,
"r": 12,
"fill": { "color": "#E6F4FF" },
"stroke": { "color": "#2B6CB0", "width": 0.5 }
}6.4 Ellipse
{
"type": "ellipse",
"cx": 70,
"cy": 40,
"rx": 16,
"ry": 10,
"rotation": 0,
"fill": { "color": "#FFF7E6" },
"stroke": { "color": "#C05621", "width": 0.5 }
}6.5 Polygon
{
"type": "polygon",
"points": [
{ "x": 20, "y": 80 },
{ "x": 35, "y": 60 },
{ "x": 50, "y": 80 },
{ "x": 40, "y": 95 }
],
"fill": { "color": "#F0FFF4" },
"stroke": { "color": "#2F855A", "width": 0.5 }
}6.6 Link (Independent Hotspot)
{
"type": "link",
"x_anchor": { "reference": "content_left", "offset": 10 },
"y": 10,
"width": 40,
"height": 8,
"target": { "type": "url", "url": "https://example.com" },
"alt": "Open website"
}7. Other Elements
7.1 Text
Required fields: y, content, and exactly one of:
xx_anchor
style uses TextStyle:
font_family,font_size,font_weight,colortext_align,vertical_align,line_heightwidth,height,text_overflowshrink_to_fit,min_font_size- Optional
link: LinkSpec
Supports variables: {page}, {total_pages}.
7.2 Barcode
Required fields: y, format, content, width, height, and exactly one of:
xx_anchor
Optional: options, hrt, rotation, z_index, comment, link
7.3 Image
Required fields: y, width, height, data, and exactly one of:
xx_anchor
Optional: format, rotation, z_index, comment, link
data supports:
- Filename (loaded from resource layer)
- Base64 data
7.4 Table
Note:
- The current public
tableschema only supports the structure defined in this section.
Top-level structure:
{
"type": "table",
"x": 12,
"y": 24,
"width": 180,
"columns": [],
"rows": [],
"cell": {},
"header": {},
"row_header": {},
"body": {},
"grid": {},
"pagination": {}
}Top-level fields:
| Field | Type | Required | Description |
|---|---|---|---|
x | number | Yes | Top-left X (mm) |
y | number | Yes | Top-left Y (mm) |
z_index | integer | No | Z-index |
comment | string | No | Annotation |
width | number | No | Total table width |
columns | TableColumn[] | Yes | Column definitions |
rows | TableRow[] | Yes | Row data |
cell | TableCellStyle | No | Default cell style for the entire table |
header | TableHeaderConfig | No | Column header configuration |
row_header | TableZoneConfig | No | Row header zone configuration |
body | TableBodyConfig | No | Body zone configuration |
grid | TableGridConfig | No | Grid line configuration |
pagination | TablePaginationConfig | No | Pagination configuration |
7.4.1 Column
{
"key": "amount",
"header": "Amount",
"width": { "mode": "fixed", "value": 30 },
"role": "data",
"cell": {
"text": { "text_align": "right" }
},
"header_cell": {
"text": { "font_weight": "bold" }
}
}| Field | Type | Required | Description |
|---|---|---|---|
key | string | Yes | Column key, must be unique |
header | string | No | Leaf column header text, default empty |
width | TableColumnWidth | Yes | Column width model |
role | string | No | data / row_header, default data |
cell | TableCellStyle | No | Body cell style for this column |
header_cell | TableCellStyle | No | Header cell style for this column |
Rules:
columns[].keymust be unique.- Columns with
role = "row_header"must be contiguous and placed leftmost. - Multiple row header columns are supported.
TableColumnWidth:
{ "mode": "fixed", "value": 30 }
{ "mode": "percent", "value": 25 }
{ "mode": "auto" }7.4.2 Row / Cell
rows is an array of objects, with keys corresponding to columns[].key.
Simple cell:
{ "name": "Apple", "qty": 2, "enabled": true, "note": null }Complex cell:
{
"group": {
"value": "Fruit",
"row_span": 2,
"col_span": 1,
"style": {
"text": { "font_weight": "bold" }
},
"link": {
"target": { "type": "url", "url": "https://example.com" }
}
}
}Complex cell fields:
| Field | Type | Required | Description |
|---|---|---|---|
value | string | number | boolean | null | No | Cell value |
row_span | integer | No | Number of rows to span downward |
col_span | integer | No | Number of columns to span rightward |
style | TableCellStyle | No | Cell style override |
link | LinkSpec | No | Cell hyperlink |
Rules:
row_span >= 1col_span >= 1- Spans cannot exceed boundaries or overlap with other spans
nullrenders as empty stringbooleanrenders as"true"/"false"linkcan only appear in complex cell objects
7.4.3 TableCellStyle
{
"padding": { "x": 1, "y": 1 },
"text": { "font_size": 9, "color": "#111111" },
"fill": { "color": "#FFFFFF" },
"content_offset_x": 1.5,
"content_offset_y": 0.5,
"borders": {
"top": false,
"right": { "color": "#111111", "width": 0.2 },
"bottom": { "color": "#111111", "width": 0.2 },
"left": false
}
}| Field | Type | Description |
|---|---|---|
padding | TablePadding | Cell padding |
text | TextStyle | Text style |
fill | FillStyle | Fill style |
content_offset_x | number | Horizontal content offset (mm) |
content_offset_y | number | Vertical content offset (mm) |
borders | TableBorders | Per-side cell borders |
TablePadding:
| Field | Type | Description |
|---|---|---|
x | number | Horizontal padding (mm) |
y | number | Vertical padding (mm) |
TableBorders:
top?: false | StrokeStyleright?: false | StrokeStylebottom?: false | StrokeStyleleft?: false | StrokeStyle
7.4.4 Header / Row Header / Body
header:
{
"show": true,
"repeat_on_page_break": true,
"rows": [
{
"cells": [
{ "content": "Product", "col_span": 2 },
{ "content": "Stock", "row_span": 2 }
]
}
],
"cell": {
"fill": { "color": "#F3F4F6" },
"text": { "font_weight": "bold" }
}
}Fields:
show?: boolean, defaulttruerepeat_on_page_break?: boolean, defaulttruerows?: TableHeaderRow[], grouped header rows; leaf headers still come fromcolumns[].headercell?: TableCellStyle
row_header:
{
"cell": {
"fill": { "color": "#F8F8F8" },
"text": { "font_weight": "bold" }
}
}Fields:
cell?: TableCellStyle
body:
{
"cell": {},
"alternate_fill": { "color": "#FAFAFA" }
}Fields:
cell?: TableCellStylealternate_fill?: FillStyle
Expression rules:
- Grouped headers use
header.rows - Leaf column headers use
columns[].header - Row headers use
columns[].role = "row_header"
7.4.5 Grid
{
"frame": {
"color": "#111111",
"width": 0.3,
"compound": { "kind": "double", "gap": 0.3 }
},
"horizontal": { "color": "#D1D5DB", "width": 0.2 },
"vertical": false
}Fields:
frame?: false | StrokeStylehorizontal?: false | StrokeStylevertical?: false | StrokeStyle
Note:
grid.frame,grid.horizontal,grid.verticalall useStrokeStyle
7.4.6 Pagination
{
"keep_spans_together": true,
"row_min_height": 10,
"header_min_height": 12
}Fields:
keep_spans_together?: boolean, defaulttruerow_min_height?: numberheader_min_height?: number
Note:
- When
row_spanis used,keep_spans_together = trueis required
7.4.7 Width & Style Rules
Width rules:
columns.length >= 1columns[].widthusesfixed / percent / auto- When
table.widthis provided:fixedoccupies space in mm firstpercentallocates based ontable.widthpercentageautotakes remaining space, distributed based on header/body content measurement- Without
auto, column widths must exactly filltable.width
- Without
table.width:- All columns must use
fixed
- All columns must use
percentsum must not exceed100- Unknown keys in
rows(not declared incolumns[].key) trigger an error - When
header.show = false,header.rows,columns[].header, andheader_cellare ignored - When using
row_span,keep_spans_together = falseis not supported
Style priority:
settings.defaultstable.cellheader.cell / row_header.cell / body.cellcolumns[].cell / columns[].header_cellcell.style
Border priority:
gridtable.cell.borders- Zone-level
cell.borders - Column-level
cell/header_cell.borders cell.style.borders
7.5 Stack / Block
Purpose:
stackis designed for invoice/statement scenarios where atableis followed by summary content.- It does not override table positioning;
table.x/y/widthretain their current semantics. stackonly manages the sequential layout and pagination between thetableand subsequent content blocks.
Structure:
{
"type": "stack",
"gap": 6,
"children": [
{
"type": "table",
"x": 18,
"y": 123,
"width": 180,
"columns": [],
"rows": []
},
{
"type": "block",
"elements": [
{ "type": "text", "x": 128, "y": 0, "content": "Subtotal" },
{ "type": "text", "x": 168, "y": 0, "content": "$1,343.65" },
{ "type": "line", "x1": 128, "y1": 7, "x2": 178, "y2": 7 }
]
}
]
}Top-level fields:
| Field | Type | Required | Description |
|---|---|---|---|
gap | number | No | Vertical gap between adjacent children in mm, default 0 |
children | StackChild[] | Yes | Sequential layout children |
Rules:
stackis only allowed inpages[].elementsstack.children[0]must betablestack.children[1..]must beblockchildren.length >= 2
block:
{
"type": "block",
"elements": [
{ "type": "text", "x": 128, "y": 0, "content": "Subtotal" },
{ "type": "text", "x": 168, "y": 0, "content": "$1,343.65" }
]
}Rules:
blockdoes not define its ownx/y/width/heightblock.elements[].xuses existing body element semanticsblock.elements[].yis relative to the start of the blockblockcannot nesttable/stack/block- Block height is automatically measured from its child elements
Pagination semantics:
- The
tablepaginates first using the existing logic blocklayout only begins after thetablehas fully completed- If a
blockdoesn't fit on the current page, it moves as a whole to the next page - If a single
blockis taller than one page's available body height, validation fails gapis the vertical distance from the previous child's end position to the next child's start- When a
blockmoves to the next page, the previous page'sgapis not preserved
8. Coordinates & Units
- Coordinate unit: mm
- Origin: top-left
(0, 0) - X-axis points right, Y-axis points down
9. Default Value Priority
9.1 Style Priority
- Element's own fields (e.g.
line.stroke.width) settings.defaultscorresponding fields (e.g.defaults.stroke.width)- System default configuration
table priority:
settings.defaultstable.cellheader.cell / row_header.cell / body.cellcolumns[].cell / columns[].header_cellcell.style
9.2 Line/Shape Default Behavior
- When
line.strokeis entirely omitted:colordefaults to#000000widthdefaults to0.4
- When
rect/circle/ellipse/polygon.strokeis entirely omitted:colordefaults to#000000widthdefaults to1.0
- When
fillis entirely omitted, no fill is applied (transparent) rect.corner_radiusdefault chain:element.corner_radiussettings.defaults.shape.corner_radius- System default
0
10. Error Codes
| Code | Trigger |
|---|---|
| API-001 | Invalid JSON payload |
| API-002 | Validation failure |
| API-101 | Missing or malformed Authorization header |
| API-102 | Authentication failed |
| API-500 | Internal system failure |
| API-501 | PDF rendering failure |
Common ValidationError triggers:
- Invalid
link(unsupported URL scheme, page index out of bounds, invalidpadding/border) - Invalid
table(unknown column key,table.widthcannot allocate positive width for undeclared columns, invalid span) - Invalid
profile xandx_anchorprovided simultaneously