Get started
Quickstart
Send a PDF for signature end to end: create a draft, place a signature field, send it, get notified when it completes and download the sealed PDF.
Before you start. API keys require a plan with API access (Business or Enterprise) and an organization admin to create them. Requests from organizations whose plan does not include API access fail with 402.
1. Create an API key#
In the app, open API keys and create a key with the scopes documents:read and documents:write. Add webhooks:read and webhooks:write if you also want to register webhooks from code. The full key (sk_live_...) is shown once; store it in a secret manager.
export SIGNYT_API_KEY="sk_live_..."The key acts on behalf of the admin who created it, inside that admin's organization. See Authentication and scopes.
2. Create a draft document#
POST /documents is a multipart/form-data request with two parts: pdf (the file, application/pdf) and payload (a JSON string). The document is created as a DRAFT; nothing is emailed yet.
curl -sS -X POST "https://api.sign.yt/v1/documents" \
-H "Authorization: Bearer $SIGNYT_API_KEY" \
-F 'payload={"title":"Mutual NDA","verification_type":"EMAIL_OTP","recipients":[{"email":"[email protected]","name":"Anna Nowak","role":"SIGNER"}]};type=application/json' \
-F "[email protected];type=application/pdf" | tee doc.json
DOC_ID=$(jq -r .id doc.json)
RECIPIENT_ID=$(jq -r '.recipients[0].id' doc.json)import os
from signyt import Signyt
client = Signyt(os.environ["SIGNYT_API_KEY"])
doc = client.documents.create(
"nda.pdf",
title="Mutual NDA",
recipients=[{"email": "[email protected]", "name": "Anna Nowak", "role": "SIGNER"}],
verification_type="EMAIL_OTP",
)
recipient_id = doc.recipients[0].id
print(doc.id, doc.status) # ... DRAFTimport { readFile } from "node:fs/promises";
import { Signyt } from "@signyt/sdk";
const client = new Signyt(process.env.SIGNYT_API_KEY!);
const doc = await client.documents.create({
pdf: await readFile("nda.pdf"),
filename: "nda.pdf",
title: "Mutual NDA",
recipients: [{ email: "[email protected]", name: "Anna Nowak", role: "SIGNER" }],
verificationType: "EMAIL_OTP",
});
const recipientId = doc.recipients[0].id;
console.log(doc.id, doc.status); // ... DRAFTPayload fields:
| Field | Type | Notes |
|---|---|---|
title | string, required | 1-500 characters. |
recipients | array, required | 1-50 items: email, name (1-200), role (SIGNER default, APPROVER, VIEWER, CC, PREPARER), optional phone (E.164, required for SMS verification), signing_method, claimed_role. |
signing_order | string | PARALLEL (default) or SEQUENTIAL (in array order). |
verification_type | string | NONE (default), EMAIL_OTP, SMS_OTP, BOTH_OTP. SMS requires a plan with SMS. |
expires_at | RFC 3339 | Future, at most 365 days ahead. Omitted: 90 days. |
allow_delegation | boolean | Let recipients hand off signing to someone else. |
metadata | object | Your own data, up to 4 KB serialized and 3 levels deep. |
team_id, folder_id | UUID | Optional organization structure. |
The response is the full document (201 Created), including the generated recipient IDs you need for the next step.
3. Place a signature field#
Every SIGNER needs at least one SIGNATURE field before the document can be sent. PUT /documents/{id}/fields replaces all fields of a draft. Positions and sizes are percentages of the page (0-100), measured from the top-left corner; page starts at 1.
curl -sS -X PUT "https://api.sign.yt/v1/documents/$DOC_ID/fields" \
-H "Authorization: Bearer $SIGNYT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"fields": [
{"type": "SIGNATURE", "page": 1, "position_x": 12, "position_y": 78,
"width": 28, "height": 7, "recipient_id": "'"$RECIPIENT_ID"'"},
{"type": "DATE", "page": 1, "position_x": 55, "position_y": 80,
"width": 18, "height": 4, "recipient_id": "'"$RECIPIENT_ID"'"}
]
}'client.documents.set_fields(doc.id, [
{"type": "SIGNATURE", "page": 1, "position_x": 12, "position_y": 78,
"width": 28, "height": 7, "recipient_id": recipient_id},
{"type": "DATE", "page": 1, "position_x": 55, "position_y": 80,
"width": 18, "height": 4, "recipient_id": recipient_id},
])await client.documents.setFields(doc.id, [
{ type: "SIGNATURE", page: 1, position_x: 12, position_y: 78, width: 28, height: 7, recipient_id: recipientId },
{ type: "DATE", page: 1, position_x: 55, position_y: 80, width: 18, height: 4, recipient_id: recipientId },
]);Field types: SIGNATURE, INITIALS, TEXT, DATE, NUMBER, EMAIL, CHECKBOX, DROPDOWN (with options), COMPANY. Up to 200 fields per document. Fields can only be changed while the document is a DRAFT (otherwise 409 document_not_draft).
4. Send it#
POST /documents/{id}/send moves the document to PENDING and emails the recipients (all at once for PARALLEL, the first one for SEQUENTIAL). The JSON body is optional: email_subject (up to 200 characters, single line) and email_message (up to 2000) override the invitation text. Send an Idempotency-Key so a retried request never sends twice.
curl -sS -X POST "https://api.sign.yt/v1/documents/$DOC_ID/send" \
-H "Authorization: Bearer $SIGNYT_API_KEY" \
-H "Idempotency-Key: send-$DOC_ID" \
-H "Content-Type: application/json" \
-d '{"email_subject": "Please sign the NDA"}'sent = client.documents.send(doc.id)
print(sent.status) # PENDINGconst sent = await client.documents.send(doc.id);
console.log(sent.status); // PENDINGSending fails with 422 and code document_no_signers or signer_missing_signature_field when the draft is incomplete, and with 402 when a plan limit is reached.
5. Get notified#
Register a webhook once per integration (organization admin key with webhooks:write; plans with webhooks). The response contains the signing secret; it is returned only on creation.
curl -sS -X POST "https://api.sign.yt/v1/webhooks" \
-H "Authorization: Bearer $SIGNYT_API_KEY" \
-H "Content-Type: application/json" \
-d '{"url": "https://example.com/hooks/signyt", "events": ["document.completed", "document.rejected", "recipient.signed"]}'hook = client.webhooks.create(
"https://example.com/hooks/signyt",
["document.completed", "document.rejected", "recipient.signed"],
)
print(hook.secret) # store it: shown only onceconst hook = await client.webhooks.create({
url: "https://example.com/hooks/signyt",
events: ["document.completed", "document.rejected", "recipient.signed"],
});
console.log(hook.secret); // store it: shown only onceWhen everyone has signed you receive:
{
"event_id": "5e0f8a1b-2c3d-4e5f-8a9b-0c1d2e3f4a5b",
"event": "document.completed",
"timestamp": "2026-10-19T09:12:44.123456+00:00",
"data": {
"id": "3f1c2a9e-5b7d-4c11-9a3e-0d4b8e2f6a71",
"title": "Mutual NDA",
"status": "COMPLETED",
"organization_id": "a7d5e0c2-1f43-4b9a-8c6e-2e9f0b3d4c58"
}
}Always verify the X-Signyt-Signature header before trusting the body: see Verify signatures. No public endpoint? Poll GET /documents/{id} until status is COMPLETED (and keep polling slow; webhooks are cheaper for everyone).
6. Download the signed PDF#
GET /documents/{id}/signed.pdf returns the sealed PDF once the document is COMPLETED (has_signed_pdf is true). Before that it returns the original upload, so check the status first.
curl -sS "https://api.sign.yt/v1/documents/$DOC_ID" \
-H "Authorization: Bearer $SIGNYT_API_KEY" | jq -r '.status, .has_signed_pdf'
curl -sS -o nda-signed.pdf "https://api.sign.yt/v1/documents/$DOC_ID/signed.pdf" \
-H "Authorization: Bearer $SIGNYT_API_KEY"current = client.documents.get(doc.id)
if current.status == "COMPLETED" and current.has_signed_pdf:
with open("nda-signed.pdf", "wb") as f:
f.write(client.documents.download_signed_pdf(doc.id))import { writeFile } from "node:fs/promises";
const current = await client.documents.get(doc.id);
if (current.status === "COMPLETED" && current.has_signed_pdf) {
await writeFile("nda-signed.pdf", await client.documents.downloadSignedPdf(doc.id));
}The signed PDF carries a certificate-of-completion page, one PAdES signature per signer and the final sign.yt platform seal. Anyone can check it at verify.sign.yt; see Verification and evidence.
Next steps#
- Documents and recipients: statuses, roles, sequential signing, reminders and cancellation.
- Webhooks: all events, retries and redelivery.
- Errors and idempotency: build retries safely.
- Templates: skip field placement for documents you send often.