SignedBySign in

Build on SignedBy. Fast.

A REST API and outbound webhooks for wiring SignedBy into your CRM, app, or onboarding flow — create and send documents, get notified the moment one's signed, and sync it all back automatically. Starts on the $7/mo Pro plan (100 free document-sends a month, then billed per document) and becomes fully unlimited on Business ($29/mo) — no separate developer plan to buy first.

Start for free →

No credit card required — 3 free documents every month to try it.

$ curl -X POST https://signedby.ai/api/v1/documents \
  -H "Authorization: Bearer sb_live_..." \
  -d '{"template_id":"3c78a1e4-...","signer":{"email":"jane@acme.com"}}'                 

{
  "id": "7fdd90eb-9152-4031-a767-c0632126dc53",
  "status": "sent"
}
The signer's side of the same API call: a handwritten signature drawn in the signature pad on a phone, with a yellow slide-to-sign bar ready to submit

A native app on Zapier, plus Pipedrive, HubSpot, Airtable, Notion, Attio, Brevo, and 1,500+ other apps via Make — see the Pipedrive walkthrough below.

Each is a trademark of its respective owner; SignedBy is not affiliated with or endorsed by any of them — they're reachable via Zapier's or Make's own connectors to each, not a native SignedBy integration (Zapier and Make themselves excepted).

Starts on Pro ($7/mo)

No separate developer plan or per-seat API tier — 100 free document-sends a month on Pro/Team, fully unlimited (plus webhooks) on Business ($29/mo). See how that compares to DocuSign, SignNow, and PandaDoc on our comparison pages.

REST + webhooks

Plain JSON over HTTPS, HMAC-signed outbound events. No SDK required.

No sandbox needed

The free tier's 3 documents/month is real enough to build and test against.

Rate limit

60 document creates per hour per org — plenty for real usage, generous for testing.

Authentication

Generate a key from Settings → Integration & API — every plan can generate one now, including Free. Send it as a bearer token on every request:

Authorization: Bearer sb_live_...

Missing or invalid keys get a 401. What happens after that depends on your plan: Business is unlimited, Pro and Team are metered (100 free document-sends/month, then billed per document — see /console for pricing), and Free is capped at the same 3 documents/month the dashboard gives you — extendable any time with a one-time $5 pack of 25 extra document credits (never expire, no subscription needed). A capped 402 carries a ready upgrade_urlStripe Checkout link right in the response, so paying doesn't mean leaving the API to go find billing in the dashboard:

401  { "error": "Missing API key. Pass it as 'Authorization: Bearer <key>'." }
401  { "error": "Invalid API key." }
402  { "error": "You've hit the Free plan's 3 documents/month limit. Upgrade to keep going.",
       "upgrade_url": "https://checkout.stripe.com/..." }

Endpoints

Prefer to import this into Postman, Insomnia, or a codegen tool? The full OpenAPI 3.0 specis generated straight from the same request-validation code this API runs — not hand-copied — so it can't drift from what's actually deployed. (First published 2026-09-12.)

POST/api/v1/documents

Create a document from a template and send it to one signer.

Request

curl -X POST https://signedby.ai/api/v1/documents \
  -H "Authorization: Bearer sb_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "template_id": "3c78a1e4-dc56-4769-87f6-65e344dd6d8f",
    "signer": { "email": "jane@acme.com", "name": "Jane", "auth_required": false },
    "expires_at": "2026-08-15T00:00:00Z",
    "invite_subject": "Please sign your Acme agreement",
    "invite_message": "Thanks for your business — just one form to go."
  }'

Response

201
{
  "id": "7fdd90eb-9152-4031-a767-c0632126dc53",
  "status": "sent",
  "expires_at": "2026-08-15T00:00:00Z",
  "auth_required": false
}
POST/api/v1/documents

Multi-party version — send the same template to 2+ role-tagged signers in one call. 'role' maps to the template's Party 1 / Party 2 / … field assignments.

Request

curl -X POST https://signedby.ai/api/v1/documents \
  -H "Authorization: Bearer sb_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "template_id": "3c78a1e4-dc56-4769-87f6-65e344dd6d8f",
    "signers": [
      { "role": 0, "email": "buyer@acme.com",  "name": "Buyer",  "auth_required": true  },
      { "role": 1, "email": "seller@acme.com", "name": "Seller", "auth_required": false }
    ]
  }'

Response

201
{
  "id": "643d45b3-...",
  "status": "sent",
  "expires_at": null,
  "signers": [
    { "id": "...", "role": 0, "email": "buyer@acme.com",  "auth_required": true  },
    { "id": "...", "role": 1, "email": "seller@acme.com", "auth_required": false }
  ]
}
GET/api/v1/documents?status=completed&limit=20&offset=0

List/search the org's documents. status is optional (draft, sent, completed, declined, voided); limit defaults to 20, max 100.

Request

curl "https://signedby.ai/api/v1/documents?status=completed&limit=20" \
  -H "Authorization: Bearer sb_live_..."

Response

200
{
  "documents": [
    { "id": "...", "title": "Freelance Agreement", "status": "completed",
      "created_at": "2026-07-28T10:04:00Z", "updated_at": "2026-07-29T09:11:00Z",
      "expires_at": null }
  ],
  "total": 42,
  "limit": 20,
  "offset": 0,
  "has_more": true
}
GET/api/v1/documents/{id}

Get a single document's status and its signers' progress.

Request

curl https://signedby.ai/api/v1/documents/<document-id> \
  -H "Authorization: Bearer sb_live_..."

Response

200
{
  "id": "...",
  "title": "Freelance Agreement",
  "status": "completed",
  "created_at": "2026-07-28T10:04:00Z",
  "updated_at": "2026-07-29T09:11:00Z",
  "expires_at": null,
  "signers": [
    { "email": "jane@acme.com", "name": "Jane", "status": "signed",
      "signed_at": "2026-07-29T09:11:00Z", "auth_required": false, "auth_verified": false }
  ]
}
GET/api/v1/templates

List the org's templates, for populating a dropdown in your own UI or a Make scenario.

Request

curl https://signedby.ai/api/v1/templates \
  -H "Authorization: Bearer sb_live_..."

Response

200
{
  "templates": [
    { "id": "3c78a1e4-...", "name": "Freelance Agreement", "page_count": 3,
      "created_at": "2026-07-01T12:00:00Z" }
  ]
}
GET/api/v1/documents/{id}/signed-file

Download the completed, flattened PDF once every signer has signed. 404 until it's ready.

Request

curl https://signedby.ai/api/v1/documents/<document-id>/signed-file \
  -H "Authorization: Bearer sb_live_..." -o signed.pdf

Response

200  <binary PDF>
404  { "error": "Signed PDF isn't ready yet." }
POST/api/v1/documents/{id}/void

Cancel a document that's out for signature — e.g. 'deal fell through, kill the pending contract.' Only works while status is 'sent'.

Request

curl -X POST https://signedby.ai/api/v1/documents/<document-id>/void \
  -H "Authorization: Bearer sb_live_..."

Response

200  { "success": true }
400  { "error": "Only documents that are out for signature can be voided." }

Webhooks

Available on Pro and higher (previously Business-only). Register one or more endpoint URLs in Settings → Webhooks (each gets its own signing secret). Every enabled endpoint receives all four document lifecycle events:

  • document.viewed — the signer opened the link for the first time
  • document.signed — one signer completed their part (fires per signer, not just once)
  • document.completed — every signer has finished
  • document.declined — a signer declined to sign

Payload shape — signer is omitted on document.completed, since that event isn't about one recipient:

{
  "event": "document.signed",
  "occurred_at": "2026-07-30T09:02:11.000Z",
  "document_id": "7fdd90eb-9152-4031-a767-c0632126dc53",
  "title": "Freelance Agreement",
  "status": "sent",
  "signer": { "email": "jane@acme.com", "name": "Jane" }
}

Each delivery is signed with your endpoint's own secret via an X-SignedBy-Signature header:

X-SignedBy-Signature: sha256=6f2c9e...

Verify it (Node.js) before trusting the payload:

const crypto = require("crypto");

function verify(rawBody, signatureHeader, secret) {
  const expected = "sha256=" + crypto.createHmac("sha256", secret).update(rawBody).digest("hex");
  return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signatureHeader));
}

Delivery is fire-and-forget with one retry after a 1-second delay (5-second timeout per attempt) — there's no persistent delivery log or manual-redelivery UI yet, so make your endpoint idempotent and check the document's current status via GET /api/v1/documents/{id} if you need to reconcile a missed event.

MCP server (for AI agents)

SignedBy also runs a real Model Context Protocol (MCP) server — ten tools show up ready to call, wrapping the same endpoints above plus Verified Badge sealing and a usage check.

MCP server URL

https://signedby.ai/api/mcp
https://signedby.ai/api/mcp

This is the one thing every client below needs — paste it into a connector (OAuth) or your MCP config (API key).

Claude (claude.ai, Desktop, Cowork) — connect with OAuth, no API key

Add SignedBy as a custom connector with just the server URL below, and you'll be walked through logging into SignedBy and approving a consent screen — the same "this app wants access to your account" flow as connecting Slack or Google Drive. Nothing to copy-paste.

  • Settings → Connectors → + → "Add custom connector" (or go straight to claude.ai/settings/connectors)
  • Paste the server URL — https://signedby.ai/api/mcp— and it detects OAuth automatically. The consent screen shows which SignedBy workspace you're connecting and exactly what's being granted: read-only access to documents and usage, the ability to send/seal/manage documents on your behalf, or both, depending on what the client asks for. Revoke it anytime from SignedBy's Settings → Connected apps.

Any other MCP client — connect with an API key

For Claude Code, or any MCP client that takes a plain URL and header rather than OAuth, add this to your MCP config:

{
  "mcpServers": {
    "signedby": {
      "url": "https://signedby.ai/api/mcp",
      "headers": { "Authorization": "Bearer sb_live_..." }
    }
  }
}

The signer's side is completely unchanged — an AI agent can trigger a signing request through these tools, but the recipient still has to open the link and sign it themselves, through the same per-recipient verification as every other document. Every document an agent creates or voids through this server is tagged in the audit trail (an agent_triggeredflag alongside the usual event log), so it's always possible to tell an agent-initiated send apart from one a person sent directly.

list_templates

List the org's templates, to find a template_id for create_and_send_document.

create_and_send_document

Create a document from a template and send it to one signer. Triggers the send only — the signer still opens the link and signs it themselves.

get_document_status

Look up a document's status and each signer's status by id.

list_documents

List the org's documents, optionally filtered by status.

void_document

Cancel a document that's still out for signature (status must be 'sent').

get_signed_file

Download the completed, signed PDF once every signer has finished.

seal_document

Certify a finished PDF as unaltered and identity-verified — hashes it, timestamps it, and returns a Verified Badge plus a public verification link. Not a signature request: it never asks anyone else to sign anything, it certifies a file the org already considers final. Requires a one-time Stripe Identity check on the org first. On Pro/Team it shares Console's metered allowance with create_and_send_document; on Business it's unlimited.

get_certificate_file

Download the standalone certificate PDF from a seal — only exists when seal_document ran with certificate_mode 'separate' or 'both' (the default).

get_badge_image

Download the Verified Badge PNG for a sealed document — the scannable proof badge, generated fresh from the audit trail.

get_usage

Check the org's plan and how much of its document allowance is left this period — read-only, no side effects.

Every tool here is available on every plan, including Free — MCP access itself isn't gated. What varies by plan is volume and templates. On Free: create_and_send_documentneeds an existing template, and Free orgs get exactly one self-saved template (there's no MCP tool to create one — save it once via the dashboard, then send with it here); usage of create_and_send_document and seal_document together is capped at 3 documents/month, extendable any time with a one-time $5 pack of 25 extra document credits (never expire, no subscription needed). Call get_usage any time to check the count without guessing, and if you do hit the cap, the error itself carries an upgrade_url— a ready Stripe Checkout link, so paying doesn't mean leaving the conversation to go find billing in the dashboard.

On Pro, Team, and Business: unlimited templates, and create_and_send_document usage rides on Console's metered access — 100 free document-sends/month, then billed per document, same metering on every plan including Business — not the plain REST API access described in Authentication above, which is metered on Pro/Team and unlimited on Business. See /console for the full pricing.

seal_document works differently by plan (updated 2026-08-28): on Free it shares the 3-document/ month allowance with sending, as above. On Pro and Team it now shares the same 100-free-then-billed Console allowance as create_and_send_document— sends and seals draw from one combined pool, and hitting your own spend cap returns a plain error asking you to raise or turn it off in the console (there's no credit pack to buy here — it's a self-set cap, not a plan limit). On Business, sealing stays fully unlimited and unmetered. It additionally needs a one-time Stripe Identity check on the org (from the dashboard's Settings page, not through this tool) before the first call succeeds, regardless of plan.

Connect via Zapier

A native SignedBy app on Zapier — no generic HTTP module or webhook copy-pasting needed. View SignedBy on Zapier → Three building blocks:

Trigger: New Document Completed

Fires when a document you sent finishes signing. Excludes documents you sealed yourself with a Verified Badge— those are certifications, not something a Zap should treat as "signed by everyone."

Action: Send Document

Pick a template, give it a recipient email, and it's out for signature — the same single-recipient path as POST /api/v1/documents. Multi-party sends aren't in this action yet; use the API directly for those.

Search: Find Document

Look up a document's status (and each recipient's) by ID mid-Zap — e.g. "before doing X, check whether this document has actually been signed yet."

Zapier is a trademark of Zapier Inc.; SignedBy is not affiliated with or endorsed by Zapier.

Connect via Make

There's no native SignedBy app in Make's marketplace yet — you don't need one for either direction:

SignedBy → Make

Add a Custom Webhook trigger module in Make, copy the URL it gives you, and paste it into Settings → Webhooks. Nothing to build on our side.

Make → SignedBy

Use Make's generic HTTP — Make a request module, with your API key as an Authorization: Bearer header, against any endpoint above.

How-to: Pipedrive, both directions

A worked example using Make to connect a Pipedrive deal to a SignedBy contract, start to finish — adapt the same shape for HubSpot, Airtable, or any other CRM.

1. Deal reaches "Contract sent" → send the contract

In Make: a Pipedrive trigger watching for deals entering your "Contract sent" stage, feeding into an HTTP — Make a request module that calls POST /api/v1/documents with the deal's contact as the signer. The contract is out for signature the moment the deal moves stage — no one has to remember to send it.

2. Contract signed → update the deal

A second Make scenario: a Custom Webhook trigger listening for document.completed, feeding into Pipedrive's Update a Deal (or Create a Note) module — logging that the contract came back signed, or moving the deal to its next stage automatically.

Pipedrive is a trademark of its respective owner; SignedBy is not affiliated with or endorsed by Pipedrive or Make.

FAQ & gotchas

What format does expires_at need?

A full UTC ISO-8601 datetime ending in Z — e.g. 2026-08-15T00:00:00Z. A timezone offset like +02:00 is rejected; convert to UTC first.

How does multi-party role numbering work?

role matches the Party 1 / Party 2 / … assignments already on the template — role 0 is Party 1, role 1 is Party 2, and so on. Every role actually used on the template needs a matching signer, or the request is rejected up front.

Is there a sandbox or test mode?

No — the free tier's 3 documents/month is real enough to build and test integrations against before upgrading.

What happens with auth_required signers?

That signer has to enter a one-time email code before the document opens at all — same per-recipient verification the dashboard offers, free on every plan.

Verify a sealed document independently

seal_documentabove and the dashboard's own Verified Badge both certify a finished PDF against a real, third-party Time Stamping Authority (RFC 3161) — not just a row in our own database. That means the result can be checked by anyone, without asking SignedBy or trusting our servers to tell the truth about our own documents.

The verification engine is open source and published as a zero-dependency npm package, verifiedby — run the exact same check inside your own app, or send a recipient to verifiedby.dev for the docs and API reference, or truedoc.eu to check a single file with nothing to install.

npm install verifiedby
import { verify } from "verifiedby";

const result = await verify(pdfBytes);
console.log(result.status);
// "verified" | "verified-untrusted-root" | "signed-untimed" |
// "unverified" | "mismatch" | "unsupported" | "no-signature"

Full source, methodology, and the complete status-verdict rubric are on GitHub.

Ready to build?

Sign up free — you can generate a key today. Upgrade to Pro ($7/mo) for real usage, or Business ($29/mo) for unlimited access and webhooks.

Start for free →