Placement modes
Where signature fields land on the PDF — anchors, auto-append, explicit. Visualised, with when-to-use guidance. Manual placement is retired.
Pick a mode with the placement form field on
POST /v1/signing-requests. The
default is anchors with a fall-through to auto_append when no
markers are found. Three live modes; every one of them places the
fields at API call time, so the request that answers 201 is the
request that went out.
manual is retired. A fourth mode — placement="manual": POST the
PDF and the signers with no field information, get back a
placementUrl, and let a person drop the fields in a hosted editor
before anything was sent — was retired on 2026-07-31. It never
dispatched a request in production, and the guest placement editor it
opened went with the classic-editor retirement. The value is still
parsed so that an old request body fails loudly instead of with a
generic validation error: POST /v1/signing-requests answers
400 placement_retired before any document is minted
(placement_assignee_email is accepted and ignored), and its companion
POST /v1/documents — ingest a PDF by URL, get the same placement
link — answers 410 placement_retired.
Use one of the three live modes instead: anchors when your generator
can drop [[ls:signature:role]] markers into the PDF, explicit when
it already knows the coordinates, auto_append when a signature page on
the end is enough. Ingest-by-URL lives on as file_url on
POST /v1/signing-requests. To keep a
person in the loop before the send, stage a template instance with
review: true and confirm or discard it
— the fields come from the template, so there is nothing left to place.
At a glance
In every mode a field belongs to a role, and each signer needs its own
role: two signers with the same role are refused with
400 duplicate_role (meta.role, meta.emails), since each would get
every field of the other.
| Mode | When to pick it | Caller passes coordinates? | Field placement happens at |
|---|---|---|---|
anchors | You generated the PDF programmatically (HTML→PDF, LaTeX, Word) with markers. | No | API call time |
auto_append | Ad-hoc letters, no structure, just need a signature page on the end. | No | API call time |
explicit | You generated the PDF programmatically AND tracked field positions yourself. | Yes | API call time |
anchors (default)
Drop a marker like [[ls:signature:tenant]] in your PDF (HTML→PDF,
LaTeX, Word, anywhere with a real text layer). We find it, mask the
marker with a white rectangle, and place the signature field on top.
Tenant signature: [[ls:signature:tenant]] ─────────────────
Best ergonomics for programmatically generated contracts — HTML→PDF templates, document-assembly tools (Portant, DocAssemble), LaTeX, Pandoc. See Anchor placeholders for the full marker syntax.
Caveats:
- The PDF's text layer must contain the marker. Scanned-image PDFs don't qualify (run them through OCR first).
- Markers wrapped across two lines are skipped silently — keep them on one line in the source template.
- A marker can appear once per page per role for
signature/initial/date/name. Duplicates → 400duplicate_anchor. - Anchored signature and initials boxes get the caption check of
explicit placement as notices only, never refused:
caption_outside_pageorfield_over_captioninwarnings[], withfield= the marker as your PDF spells it ([[ls:signature:tenant]]).
auto_append
We add a fresh signature page after your PDF — one block per signer, role-labelled with a signature line and a date line. No coordinates needed from you.
Good for ad-hoc letters and quick-and-clean send-and-forget flows
where there's no existing signature area. Also the
fall-through when anchors mode finds zero markers in the
PDF: whether you passed placement="anchors" or left the default, we
switch to auto_append so the document still goes out, and the
response says so — "placement": "auto_append", anchors.found: 0.
Check those two fields if a PDF without markers must not be sent.
Caveats:
- Adds at least one extra page — six signer blocks fit on a page, so a seventh signer starts a second one.
- The appended page is A4 portrait, unrotated and uncropped, regardless of your source PDF's page size, rotation or crop.
explicit
You pass a fields[] array with one tuple per field, in normalized
[0..1] coordinates with a top-left origin:
{
"placement": "explicit",
"fields": [
{ "page": 0, "x": 0.10, "y": 0.78, "w": 0.30, "h": 0.06, "kind": "signature", "role": "tenant" },
{ "page": 0, "x": 0.55, "y": 0.78, "w": 0.30, "h": 0.06, "kind": "signature", "role": "landlord" },
{ "page": 0, "x": 0.10, "y": 0.74, "w": 0.15, "h": 0.03, "kind": "date", "role": "tenant" }
]
}{ "page": 0,
"x": 0.10,
"y": 0.78,
...Use when you generated the PDF programmatically AND tracked field positions at generation time — most often a templating system with inline metadata about where each field belongs.
Coordinate system:
- Origin top-left (matches our editor; PDF native is bottom-left
but you don't need to think about that). Pass
"origin": "center"on a field to give its centre instead. x,y,wandhare in[0, 1]— fractions of the visible page: the CropBox (the MediaBox when the page has none) after/Rotateis applied, measured from its visible top-left corner. That is the page as a PDF viewer shows it, as the signer sees it, and as the signature is drawn into the signed PDF. A value outside[0, 1]is a400 invalid_request, not clamped.pageis 0-based.- Portrait, landscape,
/Rotate90 / 180 / 270, a MediaBox that does not start at 0/0 and a CropBox inside the MediaBox are all supported. We never rotate or crop your PDF to make a field fit. - Strict, never approximate. A field must lie wholly on an existing page:
a
pagethe document does not have,x + w > 1,y + h > 1, aworhof 0, or anorigin: "center"field reaching past an edge is a422 field_out_of_bounds. A page carrying a field that we cannot place on exactly — a CropBox not wholly inside the MediaBox, a box written reversed, under 1 pt or unreadable, a/Rotatethat is not a number or not a multiple of 90 — is a422 page_geometry_unsupported. Both list every offending page and field inmeta.problems, and nothing is created. - Leave room for the caption. Every signature and initials box gets a
caption printed below it (name and email, signing time, platform, masked
IP and method — see
What prints under each signature):
42 pt under the box, 8.5 pt more for each extra line a stated
job_titleorcompanytakes. Keepy + h ≤ 1 − D / H, H being the visible page height in points (0.950 on A4 portrait for D = 42). A box whose caption would cross the bottom edge is still created and named inwarnings[](caption_outside_page,fieldfields[i]); from 2027-01-31 (00:00 UTC) it is a422 field_out_of_boundswithmeta.reasoncaption_outside_page(caption_depth_pt,space_below_ptinmeta.problems). A field inside those D pt under a signature or initials box, at least across the box's width, prints over the caption and is named (field_over_caption), never refused. A caption whose widest line would run past the right edge moves left; it is never cut there. kindissignature|initial|date|text(defaultsignature).roleMUST match asigners[].rolevalue (400 unknown_roleotherwise), and every signer needs at least onesignatureorinitialfield (422 signer_without_signatureotherwise).- A field accepts exactly
page,x,y,w,h,kind,roleandorigin; any other key is ignored and named inwarnings[](fields[i].<key>), and from 2027-01-31 (00:00 UTC) refused with400 unknown_field.
Date fields. A date field is filled by wesign when that signer's
signature is sealed; the signer never types it. It gets the signer's
signing day in the issuer's time zone (the workspace owner's profile
time zone, else their country's, else UTC), written DD.MM.YYYY —
MM/DD/YYYY for a workspace in the US — and centred in the box at half the
box's height, between 8 and 14 pt, shrunk to fit the box's width down to
6 pt. There is no format option on explicit fields. The signing page shows
the same text, computed when the page is opened.
Caveats:
- Coords land exactly where you say — or the call is refused. A bug in your generator that keeps the box on the page still puts it on top of the legalese.
- Page sizes vary; if your generator outputs Letter and the field table assumes A4, fields land where they shouldn't.
