Docs

Docs / Guides / Templates and versions

Templates and versions

The text people sign, and why it is never changed under them.

Watch it done: Have a waiver drafted, then have it read (2:49)

A template has a code (rental-agreement) and numbered versions. A version is a draft until it is published, and a published version is frozen: the database refuses to change or delete it, because somebody may have signed it. Changing the words means a new version. Each document records the exact version it was made from, and the certificate says which, so "what did they sign?" always has one answer.

The lifecycle

StepCallWhat happens
CreatePOST /v1/templatesVersion 1, as a draft, with its text, placeholders, consent wording and questions.
Change the draftPATCH /v1/templates/{id}name, body, body_format, variables, fields (the whole list). Only while it is a draft.
PublishPOST /v1/templates/{id}/publishNew documents from this code use this version, and its text is frozen. Refused, by name, for a placeholder the template does not declare.
RevisePOST /v1/templates/{id}/reviseA new draft, one version higher, copied from the newest with whatever you change. Publish it to put it into use.
Go backPOST /v1/templates/{id}/restoreAn older published version's text, questions, consent wording and file, as a new draft above the newest. Its change_note says where it came from. Not while a draft is open.
Throw a draft awayDELETE /v1/templates/{id}Only a draft nothing was made from. The only version of a code going frees the code.
Read oneGET /v1/templates/{id}The version whole, with versions: every version of its code, newest first.
ListGET /v1/templates?code=Every version of every template, or one code's; no text.
// A new clause for next season, without touching what people signed this one.
const current = await signsealer.template(templateId);
const next = await signsealer.reviseTemplate(current.template_id, {
  body: current.body + "\n\nHelmets are required on every trail.",
});
await signsealer.publishTemplate(next.template_id);

Revise only from the newest version. Revising an older one would quietly throw away every change made since, so it answers 409 conflict, and the hint names the newest version's id. A draft is not revised; it is changed with PATCH.

What changed, and going back. Each version carries a change_note of up to 500 characters: set it on revise or with PATCH while it is a draft, and it is frozen with the version when it is published. GET /v1/templates/{id} lists every version with its note, name, whether it has a PDF and how many documents were made from it -- the same list as the dashboard's history page, which also compares any two versions line by line. To go back to an older wording, POST /v1/templates/{id}/restore on that version makes a new draft above the newest; the versions in between keep their numbers and their signatures.

Questions and fields

A template's fields are what it asks besides the signature: a date of birth, a tick against each safety statement, a photo of the boat before it leaves. Send them as a list on POST /v1/templates, or as the whole list on PATCH /v1/templates/{id} and POST /v1/templates/{id}/revise, where the list you send replaces the one it had. GET /v1/templates/{id} answers with them in the same shape. A template asks at most fifty.

The list is checked twice. The API checks each question's shape and answers 422 invalid naming the property. The engine then reads the list as a whole and refuses it with 422 and a sentence naming the question: a condition on a question below it, a block with two headings, a choice with one option. A refused list on a create leaves no template behind.

A property that is not in the table below is dropped without a word. A misspelt show_if saves, answers 201, and leaves a question that is always asked. Read the template back with GET /v1/templates/{id} when you are building the list.

Kinds

Every answer is checked by the engine when it is given, whatever the page or your client did first. An empty answer to a required question is refused as required; an optional one may be left empty.

KindWhat it acceptsHow the signing page asks it
signatureAnything that is not empty. Cannot be optional.Placed on an uploaded PDF, it is the box the signature itself fills, and is never asked. Asked under the document, it is a typed line: a second signature, typed.
initialsAnything that is not empty. Cannot be optional.A one-line box. On a page, up to six characters.
checkboxTicked is true, yes, on or 1, in any case. A required one has to be ticked.On its own, a button: I confirm: and the label, and Leave it unticked beside it when it is optional. In a block, a tickbox.
textAnything that is not empty.A one-line box.
paragraphAnything that is not empty. No minimum length: "No damage" is an answer.A box several lines tall.
dateYYYY-MM-DD, and a day the calendar has: 2026-02-31 is refused.A date picker. Placed on a page with a signing-date key or label, it is filled by signing instead (Signing a PDF).
birthdateAs a date, and no later than tomorrow (a day of slack for time zones), with the year 1900 or later, so 0084-05-02 is refused.Month and day picked, the year typed. On a page, a date picker.
timeHH:MM on a 24-hour clock: 14:30, 9:05.A time picker.
datetimeYYYY-MM-DDTHH:MM, or a space for the T, on a day the calendar has.A date-and-time picker.
photoA JPEG or PNG, sent to the photo route. Text is refused. The answer becomes sha256:<hex>.A camera button. An optional one offers Carry on without a photo.
emailSomething, an @, then a domain with a dot in it, and no spaces.An email box, with the email keyboard.
phoneAt least seven digits; anything else in it is ignored for the count.A country code to pick and the number to type. On a page, one box.
addressAt least five characters.A box several lines tall, rather than street, city and zip: those are not the shape of an address everywhere.
numberDigits, with a minus in front and a decimal point if needed: 12, -3, 4.5. No commas, no units.A number box, with the number keyboard.
moneyAn amount as people type one: 1250, 1,250.00, $1,250, USD 40. Two decimal places at most. Written into the document as typed.A box with the number keyboard, which takes a currency sign and commas.
vinSeventeen letters and digits, never I, O or Q; spaces and dashes are ignored. Written in capitals.A box that types in capitals.
ssn_last4Exactly four digits. A whole Social Security number is refused, not trimmed.A four-digit box with the number keyboard, never offered back by the browser.
postal_codeThree to ten letters and digits, with a space or a dash between, and at least one digit: 90210, 90210-1234, SW1A 1AA.A box the browser can fill with the postal code it knows.
percentA number, with or without its %. Written into the document with it.A box with the number keyboard.
choiceExactly one of its options, as written.Six options or fewer are buttons to tap; more are a list.

Properties

PropertyTakesWhat it does
keyRequired. A lower-case letter, then up to 58 lower-case letters, digits and underscores. Once per list.Names the answer everywhere: in answers, on POST /v1/sign/{token}/fields, and in values for a box the sender fills.
kindRequired. One of the kinds above.What the answer must be, and the box it is asked in.
label1 to 200 characters.What the signer reads. Without one, the key is shown.
requiredtrue or false. true when absent.The signature is refused until a required question is answered. An optional one can be passed, and the signature does not wait for it. A signature or initials cannot be false.
hintUp to 200 characters.A line under the question, read with it by a screen reader.
optionsA choice only: 2 to 30 of them, each 1 to 80 characters, none blank.What can be picked. Any other kind carrying options, even an empty list, is refused.
blanktrue or false. false when absent. Written templates only.The answer fills the blank of the same name in the text: {{plate}} for a question keyed plate. See A blank the signer fills in.
sensitivetrue or false. false when absent.The answer is masked wherever the business's staff read it back and in the API. It is not masked on the sealed PDF, because the PDF is the document that was signed. On the page the box turns off the browser's autofill and suggestions, and a box on a PDF page does not show the answer back.
supplied_byadult (when absent), per_child, everyone, staff or business.Who answers it: below.
show_ifAn object: field, the key of a question earlier in the list, and one rule. Or several such rules, joined by all or any.Asked only when those earlier answers match: below.
flag_whenAn object with one rule, about this question's own answer.Marks the answer for a person to read: below.
blockA name shaped like a key.Questions with the same block are asked together, as one thing.
block_labelUp to 200 characters.The block's heading. Given once, on any question in the block. Every block needs one; two different ones are refused, and so is one on a question with no block.
block_introUp to 2,000 characters.A sentence or two under the heading, given once on the same terms.
block_steptrue or false.true on any question of a block gives the whole block a page of its own. Refused on a question with no block.
place{page, x, y, w, h}: a page counted from one and no higher than the file has, and four fractions of the page from its top left, for a box with a width and a height that fits on it.Where on an uploaded PDF's page the question is answered (Signing a PDF).

show_if, flag_when, block, block_label, block_intro, block_step and place may also be null, which is the same as leaving them out.

Conditions and flags

show_if and flag_when both take these four rules, exactly one of them per object:

RuleMatches
{"ticked": true}A ticked box. A checkbox only.
{"unticked": true}A box not ticked, including one never answered. A checkbox only.
{"answered": true}Any answer that is not empty.
{"equals": ["Yes", "Not sure"]}One of these answers, ignoring case and the spaces around it. Not for a photo.

For show_if the rule is about the question it names, so ticked needs that question to be a checkbox: {"field": "injured", "ticked": true}. The named question has to be above this one, because a form is read top to bottom; one below it, or the question itself, is refused. A hidden question is not required, and the signature does not wait for it. Change the earlier answer so that a question is hidden again and its answer is cleared, along with any that depended on it, so nothing is kept in answer to a question the signed document never asked.

A show_if rule can also say more than the four:

RuleMatches
{"not_equals": ["No"]}An answer, and none of these. An empty answer does not match, so the question waits for the one it depends on. Not for a checkbox, photo, signature or initials.
{"contains": "allergy"}An answer with these words in it, ignoring case. A text, paragraph, email, phone or address.
{"not_contains": "nuts"}An answer without these words in it, ignoring case. Like not_equals, an empty answer does not match. The same kinds as contains.
{"gt": 10000}, {"lt": 500}A number above or below this one. A number only.
{"age_under": 18}, {"age_at_least": 18}Whole years from the date in the answer to today, under or at least this many. A birthdate, date or datetime; from 1 to 120.

Several rules, up to ten, are joined: {"match": "all", "rules": [...]} asks the question when every rule is met, and "match": "any" when one is enough. Each rule names its own question:

"show_if": {
  "match": "any",
  "rules": [
    { "field": "birthday", "age_under": 18 },
    { "field": "with_school", "ticked": true }
  ]
}

A part of a condition can be a group of two or more rules with its own match, which is how (A and B) or C is written. A group holds rules, not further groups, and a condition has at most ten rules in all, counting those in its groups. And "action": "hide" turns a condition round: the question is hidden when the condition holds, and asked otherwise.

"show_if": {
  "action": "hide",
  "match": "any",
  "rules": [
    { "match": "all", "rules": [
      { "field": "member", "unticked": true },
      { "field": "birthday", "age_under": 18 } ] },
    { "field": "injured", "answered": true }
  ]
}

In the dashboard's builder, the condition says Show or Hide, and + Add a group adds one. In a form's lines the same are hide if in place of show if on every part, a group as show if all: member is unticked; birthday is under age: 18 (its rules between semicolons), and show if notes does not contain: nuts.

A rule about a question that was itself not asked is not met, whatever it says: its answer was cleared when it was hidden.

A paragraph that depends on an answer

A paragraph of the document can be there only when an answer says so: the treatment consent's extra clause for somebody who is pregnant, the photo release left out for somebody who said no. Put a line of its own above it and another below it, in the words a question's show if takes:

[[show if pregnant is: Yes]]
My doctor has cleared me for this treatment while pregnant.
[[end]]

[[show if birthday is under age: 18 | show if with_school is ticked | show if any]]
A parent or guardian has read this with me.
[[end]]

[[hide if photos is ticked]]
We will not take or use any photographs of you.
[[end]]

[[hide if ...]] leaves a paragraph out when its condition holds, and a group is written as on a question's line: [[show if any: birthday is under age: 18; with_school is ticked | show if notes does not contain: cleared]].

In the dashboard, Show a paragraph only if… under the text writes the two lines for you. A paragraph can name a question the signer answers, or a box you fill in on an uploaded PDF; not one asked once per child, not a question your staff fill in that has no place on the page, since nobody answers it on the document, and not a box the signature fills in, such as the signing date, which has no answer until after the paragraphs are shown. The markers are refused when the text is saved if they cannot be read, and a question they name that the template does not have, or cannot name, is refused when it is published.

The first signer's answers decide it: the first person in signing order who is not only copied in. One document has one text, so it is decided once. That signer is asked every question before the document, optional ones too, and reads it with the paragraphs their answers called for; when they sign, which paragraphs applied is frozen, and anybody else on the document waits until then, so nobody signs a text that could still change. The signed copy has only the paragraphs that applied, and its certificate says how many and whose answers decided them. The fingerprint is of the text as it was sent, every paragraph and its condition included, and the answers that decided it are in the evidence. The printable blank copy and the previews show every paragraph, each with the sentence saying when it applies.

Over the API, GET /v1/sign/{token} says so under paragraphs, and a page that draws the document itself passes back which paragraphs it drew when it signs. While they are undecided, the first signer's signature is refused without that, so an app or an integration that predates it cannot freeze a paragraph its signer never saw; see the API reference.

flag_when marks an answer somebody at the business should read: the shooter who left I am not under the influence of alcohol unticked. The rule is kept with the document when it is sent, and the mark is worked out from the rule and the answer, so it cannot disagree with either. A flagged answer is listed under Needs review in the dashboard once the document is finished, and comes back flagged: true in the API. The signer is never told: a page that said so would teach the next signer which answer avoids the question. The flag is not on the certificate either, which is evidence of what was said, not of what the business made of it.

Who answers

Every question is put to every person on the document who signs, approvers and witnesses included, and to nobody copied in, except the person signing for the business. A document with two signers asks each of them the whole list, and each answers for themselves. supplied_by changes that:

  • adult: the person signing. The ordinary case.
  • per_child: asked once for each child the document covers, named, with the child's name after the label. A document knows its children when it was started from a packet (Reservations and packets) or by a booking's party (Signers, roles and order); one sent with POST /v1/documents/{id}/send covers nobody, so it asks none. It cannot be placed on a page: one box cannot hold an answer per child.
  • everyone: asked of each person for themselves and, for each child someone signs for, once more about that child, of whoever signs for them: "Can swim 50 metres" of every adult, and of Kit's parent for Kit. With no child on the document it is exactly adult. Like per_child, it is not placed on a page and no paragraph depends on it. A parent named to sign only for a child, who is not in the party themselves, is asked the child's questions and nothing about themselves. In the dashboard: Each person, and a parent or guardian for each child, which a new question about the person (swimming, allergies, a size, a date of birth) is set to until you choose.
  • staff: never asked of a signer. Placed on an uploaded PDF, it is filled in by whoever sends the document, under values on POST /v1/documents or fields or values on a workflow start, and a required one that is missing refuses the send. The signer sees it filled in and cannot change it. Such a box cannot be a signature, initials, a photo or the signing date, cannot be sensitive, and cannot have a show_if, because it is filled in before anybody has answered anything. GET /v1/workflows lists a workflow's as sender_fields.
  • business: answered by the person who signs for the business, and nobody else. On an uploaded PDF, the business's own signature line, its date and its name. That person is the signer sent with for_business: true, or a workflow's fixed role with business: true. Everybody else's questions are not asked of them, so each box on the page has one person's answer. A business question's show_if can name only another business question, and nobody else's can name one. Sent with nobody for the business, its boxes stay empty.

A blank the signer fills in

A written template's blanks are the sender's to fill: on POST /v1/documents a blank with no value is refused with no value for …. A blank only the signer knows (a licence plate, an emergency contact) is filled by them instead: give the template a question with the blank's name and "blank": true, or on the dashboard tick Write the answer into the document on the question.

  • Sending. The sender may still fill it. Then it is filled and nobody is asked. Left out of values, the text keeps {{plate}} for the signer.
  • Who answers. The first signer: the first person in sign_order, never somebody copied in, never the person signing for the business. Nobody else is asked it.
  • Signing. The signing page shows the question's label in brackets where the blank is, then the signer's answer once they give it. Their signature writes the answers into the text, in words as a form's answers are, before the text is dated and fingerprinted. Every signature, the certificate and GET /v1/documents/{id}/verify are of the finished text.
  • Order. Nobody signs a text with a blank still open. The first signer is told to answer it, and anybody else that the document is waiting for the first signer: in parallel or in order, the first signer signs first.
  • Evidence. The first signature's event keeps which blanks it filled and the fingerprint of the text before (detail.filled), and the certificate names them.

Because everyone who reads the document reads the answer, a blank question must be words that can sit in a sentence (not a signature, initials, a tick box, a photo or the last four of a Social Security number), required, not sensitive, without a show_if, and for no particular recipient. A template is refused if a blank question names a blank the text does not have, or the signing date.

What the signer sees

The signing page runs no script, so every box is one the browser draws and checks itself. None of that is the check; the engine is.

  • The questions come after the signer agrees to sign electronically and before the signature, under the document, in the order of the list. A workflow can ask them before the document instead (document_after_questions on GET /v1/sign/{token}).
  • A block's questions are drawn under its heading and introduction. Several in a block are one form with one Continue, unless the block holds a photo or a per-child question; then each is asked on its own. The page groups a block's questions where they sit next to each other in the list, so keep them together.
  • A block with block_step is a page of its own. The page always opens on the first step with anything left to answer, so a signer who comes back lands where they stopped, and a step whose questions are all hidden is skipped.
  • An optional question says (optional) beside its label and can be passed: Save or skip, Leave it unticked, Carry on without a photo.
  • A question placed on an uploaded PDF is a box on the page, filled in on the same step as the signature. A signature box and a signing-date box are filled by signing, and a box the sender filled is shown and not asked (Signing a PDF).

Your own signing screen reads the questions from GET /v1/sign/{token}, each with visible, the engine's own answer to its condition: draw only the visible ones. A screen that decides conditions itself as the answers come in has rules_at_start, the engine's answer to each rule on its own, for a rule about a question it does not ask, and condition_zone, the zone whose day an age is counted on. Answer with POST /v1/sign/{token}/fields, taking key, value (up to 20,000 characters) and, for a per-child question, about; a photo goes to POST /v1/sign/{token}/fields/{key}/photo. A refused answer is 422 with the engine's sentence, such as Date of birth needs the year in full, like 1984. While a required, visible question is unanswered, POST /v1/sign/{token} is refused with 422 naming each one: still needed before signing: ….

An example

A kayak rental waiver: a safety statement that is optional but flagged when left unticked, a birthday, a section on its own page with a follow-up that is only asked after a yes, a medical note that is masked and always flagged when given, an optional photo, and initials.

"fields": [
  { "key": "swimmer", "label": "I can swim 50 metres unaided", "kind": "checkbox",
    "required": false, "flag_when": { "unticked": true } },
  { "key": "date_of_birth", "label": "Date of birth", "kind": "birthdate" },
  { "key": "paddled_before", "label": "Have you paddled before?", "kind": "choice",
    "options": ["Yes", "No"],
    "block": "experience", "block_label": "Your paddling",
    "block_intro": "So we can give you the right boat.", "block_step": true },
  { "key": "trips", "label": "Roughly how many trips?", "kind": "number",
    "block": "experience", "show_if": { "field": "paddled_before", "equals": ["Yes"] } },
  { "key": "medical", "label": "Anything medical we should know about?", "kind": "paragraph",
    "required": false, "sensitive": true, "hint": "Only what affects you on the water.",
    "flag_when": { "answered": true } },
  { "key": "hull_photo", "label": "Photo of the boat before you launch", "kind": "photo",
    "required": false },
  { "key": "rules_initials", "label": "Initial to confirm you read the safety rules",
    "kind": "initials" }
]

How answers come back

GET /v1/documents/{id} carries answers: every question on the document, in the order the form asks it, for each signer in turn.

"answers": [
  { "signer_id": "5f0a…", "key": "swimmer", "label": "I can swim 50 metres unaided",
    "kind": "checkbox", "block": null, "about": null, "required": false,
    "sensitive": false, "asked": true, "value": "",
    "supplied_at": "2026-10-03T14:20:51Z", "flagged": true },
  { "signer_id": "5f0a…", "key": "medical", "label": "Anything medical we should know about?",
    "kind": "paragraph", "block": null, "about": null, "required": false,
    "sensitive": true, "asked": true, "value": "•••••••thma",
    "supplied_at": "2026-10-03T14:21:30Z", "flagged": true }
],
"flagged": 2
  • asked is whether the question was on that signer's page. A question its condition hid is asked: false, has no value, and is never flagged.
  • A sensitive value is masked: shorter than eight characters, it is all dots; longer, dots and its last four.
  • A photo's value is sha256:<hex>, the fingerprint of the photograph kept with the document.
  • about names the child a per-child answer is for, and block the block it was asked in.
  • flagged on the document counts the flagged answers.
  • A redacted document keeps its questions and gives up every value, and each child's name: about reads Redacted child 1, Redacted child 2 and so on, the same child the same number on every question.

The AI assistants' signsealer_get_document leaves the answers out, and says how many there are, unless asked with include_answers: true (AI assistants).

Webhooks do not carry answers, on purpose: a webhook goes to whatever address was typed into a form, and some answers are about somebody's health. Two events come close. signing.photo_supplied names the question and the child it was about and carries the photo's SHA-256, never the image; and when the sender filled boxes on an uploaded PDF, signing.created lists their keys under detail.filled_by_sender, never the values (Webhooks). Wait for signing.completed, then read the document.

What is frozen, and when

The list is part of the version. While the version is a draft it changes freely; once it is published, PATCH answers 422, and the way to ask something different is the next version, with fields on POST /v1/templates/{id}/revise. What you were asked to initial is part of what you agreed to.

Then each document keeps its own copy. When a document is sent, every question is written onto it for each signer: its label, kind, options, hint, whether it is required or sensitive, its block's heading and introduction, its condition, its flag rule and its place on the page. A later version changes nothing about a document already sent, so a flag rule changed next season does not decide what last season's answers meant.

What happens to what was already sent

  • Documents already sent keep their version. The text is stored on the document when it is prepared, and a new version changes nothing about a document on its way to a signer.
  • New documents use the newest published version. A draft is never sent.
  • A tablet that ran the form with no network names the version it showed (expected_template_version) when it catches up. If a newer one was published meanwhile, the start answers 409 template_changed and nothing is sealed against words the person did not read.

Settings that outlive a version

Some settings belong to the code, not one version, and carry over to every version after: PATCH /v1/templates/{id}/settings with any of

SettingMeans
valid_monthsHow long a signature stays good, in months; null for as long as the document is kept. GET /v1/standing answers against it.
remind_daysDays before the end each signer is emailed a link to sign again. Needs valid_months.
resigntrue: everybody who signed an older version must sign the newest published one; their standing reads resign. false lifts it.
one_timeMade to send once rather than reused.
show_addressThe business's address printed under it.
photos_in_copyThe photographs a signer takes as answers are printed after the certificate in the signed PDF, each with its SHA-256, under the same seal. On unless turned off; the certificate keeps every hash either way.

What an NDA grants

An NDA's template can carry grants: a JSON object, set when the template is created (POST /v1/templates), recording what signing it gives the signer. SignSealer keeps it and does not act on it: who may see your material is for the system that holds the material to decide. A template of any other kind that carries one is refused with 422 (“only an NDA carries a grant”).

Grants belong to a version, as its text does. PATCH does not change them; a revision carries the newest version's forward, and a restore brings back the restored version's. They come back on the template (GET /v1/templates/{id}); on each document made from it, as grants on GET /v1/documents/{id}, where each signer whose role is signer is also marked grants: true; on the signing link (GET /v1/sign/{token}); and in the certificate, as payload.document.grants from GET /v1/documents/{id}/certificate, sealed into its hash when the document completes. The sealed PDF does not print them, and the signing.completed webhook does not carry them: read the document or its certificate when it arrives.

A template that is a PDF

Uploading the file is a dashboard step, where the boxes are placed on its pages; there is no API route for the upload yet (Signing a PDF). Everything on this page works on it afterwards, except changing its text: a PDF's text is the file. Revise it to rename it or change its questions; to change what it says, start a new template from the new file.

This page as Markdown Ask an AI about it: ChatGPT · Claude · Perplexity · Google AI · Grok

Ready to build? An API key takes a minute in the portal, and the free plan covers the first 10 agreements a month.

Get an API key