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