Documentation menu

Quickstart

Four calls: create a template, publish it, prepare a document, send it.

1. Create a template

Placeholders are {{like_this}}, and every one of them has to be declared. Publishing checks that, so a contract cannot ship with an unfilled placeholder in it.

curl https://api.signsealer.com/v1/templates \
  -H "Authorization: Bearer $SIGNSEALER_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "code": "rental-agreement",
    "name": "Short-term rental agreement",
    "kind": "contract",
    "variables": ["guest_name", "property", "arrival"],
    "body": "This agreement is between Harbour Rentals and {{guest_name}} for {{property}}, arriving {{arrival}}."
  }'

2. Publish it

Publishing freezes the text. From then on the template is immutable and POST /v1/templates with the same code is refused — revise it instead, which makes version 2 and leaves version 1 exactly as the people who signed it saw it.

curl -X POST https://api.signsealer.com/v1/templates/publish \
  -H "Authorization: Bearer $SIGNSEALER_KEY" -d '{"code":"rental-agreement"}'

3. Prepare a document

The rendered text is stored on the document, not a reference to the template. That is what retention means: the template can change, and the whole point of versioning it is that it might.

curl https://api.signsealer.com/v1/documents \
  -H "Authorization: Bearer $SIGNSEALER_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "template_code": "rental-agreement",
    "values": { "guest_name": "Dana Reyes", "property": "River Bend Cabin", "arrival": "3 October" }
  }'

4. Send it

You get one link per signer, once. The tokens are never stored — only their hashes — so a database dump is not a set of signing links, and a lost link is replaced with a reminder rather than looked up.

curl https://api.signsealer.com/v1/documents/$DOC/send \
  -H "Authorization: Bearer $SIGNSEALER_KEY" \
  -H "Content-Type: application/json" \
  -d '{"signers":[{"email":"dana@example.com","full_name":"Dana Reyes"}]}'

The same in Node

@signsealer/node has no dependencies and runs anywhere fetch does. It adds the idempotency key itself and retries only the failures worth retrying.

import { SignSealer } from "@signsealer/node";

const signsealer = new SignSealer({ apiKey: process.env.SIGNSEALER_KEY! });

const { document_id } = await signsealer.prepareDocument({
  template_code: "rental-agreement",
  values: { guest_name: "Dana Reyes", property: "River Bend Cabin", arrival: "3 October" },
});

const links = await signsealer.sendForSignature(document_id, [
  { email: "dana@example.com", full_name: "Dana Reyes" },
]);

The same in Python

No client library yet; the API is small enough that requests is the client. Send your own Idempotency-Key on the writes, and retry only 429 and 500.

import os, uuid, requests

API = "https://api.signsealer.com"
headers = {"Authorization": f"Bearer {os.environ['SIGNSEALER_KEY']}"}

def post(path, body):
    r = requests.post(API + path, json=body, timeout=15,
                      headers={**headers, "Idempotency-Key": str(uuid.uuid4())})
    r.raise_for_status()
    return r.json()

doc = post("/v1/documents", {
    "template_code": "rental-agreement",
    "values": {"guest_name": "Dana Reyes", "property": "River Bend Cabin", "arrival": "3 October"},
})
links = post(f"/v1/documents/{doc['document_id']}/send", {
    "signers": [{"email": "dana@example.com", "full_name": "Dana Reyes"}],
})["links"]

Then what? Add a webhook and wait for signing.completed. Polling works and is the wrong shape: a completion is an event, and the event carries everything you need to act without a second call.

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

Get an API key