Skip to content

Signature fields and coordinates

Where a participant’s handwritten signature is stamped into the PDF is described by a rectangle on a page. The same contract applies to every rectangle the API accepts or returns: the signature fields you predefine in POST /cases, the placements a signer sends to POST /participant/{participantId}/signature, and the fields GET /participant/{participantId}/caseDefinition returns.

Field Meaning
page Page number, counted from 1.
x, y The rectangle’s bottom-left corner.
width, height Its size. Both greater than 0.
unit pt or fraction, see below. Placements without a unit are in pt.

The origin is the bottom-left corner of the page’s MediaBox, with x growing to the right and y growing upwards. That is the PDF convention, and the opposite of a browser or an image, where y grows downwards from the top. Coordinates refer to the unrotated page.

An A4 page in PDF points. The rectangle {"page": 1, "x": 380, "y": 60, "width": 180, "height": 60} is measured from the bottom-left corner.A4 · 595 × 842 ptnot here: browsers and images start top-leftxy0, 0x = 380y = 60width 180height 60
An A4 page in PDF points. The rectangle {"page": 1, "x": 380, "y": 60, "width": 180, "height": 60} is measured from the bottom-left corner.
  • pt: PDF points, 1/72 inch. An A4 page is 595 × 842 pt, US Letter 612 × 792 pt.
  • fraction: a share of the page. x and width are divided by the page width, y and height by the page height, so every value lies between 0 and 1. Use it when you do not know the page size, for example to place a field in “the bottom-right quarter” of any page.

The rectangle must lie inside the page: x + width ≤ page width and y + height ≤ page height, with a page of 1 × 1 for fractions.

Rectangle Checked when
A fraction field or placement on the request: it must fit into 0..1
A pt placement sent with pageSize on the request, against that page size
A pt field or placement without a page size when the document is signed; one that reaches past the page is logged, a page the document does not have fails the signing

You can fix where each participant signs by giving them signatureFields in POST /cases:

{
"firstName": "Jana",
"lastName": "Nováková",
"email": "jana.novakova@example.com",
"signatureFields": [
{"id": "customer", "page": 1, "x": 0.55, "y": 0.05, "width": 0.35, "height": 0.08, "unit": "fraction"},
{"page": 2, "x": 56.7, "y": 70.9, "width": 170.1, "height": 56.7, "unit": "pt"}
]
}

A field without an id gets one from the server; the case in the response carries it. The signing page reads the fields from the case definition (participant.signatureFields), and the participant then signs exactly those fields:

{"encodedSignature": "<base64 PNG>", "fieldIds": ["customer", "4f1c..."], "pageSize": {"width": 595, "height": 842},
"consent": {"textVersion": "sk-2026-09", "acceptedAt": "2026-09-24T10:00:00+02:00"}}

A participant without predefined fields places the signature themselves:

{"encodedSignature": "<base64 PNG>", "signatures": [{"page": 1, "x": 380, "y": 60, "width": 180, "height": 60}],
"consent": {"textVersion": "sk-2026-09", "acceptedAt": "2026-09-24T10:00:00+02:00"}}

The two are exclusive. Sending fieldIds for a participant without fields, signatures for one with fields, both at once, or fieldIds that are not exactly the predefined set is answered with 400. Either way the body also carries the participant’s consent.

pageSize, in points, is required whenever a rectangle is in fraction, because the server converts everything to points before it is stored. One size applies to every page, so a document whose pages differ in size should use pt.

The signature image is a base64 PNG or JPEG of at most 200 KB once decoded, without a data: prefix or line breaks.

If you build your own signing page with pdf.js, it measures the pointer in CSS pixels from the top-left of the canvas. PageViewport.convertToPdfPoint does the conversion, the flip of the y axis included:

const viewport = page.getViewport({scale});
// the two corners of the box the signer drew, in canvas pixels
const [x1, y1] = viewport.convertToPdfPoint(left, top);
const [x2, y2] = viewport.convertToPdfPoint(left + boxWidth, top + boxHeight);
const rectangle = {
page: page.pageNumber,
x: Math.min(x1, x2),
y: Math.min(y1, y2),
width: Math.abs(x2 - x1),
height: Math.abs(y2 - y1),
unit: 'pt'
};
// the MediaBox corner, which is not always 0,0: the API measures from it
const [boxX, boxY] = page.view;
rectangle.x -= boxX;
rectangle.y -= boxY;

page.view is the visible box as [x1, y1, x2, y2]. It is the MediaBox unless the PDF sets a smaller CropBox; for such documents take the MediaBox from the page dictionary instead. pageSize is {width: view[2] - view[0], height: view[3] - view[1]}.

Coming from a top-left system of your own (an image, an HTML overlay), flip y yourself:

// top-left box in points → the API's bottom-left rectangle
const toApi = ({page, left, top, width, height}, pageHeight) =>
({page, x: left, y: pageHeight - top - height, width, height, unit: 'pt'});

Every rejected rectangle is reported in the usual 400 shape, {message, validationErrors}:

keyword When
insidePage the rectangle reaches past the page (params.limit is the page width or height)
required with missingProperty: pageSize fractions without a page size
oneOf both or neither of fieldIds and signatures
const fieldIds is not exactly the predefined set (params.allowedValue lists it)
forbidden fieldIds for a participant without fields, or signatures for one with fields
uniqueItems two predefined fields with the same id
contentEncoding, contentMediaType, maxSize the signature image is not base64, not PNG or JPEG, or too large