CyberSygn API. v1.
A REST API for the whole signing lifecycle.
Create a document, let CyberSygn find the signature fields, route it to your signers, and pull back the signed PDF and a tamper-evident audit certificate. One API key, server to server. The same detection and signing engine that runs the app, exposed for your code.
Base URL #
Every endpoint lives under one host. All paths below are relative to it.
https://cybersygn.io/api/v1
Responses are JSON, except the document download and audit endpoints, which return a PDF. Timestamps are ISO 8601 strings. API access needs a paid plan.
Authentication #
Authenticate every request with an API key. Keys are account-bound: each key acts as the CyberSygn account that minted it, so your plan limits, branding, and webhook config all apply automatically. Only the account owner can mint keys, from the dashboard.
Pass the key as a bearer token (preferred) or in the X-API-Key header.
Authorization: Bearer cs_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Or:
X-API-Key: cs_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Live keys are prefixed cs_live_. Test-mode keys are prefixed cs_test_. We never store the raw key, only its hash, so a key is shown to you exactly once at creation. Store it somewhere safe and never commit it. A missing or invalid key returns 401 unauthorized. A key may only read and act on documents its own account created; touching another account's document returns 403 forbidden.
Create a document #
Create a document and send it for signature in one call. Give it a source PDF and a list of signers. CyberSygn detects the signature fields for you when you do not supply them, then emails each signer a magic link and returns one signing_url per signer.
Body
pdf_base64— the source PDF as base64 (adata:prefix is stripped for you). Provide this ortemplate.template— a template slug from your library (see List templates). Provide this orpdf_base64.signers— required array of{ name, email, order }. At least one, up to 20.orderis optional and defaults to array position.fields— optional array of field objects. Omit it and CyberSygn auto-detects every signature, initial, date, and checkbox in the PDF. Pass an explicit array to place them yourself.assignments— optional map offieldId → signerId. Omit it and every field is assigned to the first signer. Pass it for multi-party routing.cc— optional array of email addresses to copy on the completed document.signing_order—"parallel"(default, everyone can sign at once) or"sequential"(one signer at a time, in order).title— optional document title.
curl https://cybersygn.io/api/v1/documents \
-H "Authorization: Bearer cs_live_xxx" \
-H "Content-Type: application/json" \
-d '{
"title": "Mutual NDA",
"pdf_base64": "JVBERi0xLjcK...",
"signers": [
{ "name": "Ada Lovelace", "email": "ada@example.com", "order": 0 },
{ "name": "Alan Turing", "email": "alan@example.com", "order": 1 }
],
"signing_order": "sequential"
}'
const res = await fetch("https://cybersygn.io/api/v1/documents", {
method: "POST",
headers: {
"Authorization": "Bearer cs_live_xxx",
"Content-Type": "application/json",
},
body: JSON.stringify({
title: "Mutual NDA",
pdf_base64: pdfBase64, // or: template: "non-disclosure-agreement"
signers: [
{ name: "Ada Lovelace", email: "ada@example.com", order: 0 },
{ name: "Alan Turing", email: "alan@example.com", order: 1 },
],
signing_order: "sequential",
}),
});
const doc = await res.json();
console.log(doc.id, doc.signers[0].signing_url);
Response 201
{
"id": "doc_abc123",
"status": "sent",
"title": "Mutual NDA",
"signing_order": "sequential",
"field_count": 4,
"detected_fields": true,
"signers": [
{
"id": "s1",
"name": "Ada Lovelace",
"email": "ada@example.com",
"status": "pending",
"signing_url": "https://cybersygn.io/x?t=..."
}
],
"created_at": "2026-06-13T17:04:00.000Z",
"status_url": "https://cybersygn.io/api/v1/documents/doc_abc123",
"webhook_note": "Subscribe to doc.completed via your account webhook to be notified when all signers finish."
}
fields, detected_fields is true in the response. If the PDF has no detectable signature fields you get 422 no_fields_detected — pass an explicit fields array to place them yourself.Get a document #
Fetch a document's current status and per-signer state. This is the simplest way to track progress: poll it until status is completed (see Webhooks for the push-based alternative).
curl https://cybersygn.io/api/v1/documents/doc_abc123 \
-H "Authorization: Bearer cs_live_xxx"
const res = await fetch(
"https://cybersygn.io/api/v1/documents/doc_abc123",
{ headers: { "Authorization": "Bearer cs_live_xxx" } }
);
const doc = await res.json();
if (doc.status === "completed") {
console.log("Ready:", doc.download_url);
}
Response 200
{
"id": "doc_abc123",
"status": "partially_signed",
"title": "Mutual NDA",
"created_at": "2026-06-13T17:04:00.000Z",
"completed_at": null,
"voided_at": null,
"signers": [
{ "id": "s1", "name": "Ada Lovelace", "email": "ada@example.com",
"status": "signed", "signed_at": "2026-06-13T17:10:00.000Z", "order": 0 },
{ "id": "s2", "name": "Alan Turing", "email": "alan@example.com",
"status": "pending", "signed_at": null, "order": 1 }
],
"download_url": null,
"audit_url": null
}
Document status is one of sent, partially_signed, completed, or voided. Signer status is one of pending, signed, or declined. download_url and audit_url are populated only once the document is completed.
Download the signed PDF #
Returns the flattened, signed PDF bytes — the same document a signer receives. Only available once the document is completed; before that you get 409 not_completed.
curl https://cybersygn.io/api/v1/documents/doc_abc123/download \
-H "Authorization: Bearer cs_live_xxx" \
-o signed.pdf
const res = await fetch(
"https://cybersygn.io/api/v1/documents/doc_abc123/download",
{ headers: { "Authorization": "Bearer cs_live_xxx" } }
);
if (res.ok) {
const bytes = new Uint8Array(await res.arrayBuffer());
// write bytes to disk / storage
}
Audit certificate #
Returns the tamper-evident audit certificate for a completed document: who signed, when, from where, and the document fingerprint. Like the download, it is available only once the document is completed and returns 409 not_completed otherwise.
curl https://cybersygn.io/api/v1/documents/doc_abc123/audit \
-H "Authorization: Bearer cs_live_xxx" \
-o audit.pdf
const res = await fetch(
"https://cybersygn.io/api/v1/documents/doc_abc123/audit",
{ headers: { "Authorization": "Bearer cs_live_xxx" } }
);
const auditBytes = new Uint8Array(await res.arrayBuffer());
Void a document #
Cancel an in-flight document so it can no longer be signed. A document that is already completed cannot be voided and returns 409 already_completed. Voiding an already-voided document is idempotent and returns its current state.
curl -X POST https://cybersygn.io/api/v1/documents/doc_abc123/void \
-H "Authorization: Bearer cs_live_xxx"
const res = await fetch(
"https://cybersygn.io/api/v1/documents/doc_abc123/void",
{ method: "POST", headers: { "Authorization": "Bearer cs_live_xxx" } }
);
const doc = await res.json(); // doc.status === "voided"
A subsequent attempt to act on a voided document returns 410 voided.
List templates #
List the templates available to your account. Use a returned slug as the template field when you create a document, instead of uploading a PDF every time.
curl https://cybersygn.io/api/v1/templates \
-H "Authorization: Bearer cs_live_xxx"
const res = await fetch("https://cybersygn.io/api/v1/templates", {
headers: { "Authorization": "Bearer cs_live_xxx" },
});
const { count, templates } = await res.json();
Response 200
{
"count": 2,
"templates": [
{ "slug": "non-disclosure-agreement", "title": "Non-Disclosure Agreement", "group": "Confidentiality & IP" },
{ "slug": "master-services-agreement", "title": "Master Services Agreement", "group": "Business" }
]
}
Detect fields #
Run field detection on a PDF without creating or sending anything. Useful to preview what CyberSygn would place, or to seed your own fields array. Pass pdf_base64 in the body.
curl https://cybersygn.io/api/v1/detect \
-H "Authorization: Bearer cs_live_xxx" \
-H "Content-Type: application/json" \
-d '{ "pdf_base64": "JVBERi0xLjcK..." }'
const res = await fetch("https://cybersygn.io/api/v1/detect", {
method: "POST",
headers: {
"Authorization": "Bearer cs_live_xxx",
"Content-Type": "application/json",
},
body: JSON.stringify({ pdf_base64: pdfBase64 }),
});
const { count, fields } = await res.json();
Response 200
{
"count": 1,
"fields": [
{ "id": "f1", "type": "signature", "page": 1,
"x": 120, "y": 640, "width": 200, "height": 36,
"confidence": 0.94, "label": "Signature" }
]
}
Check the key #
Sanity-check a key. Returns the account it resolves to, the key id, and the mode. Handy as a first call when wiring up an integration.
curl https://cybersygn.io/api/v1/me \
-H "Authorization: Bearer cs_live_xxx"
const res = await fetch("https://cybersygn.io/api/v1/me", {
headers: { "Authorization": "Bearer cs_live_xxx" },
});
const me = await res.json(); // { ok, account, key_id, mode }
Response 200
{ "ok": true, "account": "acct_123", "key_id": "key_abc", "mode": "live" }
Provision keys for your users (partners) #
There are two ways to access the API. Standalone customers who buy CyberSygn directly get a metered key, gated by their plan. A partner that builds CyberSygn into its own product gets one server-side partner master key (capability canProvision) and uses it to mint an individualized, unmetered key for each of its own users. Every tenant key is its own credential: isolated to its own account namespace, attributable, and revocable on its own. Keep the master key on your server and never expose it to end users. Partner master keys are minted from the owner console.
Mint a tenant key
curl -X POST https://cybersygn.io/api/v1/keys \
-H "Authorization: Bearer $CYBERSYGN_PARTNER_KEY" \
-H "content-type: application/json" \
-d '{"tenant_id": "your-customer-123", "label": "acme"}'
Response 201
{
"key": "cs_live_...",
"key_id": "key_...",
"tenant_id": "your-customer-123",
"account": "p-yourname-your-customer-123",
"unmetered": true
}
The tenant key is shown once — store it against your user and use it as that user's bearer token. List your tenant keys with GET /api/v1/keys; revoke one with DELETE /api/v1/keys and body {"key_id":"key_..."}. A tenant key cannot mint further keys.
Webhooks #
Rather than poll, configure a webhook URL on your account and CyberSygn will POST a JSON payload to it as the document moves through its lifecycle. Because keys are account-bound, your existing account webhook config applies to API-created documents automatically.
Events
| Event | Fires when |
|---|---|
| doc.created | A document is created and sent for signature. |
| signer.completed | An individual signer finishes their part. |
| doc.completed | Every signer has finished and the document is final. |
Payload
{
"event": "doc.completed",
"document": {
"id": "doc_abc123",
"status": "completed",
"title": "Mutual NDA",
"completed_at": "2026-06-13T17:20:00.000Z"
}
}
On doc.completed, call GET /:id/download and GET /:id/audit to retrieve the signed PDF and certificate.
status is completed, then download. Webhooks are the efficient option, not a requirement.Embedded signing #
Keep signing inside your own app. Every signer in the create response carries a signing_url — drop it into an iframe and the full CyberSygn signing flow renders in place. No redirect to a third-party site.
<iframe
src="https://cybersygn.io/x?t=..." <!-- the signer's signing_url -->
style="width:100%;height:90vh;border:0"
allow="clipboard-read; clipboard-write; camera"
title="Sign with CyberSygn">
</iframe>
The signing surface posts a cybersygn:complete message to the parent window when signing finishes, so you can update your own UI:
window.addEventListener("message", (e) => {
if (e.origin !== "https://cybersygn.io") return;
if (e.data && e.data.type === "cybersygn:complete") {
// close the modal, refresh status, etc.
}
});
If you would rather not build the modal yourself, the hosted embed widget does it for you. Drop one script tag and any element with a data-cybersygn-sign attribute becomes a launcher that opens signing in a managed iframe modal, complete with backdrop, close handling, and the same completion message.
<script src="https://cybersygn.io/embed.js"></script>
<button data-cybersygn-sign="https://your-site.com/contract.pdf"
data-cybersygn-source="your-app">
Sign this document
</button>
See the embed widget docs for theming, programmatic CyberSygn.open() control, and prefill options.
Error codes #
Errors come back as JSON with a stable error code and a human message. Switch on the code, show the message.
{ "error": "not_completed", "message": "The document is not fully signed yet." }
| HTTP | Code | Meaning |
|---|---|---|
| 401 | unauthorized | Missing or invalid API key. |
| 403 | forbidden | The document belongs to a different account. |
| 404 | not_found | No document, template, or route matches. |
| 409 | not_completed | Download or audit requested before the document is fully signed. |
| 409 | already_completed | A completed document cannot be voided. |
| 410 | voided | The document was voided and can no longer be acted on. |
| 413 | payload_too_large | The PDF exceeds the 25 MB limit. |
| 422 | detection_failed | The PDF could not be read for field detection. |
| 422 | no_fields_detected | No signature fields were found. Pass an explicit fields array. |
