Templates
A template is a PDF with roles ("Client", "Manager")
and fields placed once. Using it starts a normal
signature request: you name the real people for each role, optionally
prefill fields, and the document goes out like any other. The dashboard's
visual editor and POST /v1/templates write the same
definition, so what you build in one you can read, diff, and push from the
other.
Create one
In the dashboard: Templates, New template, upload the PDF, then place fields on the page (drag, or keyboard: arrow keys move a selected field by 0.5% of the page, Shift by 2%, Alt resizes). Every field belongs to a role; a template with an unassigned field cannot be saved, and the error names the field and page.
Over the API, the PDF travels base64-encoded and the definition is JSON:
curl -s -X POST https://signenvoystaging.fly.dev/v1/templates \
-H "Authorization: Bearer $SENV_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Consulting agreement",
"pdf_base64": "JVBERi0xLjQK...",
"recipients": [
{"name": "Client", "email": "client@placeholders.example", "role": "signer", "signing_order": 1},
{"name": "Manager", "email": "manager@placeholders.example", "role": "approver", "signing_order": 2}
],
"fields": [
{"type": "signature", "recipient_index": 0, "page": 1, "x": 0.1, "y": 0.75, "w": 0.3, "h": 0.05, "name": "client_sig"},
{"type": "text", "recipient_index": 1, "page": 2, "x": 0.1, "y": 0.2, "w": 0.3, "h": 0.03,
"name": "po_number", "required": false, "settings": {"max_length": 20}}
]
}' {
"id": "tpl_...",
"name": "Consulting agreement",
"version": 1,
"updated_at": null,
"recipients": [...],
"fields": [...],
"sha256": "..."
} recipients are placeholder identities, bound by position:
the first real recipient you pass at use time becomes role 0, the second
role 1, and so on. Their role and signing order come from the template.
The placeholder email is never mailed.
Fields
type:signature,initials,name,email,date,text,number,checkbox,radio,dropdown.recipient_index: which role fills it in (0-based position inrecipients).page,x,y,w,h: where it sits. Coordinates are fractions of the page in[0, 1], origin at the top-left corner,ymeasured down from the top, relative to the page as stored (before any rotation the PDF asks viewers to apply). Renderers apply the rotation; the numbers do not change. A rect ofx: 0.25, y: 0.75, w: 0.5, h: 0.05is a wide box low on the page.name: optional, letters, digits, dots, dashes, underscores, unique within the template. It is howform_valuesrefers to the field.required,read_only,prefill_value, andsettings(max_length,pattern,min_value,max_value,input_modesfor signatures,optionsfor radio and dropdown).
Use one
curl -s -X POST https://signenvoystaging.fly.dev/v1/templates/tpl_.../use \
-H "Authorization: Bearer $SENV_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"title": "Consulting agreement: Acme",
"recipients": [
{"name": "Ada Lovelace", "email": "ada@acme.example"},
{"name": "Bob Byrne", "email": "bob@acme.example"}
],
"form_values": {"po_number": "PO-88213"},
"display_timezone": "Europe/London"
}'
The response is a draft document (doc_..., status
draft) with the template's fields copied in. Send it with
POST /v1/documents/{id}/send.
form_values keys resolve by field name first,
then by field index as a string ("0", "1", in
the template's field order). An unknown key fails the whole request:
HTTP/1.1 422 Unprocessable Content
Content-Type: application/problem+json
{
"type": "https://docs.signenvoy.com/errors/validation-failed",
"title": "Validation failed",
"status": 422,
"detail": "form_values: unknown field key(s) 'po_numbre'",
"docs_url": "https://docs.signenvoy.com/errors/validation-failed"
}
Silently dropping a prefill would be the worst outcome (a document goes
out with a blank where a value was meant to be), so a typo is refused
rather than ignored. See validation-failed.
display_timezone, date_format, and
redirect_url on the use request set the document's display
preferences. Leave them out and the workspace's team defaults apply,
the same as for any other new document.
Change one: versions
curl -s -X PATCH https://signenvoystaging.fly.dev/v1/templates/tpl_... \
-H "Authorization: Bearer $SENV_API_KEY" \
-H "Content-Type: application/json" \
-d '{"fields": [ ... the full field list ... ]}' PATCH replaces the parts you send (name,
recipients, fields) and bumps
version; updated_at moves with it. Documents
already created from the template carry their own copy of the definition
and are unaffected. The next use takes the new version. The dashboard
says the same thing on every save.
DELETE /v1/templates/{id}(204) hides the template from listing, reading, and use. Documents created from it are unaffected.POST /v1/templates/{id}/duplicate(201) copies the PDF and the definition under a new id at version 1; pass{"name": "..."}or get "(copy)" appended.POST /v1/documents/{id}/duplicate(201) does the same for a document: a fresh draft with the same PDF, people, fields, and display preferences, and no signing links.
Templates as code
GET /v1/templates/{id} returns the complete definition,
fields and recipients included, exactly as
stored. The dashboard editor reads and writes the same shape: a field
placed in the editor and a field pushed over the API are the same field,
and a setting the editor cannot display is kept rather than dropped.
From senv 0.2.0, senv template pull <id>
writes that definition to a file and senv template push
sends it back as a PATCH (a new version); senv
template use <id> --var po_number=PO-88213 prefills by name.
Until then the same round trip is the two API calls above.
Who can use templates
Personal workspaces have a template library and the editor in the
dashboard but no API. Pro workspaces share templates across the team:
every member sees the team's templates, and GET /v1/templates
lists them.