Field Placement (beta)
These endpoints give you a programmatic way to position e-signature fields on a Loyva template without using the in-app builder UI. Use them when your system already knows where signatures, dates, or text fields belong — for example, when you generate the source PDF yourself and want signing fields placed at deterministic locations.
This API surface is in proof-of-concept stage. The request shape may change based on partner feedback. Email support@loyva.com if you depend on it so we can notify you of any breaking changes before they ship.
Two endpoints, picked by starting point
| If you have… | Use | Result |
|---|---|---|
| A fresh PDF and want to create a template from it. | POST /partner/templates (multipart) | Creates a new Loyva template + DocuSeal counterpart with the PDF and fields baked in. Returns a new template_id. |
| An existing Loyva template and want to add fields to it. | POST /partner/templates/:template_id/fields | Places fields on the existing template. Returns the resolved areas. |
Both endpoints share the same field/placement schema, so payloads are portable between them.
Three placement modes (per field)
| Mode | When to use | How |
|---|---|---|
| Absolute | You already know the (x, y, w, h, page) of each field. | placement.mode = "absolute" |
| Anchor | You want to say "put the signature near the text 'Signed by:'". You don't know the coords, but the source PDF contains a stable text marker. | placement.mode = "anchor" — we resolve the text against the PDF's text layer and convert to absolute coords. |
Embedded {{ }} tags | You generate the source PDF and can bake markers directly into it. | No placement block — bake the marker into the PDF before uploading. DocuSeal auto-extracts at upload. See Embedded tag syntax. |
Modes can be mixed within a single request.
Coordinate system
- All
x,y,w,h,offset_x,offset_yvalues are normalized 0.0–1.0 fractions of the page (top-left origin, y grows downward). pageis 1-based at the API boundary (page 1 = first page).- Example: a 200pt-wide field centered on an 8.5"x11" page (612pt wide) has
w ≈ 0.327.
If you send values greater than 1 we reject the request — that almost always means you sent pixels or percentages by mistake.
Create a template from a PDF
POST/partner/templates
Required scope: envelopes:write
Content-Type: multipart/form-data
Uploads a fresh PDF and (optionally) bakes field placement into the same call. Use this when you don't have a Loyva template yet — drop a PDF, define fields, and you're done in one round-trip. The response returns a new template_id you can use on subsequent envelope and field-placement calls.
Form fields
| Field | Type | Required | Description |
|---|---|---|---|
file | file (binary) | Yes | PDF, max 50 MB. |
name | string | Yes | Display name for the template. |
external_id | string | No | Your system's reference ID for the template. |
fields_json | string | No | JSON-encoded fields array (same schema as the placement endpoint below). Omit to create an empty template — then call POST /partner/templates/:id/fields to add fields later. |
fields_json is a stringified version of the same array you'd send to the placement endpoint. Anchor-mode fields are resolved against the uploaded PDF before being forwarded.
Example (curl)
curl -X POST https://api.stg.loyva.net/api/v2/partner/templates \
-H "X-API-Key: lk_..." \
-F "file=@./loan-agreement.pdf" \
-F "name=Loan Agreement v3" \
-F 'fields_json=[{
"name": "Borrower signature",
"type": "signature",
"role": "Signer 1",
"required": true,
"placement": {
"mode": "anchor",
"anchor_text": "Signed by:",
"offset_y": 0.02,
"w": 0.30,
"h": 0.05
}
}]'
Response (200)
{
"data": {
"template_id": "tmpl_a1b2c3d4e5f6g7h8",
"docuseal_template_id": 5821,
"name": "Loan Agreement v3",
"fields_added": 1,
"fields": [
{
"name": "Borrower signature",
"type": "signature",
"role": "Signer 1",
"required": true,
"anchor_match": { "page": 1, "x": 0.20, "y": 0.83, "w": 0.18, "h": 0.02 },
"areas": [{ "page": 1, "x": 0.20, "y": 0.85, "w": 0.30, "h": 0.05 }]
}
]
}
}
Hold on to template_id — that's the Loyva ID you'll pass to envelope-creation and follow-up field-placement calls.
Error responses
| Code | When |
|---|---|
400 | Missing file/name form field, or fields_json is not valid JSON. |
403 | Missing envelopes:write scope. |
413 | File too large (50 MB cap). |
415 | File is not application/pdf. |
422 | Validation failed, or an anchor text wasn't found in the uploaded PDF. |
500 | Failed to persist the Loyva template row. |
502 | Signing provider rejected the create. |
503 | Signing provider not configured. |
Place fields on an existing template
POST/partner/templates/:template_id/fields
Required scope: envelopes:write
Use this when you already have a template_id — either from an earlier POST /partner/templates call, an existing envelope, or one provisioned for you by Loyva support.
Request body
{
"fields": [
{
"name": "Borrower signature",
"type": "signature",
"role": "Signer 1",
"required": true,
"placement": {
"mode": "absolute",
"areas": [
{ "page": 1, "x": 0.10, "y": 0.85, "w": 0.30, "h": 0.05 }
]
}
},
{
"name": "Borrower date",
"type": "date",
"role": "Signer 1",
"required": true,
"placement": {
"mode": "anchor",
"anchor_text": "Date signed:",
"offset_x": 0.10,
"offset_y": 0,
"w": 0.15,
"h": 0.03
}
}
]
}
Field fields
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Display name shown to signers. |
type | enum | Yes | One of signature, initials, text, date, checkbox. |
role | string | Yes | Submitter role this field belongs to (e.g. "Signer 1", "Client"). Must match a role you use when creating submissions from this template. |
required | bool | No | Defaults to false. |
default_value | string | No | Prefill for text/date fields. |
placement | object | Yes | One of the two shapes below. |
Placement: absolute
{
"mode": "absolute",
"areas": [
{ "page": 1, "x": 0.10, "y": 0.85, "w": 0.30, "h": 0.05 }
]
}
Repeat objects in areas to place the same field on multiple pages (e.g. an "Initials" field on every page).
Placement: anchor
{
"mode": "anchor",
"anchor_text": "Signed by:",
"occurrence": 1,
"page": 2,
"offset_x": 0,
"offset_y": 0.02,
"w": 0.30,
"h": 0.05
}
| Field | Type | Required | Description |
|---|---|---|---|
anchor_text | string | Yes | Literal text to search for in the PDF text layer. Matching is case-insensitive and whitespace-normalized. |
occurrence | int | No | 1 (default) = first match in document order; 2 = second; etc. Out-of-range → 422. |
page | int | No | Restrict the anchor search to a specific 1-based page. Omit to search every page. |
offset_x | float | No | Horizontal offset from the anchor's top-left, as a 0–1 fraction of page width. Defaults to 0. |
offset_y | float | No | Vertical offset from the anchor's top-left, as a 0–1 fraction of page height. Defaults to 0. |
w | float | Yes | Field width (0–1 fraction of page width). |
h | float | Yes | Field height (0–1 fraction of page height). |
The anchor must appear in the PDF's text layer. Scanned-image PDFs without OCR'd text will return 422 Anchor text not found.
Response (200)
{
"data": {
"template_id": "tmpl_abc123",
"fields_added": 2,
"fields": [
{
"name": "Borrower signature",
"type": "signature",
"role": "Signer 1",
"required": true,
"areas": [
{ "page": 1, "x": 0.10, "y": 0.85, "w": 0.30, "h": 0.05 }
]
},
{
"name": "Borrower date",
"type": "date",
"role": "Signer 1",
"required": true,
"anchor_match": { "page": 1, "x": 0.20, "y": 0.83, "w": 0.18, "h": 0.02 },
"areas": [
{ "page": 1, "x": 0.30, "y": 0.83, "w": 0.15, "h": 0.03 }
]
}
]
}
}
For anchor-mode fields the response includes anchor_match — the rect of the text we matched — so you can verify the resolution was correct.
Error responses
| Code | When |
|---|---|
403 | Missing envelopes:write scope. |
404 | Template not found or not owned by your org. |
409 | Template has no document attached / no signing-provider counterpart. |
422 | Validation failed, or an anchor text wasn't found / requested occurrence exceeds matches. |
502 | Could not fetch the template PDF, or signing provider rejected the write. |
503 | Signing provider not configured. |
Embedded tags
If you generate the source PDF yourself, the simplest way to position fields is to bake tag markers directly into the document. Loyva's signing provider auto-extracts them at upload time — no POST /fields call needed.
Tag syntax (place anywhere in the PDF text):
{{Borrower Signature;type=signature;role=Signer 1;required=true;width=300;height=60}}
{{Date Signed;type=date;role=Signer 1;required=true;width=150;height=25}}
{{Borrower Initials;type=initials;role=Signer 1;width=80;height=30}}
Attribute reference:
| Attribute | Notes |
|---|---|
type | signature, initials, text, date, number, checkbox, select, radio. |
role | Submitter role (must match what you use when creating submissions). |
required | true / false. |
width, height | Pixels at 72 DPI (this is the only place pixel units appear — everywhere else in this doc uses 0–1 normalized fractions). |
default | Optional prefill value. |
readonly | true to lock the field. |
The tag is stripped from the rendered PDF at upload, leaving a field where it sat. Multiple identical tags (same name) place multiple fields.
To upload a tagged PDF as a partner, send it to POST /partner/templates without a fields_json — DocuSeal extracts the tags from the PDF on upload, and the template arrives in Loyva with the fields already in place.
Coordinate cheat sheet
For an 8.5" × 11" US Letter page (612 × 792 pt):
| Pixels (72 DPI) | Normalized x | Normalized y |
|---|---|---|
| 50pt from left | 0.082 | — |
| 1" from left | 0.118 | — |
| Page center x | 0.500 | — |
| 50pt from top | — | 0.063 |
| 1" from top | — | 0.091 |
| Bottom 15% | — | ~0.85 |
A standard signature box is typically w: 0.30, h: 0.05 (≈ 180pt × 40pt) — wide enough for a long name without overlapping nearby text.