Getting started

Create a key, send your first document in test mode, and watch the events arrive. About ten minutes end to end.

Updated

On this page

This walks you from nothing to a signed document. Everything here runs against a sandbox key, so emails are marked as test documents, documents are watermarked, and nothing is billed. Use email addresses you control.

1. Get a key

Create an account and choose Light or above for API access, then open Settings → API keys and create a key. Sandbox keys are prefixed sk_test_, live keys sk_live_. Use the sandbox one for everything below.

The key identifies both your workspace and the mode. There is no separate “test mode” flag to remember and no environment variable to get wrong — if the key starts with sk_test_, the request is a test request.

2. Send a document

Signclad fetches your file from a URL, so a presigned S3 link, a public URL, or anything your server can hand out for a few minutes all work. You can also upload the bytes with POST /files first and pass the returned file_id. Documents and templates take PDF, .doc and .docx — up to 20 files per document, 25 MB each.

curl -X POST https://api.signclad.com/api/v1/documents \
  -H "X-Api-Key: $SIGNCLAD_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Loan agreement",
    "subject": "Please sign your loan agreement",
    "message": "<p>Two signatures, about a minute.</p>",
    "reminders": true,
    "expires_in": 30,
    "metadata": { "loan_id": "48213" },
    "files": [
      { "name": "loan-agreement.pdf",
        "file_url": "https://files.example.com/loan-agreement.pdf?X-Amz-Expires=7200" }
    ],
    "recipients": [
      { "id": "signer-1", "name": "Jane Doe", "email": "[email protected]" }
    ],
    "fields": [[
      { "api_id": "borrower_signature", "type": "signature", "recipient_id": "signer-1",
        "page": 1, "x": 375, "y": 206, "width": 180, "height": 44, "required": true },
      { "api_id": "borrower_date", "type": "date", "recipient_id": "signer-1",
        "page": 1, "x": 375, "y": 262, "width": 120, "height": 24,
        "lock_sign_date": true, "date_format": "MM/DD/YYYY" }
    ]]
  }'

The same call from Node (18+, no dependencies) and from Python with requests:

import { randomUUID } from 'node:crypto';

const body = {
  name: 'Loan agreement',
  subject: 'Please sign your loan agreement',
  files: [
    { name: 'loan-agreement.pdf', file_url: 'https://files.example.com/loan-agreement.pdf' },
  ],
  recipients: [{ id: 'signer-1', name: 'Jane Doe', email: '[email protected]' }],
  fields: [[
    { api_id: 'borrower_signature', type: 'signature', recipient_id: 'signer-1',
      page: 1, x: 375, y: 206, width: 180, height: 44, required: true },
  ]],
};

const response = await fetch('https://api.signclad.com/api/v1/documents', {
  method: 'POST',
  headers: {
    'X-Api-Key': process.env.SIGNCLAD_API_KEY,
    'Idempotency-Key': randomUUID(),
    'Content-Type': 'application/json',
  },
  body: JSON.stringify(body),
});

if (!response.ok) throw new Error(`${response.status}: ${await response.text()}`);
const document = await response.json();
console.log(document.id, document.status); // "Created" — the transient value
import os
import uuid

import requests

body = {
    "name": "Loan agreement",
    "subject": "Please sign your loan agreement",
    "files": [
        {"name": "loan-agreement.pdf", "file_url": "https://files.example.com/loan-agreement.pdf"}
    ],
    "recipients": [{"id": "signer-1", "name": "Jane Doe", "email": "[email protected]"}],
    "fields": [[
        {"api_id": "borrower_signature", "type": "signature", "recipient_id": "signer-1",
         "page": 1, "x": 375, "y": 206, "width": 180, "height": 44, "required": True}
    ]],
}

response = requests.post(
    "https://api.signclad.com/api/v1/documents",
    headers={
        "X-Api-Key": os.environ["SIGNCLAD_API_KEY"],
        "Idempotency-Key": str(uuid.uuid4()),
    },
    json=body,
    timeout=30,
)
response.raise_for_status()
document = response.json()
print(document["id"], document["status"])  # "Created" — the transient value

Two things about the response will look wrong if you are not expecting them, and both are deliberate:

  • status is Created, whatever you passed for draft. It is a transient value. The settled status — Draft or Sent — appears on the next GET.
  • files[].pages_number is 0 and files[].status is not yet ready — it reads uploaded, then processing. Ingest has not finished. The real page count arrives on the next GET.

Signing URLs in the response are valid immediately, so you can hand a recipient their link before ingest finishes.

3. Read it back

curl https://api.signclad.com/api/v1/documents/8f14e45f-ea8f-4b2c-9f0e-1b2c3d4e5f60 \
  -H "X-Api-Key: $SIGNCLAD_API_KEY"

Once the send job has run, status is Sent, pages_number is real, files[].status is ready, and each recipient carries a status of its own. Read it immediately after the create and you may still catch Created or Sending. Fields come back grouped by type rather than in the order you sent them, and width and height are decimal strings while x, y, and page stay numbers. That is the compatibility contract, quirks included.

4. Sign it

Test mode emails the recipients with a clear test notice. Open the link in the email or the recipient’s embedded_signing_url and sign the watermarked test document. If you lose the URL, mint a new one rather than recreating the document:

curl -X POST \
  https://api.signclad.com/api/v1/documents/{id}/recipients/signer-1/signing_url \
  -H "X-Api-Key: $SIGNCLAD_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "expires_in_minutes": 60 }'

5. Watch the events

Create a webhook endpoint pointed at your receiver — or at the hosted inbox in Settings → Webhooks, which records requests so you can look at real payloads before writing any code.

curl -X POST https://api.signclad.com/api/v1/webhook_endpoints \
  -H "X-Api-Key: $SIGNCLAD_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://app.example.com/webhooks/signclad",
    "description": "Local development",
    "event_types": ["document_sent", "document_viewed", "document_signed", "document_completed"],
    "include_test_mode": true,
    "verify": true
  }'

verify: true sends a ping before the endpoint goes live and shows you the result: the endpoint starts paused and only activates if your receiver answers 2xx. Without it the endpoint is active immediately and nothing is pinged. Note include_test_mode too — without it, test-mode documents produce no deliveries.

6. Download the evidence

When every signer has finished:

curl -L https://api.signclad.com/api/v1/documents/{id}/completed_pdf \
  -H "X-Api-Key: $SIGNCLAD_API_KEY" -o completed.pdf

Before that, this endpoint returns 404 — not 403, and not an empty 200. It is the one place where a 404 does not mean “no such document”.

The completed PDF carries the audit page and a digital seal using a self-issued certificate. audit_trail_url on the document gives you the same evidence as signed JSON.

Common questions

Which languages does the Signclad eSignature API support?

Any language that can make an HTTPS request. Signclad is a plain REST API — JSON in, JSON out, one X-Api-Key header — so curl, Node’s fetch, Python’s requests, PHP’s Guzzle, Ruby’s Net::HTTP, and Go’s net/http all work without a library. The samples above are Node and Python; the API reference gives the request shape for every operation, and the OpenAPI document generates a typed client for most languages. There is no published Signclad SDK package yet.

Why is status still Created after I sent the document?

Because the create and send responses answer with a transient status, deliberately: POST /documents answers Created and POST /documents/{id}/send answers Draft, whatever you passed. The settled status — Draft, Sent, Completed — appears on the next GET /documents/{id}. Never branch on the status in a create response; read the document back, or wait for the webhook.

Does test mode send real email?

Yes. A sandbox key (sk_test_) delivers real messages to the recipients, marked as a test and watermarked as not legally binding, and never bills. That means a send to an address you do not control really bounces. Use addresses you own, or set embedded_signing: true — which already defaults every recipient’s send_email to false — to get signing links without any mail at all. See Sandbox and test mode.