Signadoc Sign in Start free

Developer guide

Signadoc turns a PDF into a reusable template with named fields, then produces signing links that stamp in the data you already know. There are two ways to integrate: embed Signadoc’s pages into your own site with a few lines of HTML, or drive everything from your server with the REST API. Most integrations use both: the embed for setup, the API for sending.

1

Embed: put Signadoc in your web page

An iframe (or our tiny helper script) shows the field designer or the signing page inside your site. Your users never see Signadoc or log into it.

  • No backend code needed to start
  • Your users upload PDFs and place fields themselves
  • You get JavaScript events when things happen
2

REST API: drive Signadoc from your server

Plain HTTPS + JSON with your secret key. Create signing links from a CRM record, send a contract to 200 people, or fill a PDF without any signature at all.

  • Pre-fill fields with data from your system
  • Email signers or embed the link in your own flow
  • Fetch status, audit trail and the signed PDF

Which should I use?

You want to…UseWhy
Let customers upload their own contract and mark where to sign, inside your portalEmbed (designer)The designer is a full UI. Embedding it is one iframe; rebuilding it on the API would be a lot of work.
Send your standard agreement to a new customer when they are created in your CRMREST APIOne POST with the customer’s name, address and email. Signadoc stamps the values and emails the link.
Have the customer sign without leaving your checkout or onboarding pageREST API + embed (signing page)Create the envelope server-side, then show its embed_sign_url in an iframe and listen for the signed event.
Generate a filled-in PDF (no signature) for records or printingREST APIPOST /api/documents/:id/fill returns the PDF bytes directly.
Try it in an afternoon with no developers availableDashboard onlyUpload, Detect blanks, Send for signature. Come back to this page when you want to automate.
Examples use placeholder keys. Sign in to see this guide with your own keys and this server’s address filled in, ready to paste.

1Embedding Signadoc in your site

Embedding means placing one of Signadoc’s pages inside yours with an <iframe>. Two pages can be embedded: the field designer (upload a PDF, place fields, create a signing link) and the signing page (what a signer sees). Signadoc talks back to your page with window.postMessage events so you know when something happened.

A. Embed the field designer

  1. Allow your origin. In Settings → Allowed embed origins add the origin of the page that will host the iframe, for example https://portal.example.com. Signadoc refuses to load the designer from anywhere else, which is what makes it safe to use the publishable key in HTML.
  2. Add the iframe. Point it at /embed/define with your publishable key. Optionally pre-select an existing template so the user only places fields or creates a link.
  3. Listen for events. Every message has source: "signadoc" and an event name. Use them to close a modal, store the new document ID, or show the signing link.
<!-- 1. The iframe. Signadoc checks that this page's origin is on your allowed list. -->
<iframe id="signadoc" src="https://signadoc.app/embed/define?key=pk_YOUR_PUBLISHABLE_KEY"
        width="100%" height="820" style="border:0;border-radius:12px"></iframe>

<!-- 2. Listen for what happens inside it. -->
<script>
  window.addEventListener('message', (e) => {
    if (e.data?.source !== 'signadoc') return;
    switch (e.data.event) {
      case 'document.uploaded': console.log('New template', e.data.document_id); break;
      case 'fields.saved':      console.log('Fields defined', e.data.document_id); break;
      case 'envelope.created':  console.log('Signing link', e.data.envelope.sign_url); break;
      case 'done':              /* close your modal, move on */ break;
    }
  });
</script>

Events from the designer

EventMeaning
readyThe designer has loaded and is ready for input.
document.uploadedThe user uploaded a PDF. Includes document_id. Store it: it is now a reusable template on your account.
document.openedAn existing document was opened (when you passed document=).
fields.savedFields were saved. Includes document_id and the field list.
envelope.createdThe user clicked “Send for signature”. Includes the envelope with sign_url and embed_sign_url.
doneThe user finished. A good moment to close your modal.
errorSomething went wrong. Includes message.
resizeThe content height changed. Includes height, so you can grow the iframe.

Prefer a helper script?

Load https://signadoc.app/embed.js and let it build the iframe, handle auto-resizing and route events to one callback:

<script src="https://signadoc.app/embed.js"></script>
<div id="signadoc"></div>
<script>
  Signadoc.embed({
    container: '#signadoc',
    publishableKey: 'pk_YOUR_PUBLISHABLE_KEY',
    documentId: 'doc_…',                 // optional: open an existing template
    options: { title: 'Set up your contract', accent: '#4f46e5', allow_upload: 1 },
    onEvent(event, data) {
      if (event === 'envelope.created') window.location = data.envelope.sign_url;
    },
  });
</script>

URL options (also accepted by the helper’s options): document (open a template), title, accent (hex colour), allow_upload=0 (hide the upload step), external_ref (your own ID stored on the document).

Keep the publishable key out of the page

If you would rather not expose any key in HTML, create a short-lived embed session from your server with the secret key and give the iframe the returned URL. The token expires (default 1 hour) and can only touch the document you name.

curl -X POST https://signadoc.app/api/embed/sessions \
  -H "X-Api-Key: sk_YOUR_SECRET_KEY" -H "Content-Type: application/json" \
  -d '{ "kind": "define", "document_id": "doc_…", "ttl_seconds": 1800, "title": "Set up your contract" }'

# → { "kind": "define", "token": "emb_…", "url": "https://signadoc.app/embed/define?token=emb_…", "expires_in": 1800 }
#   Use "url" as the iframe src. No key appears in your HTML.

B. Embed the signing page

Every signing request (an envelope) has two links: sign_url for emails, and embed_sign_url which is the same page with ?embed=1. The embed version hides Signadoc branding and emits events, so you can show it in an iframe on your own thank-you, checkout or onboarding page.

  1. Create the envelope on your server (see the API section below, or use POST /api/embed/sessions with kind: "sign" which creates it and returns the embed URL in one call).
  2. Render the iframe with embed_sign_url and listen for signed.
<!-- Server side: create the envelope and hand embed_sign_url to your template -->
<iframe id="sign" src="EMBED_SIGN_URL" width="100%" height="900" style="border:0"></iframe>
<script>
  window.addEventListener('message', (e) => {
    if (e.data?.source !== 'signadoc') return;
    if (e.data.event === 'progress') console.log(e.data.done + ' of ' + e.data.total + ' fields');
    if (e.data.event === 'signed')   { /* e.data.envelope_id is complete: fetch the PDF from your server */ }
  });
</script>

<!-- Or with the helper -->
<script>
  Signadoc.embed({ container: '#sign', mode: 'sign', signUrl: 'SIGN_URL', onEvent: (ev, d) => ev === 'signed' && done(d) });
</script>
EventMeaning
readyThe signing page has loaded.
progressThe signer completed a field. Includes done and total counts.
signedThe signer finished. The signed PDF is now available through the API.
errorThe link is invalid, expired, voided or already signed. Includes message.
resizeContent height changed. Includes height.

You can also pre-fill unlocked fields on the URL: …?embed=1&Phone=555-0100. A complete host page lives at /examples/host-page.html.

2Using the REST API from your server

The API is ordinary HTTPS with JSON bodies. Authenticate every call with your secret key in an X-Api-Key header (or Authorization: Bearer). The typical flow has four steps; steps 1 and 2 happen once per template, steps 3 and 4 once per signer.

1

Upload a template

POST /api/documents

Upload the PDF once. It becomes a document with an ID you reuse forever.

2

Define the fields

PUT /api/documents/:id/fields

Or skip the API and place fields in the dashboard with Detect blanks. Each field has a key such as Name or Address.

3

Create a signing request

POST /api/documents/:id/envelopes

Pass values for the keys you know. They are stamped in and locked. Signadoc returns sign_url (and emails it if you ask).

4

Collect the result

GET /api/envelopes/:id

Poll status, read the audit trail, and download the signed PDF from signed_pdf_url.

Example: send a contract to a new customer

Suppose you created the template “Service Agreement” in the dashboard with fields Name, Address, Phone, Email, Date (automatic) and Signature. When a customer signs up, your server runs:

curl -X POST https://signadoc.app/api/documents/DOC_ID/envelopes \
  -H "X-Api-Key: sk_YOUR_SECRET_KEY" -H "Content-Type: application/json" \
  -d '{
        "signer_name":  "Jane Doe",
        "signer_email": "jane@example.com",
        "values": { "Name": "Jane Doe", "Address": "123 Main St\nSpringfield, IL", "Phone": "555-0100", "Email": "jane@example.com" },
        "locked": true,
        "redirect_url": "https://example.com/thanks",
        "external_ref": "customer_8841",
        "send": true,
        "subject": "Please sign your service agreement"
      }'

Signadoc creates the envelope, stamps the four values into the PDF as read-only text, emails Jane a private link, and responds:

{
  "envelope": {
    "id": "env_gKGmIzcI59N6",
    "status": "pending",
    "signer_name": "Jane Doe",
    "signer_email": "jane@example.com",
    "sign_url": "https://signadoc.app/sign/…",
    "embed_sign_url": "https://signadoc.app/sign/…?embed=1",
    "signed_pdf_url": null,
    "external_ref": "customer_8841",
    "created_at": "2026-09-28T02:31:10.512Z"
  },
  "delivery": { "sent": true, "mode": "smtp" }
}

Jane opens the link, fills anything you did not supply, signs, and is redirected to your redirect_url. Later your server checks on it:

curl https://signadoc.app/api/envelopes/env_gKGmIzcI59N6 -H "X-Api-Key: sk_YOUR_SECRET_KEY"
# → { "envelope": { "status": "completed", "completed_at": "…", "signed_pdf_url": "https://signadoc.app/api/envelopes/env_…/signed.pdf",
#                   "audit": [ { "event": "envelope.created", "at": "…" }, { "event": "envelope.viewed", "ip": "…" }, { "event": "envelope.signed" }, … ] } }

curl https://signadoc.app/api/envelopes/env_gKGmIzcI59N6/signed.pdf -H "X-Api-Key: sk_YOUR_SECRET_KEY" -o signed.pdf

The same thing in JavaScript

// Node 18+ (or any runtime with fetch). Keep SIGNADOC_SECRET_KEY in an environment variable.
const BASE = 'https://signadoc.app';
const headers = { 'X-Api-Key': process.env.SIGNADOC_SECRET_KEY, 'Content-Type': 'application/json' };

async function sendAgreement(customer) {
  const res = await fetch(`${BASE}/api/documents/${process.env.AGREEMENT_DOC_ID}/envelopes`, {
    method: 'POST', headers,
    body: JSON.stringify({
      signer_name: customer.name, signer_email: customer.email,
      values: { Name: customer.name, Address: customer.address, Phone: customer.phone },
      external_ref: customer.id, send: true,
    }),
  });
  if (!res.ok) throw new Error((await res.json()).error);   // every error is { "error": "why" }
  const { envelope } = await res.json();
  return envelope;                                           // envelope.sign_url, envelope.id
}

async function isSigned(envelopeId) {
  const res = await fetch(`${BASE}/api/envelopes/${envelopeId}`, { headers });
  const { envelope } = await res.json();
  return envelope.status === 'completed' ? envelope.signed_pdf_url : null;
}

Other common calls

Send one document to many people

Each recipient gets their own envelope and email. Shared values apply to everyone; per-recipient values override them.

curl -X POST https://signadoc.app/api/documents/DOC_ID/send \
  -H "X-Api-Key: sk_YOUR_SECRET_KEY" -H "Content-Type: application/json" \
  -d '{ "recipients": [
          { "name": "Jane Doe", "email": "jane@example.com", "values": { "Name": "Jane Doe" } },
          { "name": "Bob Ray",  "email": "bob@example.com",  "values": { "Name": "Bob Ray" } } ],
        "values": { "Company": "Acme LLC" },
        "subject": "Please sign the updated policy" }'
# → { "created": 2, "sent": 2, "results": [ { "email": "jane@example.com", "envelope": { "sign_url": "…" }, "delivery": { "sent": true } }, … ] }

Fill a PDF without any signing

curl -X POST https://signadoc.app/api/documents/DOC_ID/fill -H "X-Api-Key: sk_YOUR_SECRET_KEY" \
  -H "Content-Type: application/json" -d '{ "values": { "Name": "Jane Doe", "Phone": "555-0100" } }' -o filled.pdf

Upload a template and define its fields in code

# Upload (multipart). Optional: name, tags, external_ref, and "fields" as a JSON string.
curl -X POST https://signadoc.app/api/documents -H "X-Api-Key: sk_YOUR_SECRET_KEY" \
  -F "file=@agreement.pdf" -F "name=Service Agreement" -F "tags=Sales, Onboarding"
# → { "document": { "id": "doc_…", "pages": [ { "width": 612, "height": 792 } ], "fields": [] } }

# Fields: coordinates are PDF points from the top-left of the page.
curl -X PUT https://signadoc.app/api/documents/DOC_ID/fields -H "X-Api-Key: sk_YOUR_SECRET_KEY" -H "Content-Type: application/json" \
  -d '{ "fields": [
        { "key": "Name",      "label": "Name",      "type": "name",      "page": 1, "x": 90, "y": 150, "w": 200, "h": 18, "fill": "api" },
        { "key": "Date",      "label": "Date",      "type": "date",      "page": 1, "x": 400, "y": 150, "w": 110, "h": 18, "fill": "auto", "auto_source": "date" },
        { "key": "Signature", "label": "Signature", "type": "signature", "page": 1, "x": 90, "y": 640, "w": 200, "h": 44 } ] }'

Tip: it is usually easier to place fields visually in the dashboard, then read them back with GET /api/documents/:id/fields if your code needs the keys.

Full endpoint reference

Every endpoint with parameters, example requests and responses, error codes and object shapes.

Open the REST API reference ›