# squiggli > squiggli is the e-signature API built for AI agents and the apps they make. Send a document for signing with one API call; a person signs it in the browser; you get the signed PDF and a completion certificate. Australian business (prices in AUD ex GST). - API base: https://squiggli.billy-965.workers.dev/v1 (Bearer API key: sq_live_... or sq_test_...) - OpenAPI: https://squiggli.billy-965.workers.dev/openapi.json - MCP server (Streamable HTTP, OAuth or Bearer key): https://squiggli.billy-965.workers.dev/mcp - Sign up (free test key at once): https://squiggli.billy-965.workers.dev/app/signup - Embed script: https://squiggli.billy-965.workers.dev/embed.js (Squiggli.sign, Squiggli.editor) ## Pricing - $1.00 per document sent, $1.50 fully white-labelled. A document is one envelope, however many files or signers. - The first 15 documents a business sends are free. Test documents are always free and unlimited. - Drafts, templates, reminders, resends and downloads are free. Signers never pay or need an account. - Over $5,000 a month: rates are negotiated. ## Core objects - Envelope: documents + signers + fields. Status draft, sent, viewed, signed (complete), declined, voided or expired. - Signer: name, email, delivery (email or link), order for sequential signing. Each has a signing_url. - Field: signature, initials, date_signed, name, email, text or checkbox, on a page at x/y points from the top-left. - Template: a block document (headings, paragraphs, lists, tables, fields, signature blocks, {{variables}}) or a PDF with fields by role. - Webhook events: envelope.sent, envelope.viewed, envelope.signed, envelope.completed, envelope.declined, envelope.voided, envelope.expired. ## Connect your agent (MCP server: https://squiggli.billy-965.workers.dev/mcp) - Claude Code: claude mcp add --transport http --scope user squiggli https://squiggli.billy-965.workers.dev/mcp --header "Authorization: Bearer $SQUIGGLI_API_KEY" - Codex: in ~/.codex/config.toml add [mcp_servers.squiggli] with url = "https://squiggli.billy-965.workers.dev/mcp" and bearer_token_env_var = "SQUIGGLI_API_KEY" - Cursor: ~/.cursor/mcp.json { "mcpServers": { "squiggli": { "url": "https://squiggli.billy-965.workers.dev/mcp", "headers": { "Authorization": "Bearer sq_test_..." } } } } - VS Code (Copilot): .vscode/mcp.json server { "type": "http", "url": "https://squiggli.billy-965.workers.dev/mcp" } with an Authorization header from a promptString input - Windsurf: ~/.codeium/windsurf/mcp_config.json with "serverUrl": "https://squiggli.billy-965.workers.dev/mcp" and the Authorization header - Gemini CLI: ~/.gemini/settings.json with "httpUrl": "https://squiggli.billy-965.workers.dev/mcp" and the Authorization header - Claude app and ChatGPT: add a custom connector with the URL https://squiggli.billy-965.workers.dev/mcp and sign in (OAuth). No key needed. - Keys: sign up at https://squiggli.billy-965.workers.dev/app/signup for a test key, or create one with the squiggli_create_api_key tool. Keep it in SQUIGGLI_API_KEY, never in the repo. ## Electronic signatures and the law - Signatures meet the Electronic Transactions Act 1999 (Cth) s 10 and the state Acts: each signer is identified by their own link (optionally plus an access code: signers[].access_code), must open the document first, agrees to sign electronically, and confirms an intent statement. Document hashes are checked at every signature, and the certificate records it all. - Some documents need more than an e-signature (wills, powers of attorney, documents that must be witnessed, land title documents). See https://squiggli.billy-965.workers.dev/legal/electronic-signatures. - Anyone can check a signed PDF hasn't changed at https://squiggli.billy-965.workers.dev/verify. ## Comparisons - [squiggli vs DocuSign](https://squiggli.billy-965.workers.dev/compare/docusign): No seats and no envelope caps. Pay $1.00 when a document goes out. - [squiggli vs PandaDoc](https://squiggli.billy-965.workers.dev/compare/pandadoc): API documents never pause. No credit packs to top up. - [squiggli vs Dropbox Sign](https://squiggli.billy-965.workers.dev/compare/dropbox-sign): The API has no monthly plan. Send one document for $1.00. - [squiggli vs Adobe Acrobat Sign](https://squiggli.billy-965.workers.dev/compare/adobe-acrobat-sign): Signing from code, without an Acrobat licence for everyone. - [squiggli vs SignNow](https://squiggli.billy-965.workers.dev/compare/signnow): Pay per document, not per user. - [squiggli vs Zoho Sign](https://squiggli.billy-965.workers.dev/compare/zoho-sign): No credits to buy in blocks of 500. Pay for what you send. - [squiggli vs Annature](https://squiggli.billy-965.workers.dev/compare/annature): $1.00 a document instead of $1.75, and built for software to send. - [squiggli vs Documenso](https://squiggli.billy-965.workers.dev/compare/documenso): Unlimited API access without a US$250 a month plan. - [squiggli vs BoldSign](https://squiggli.billy-965.workers.dev/compare/boldsign): Made for AI agents, from the first call. ## Docs - [Documentation](https://squiggli.billy-965.workers.dev/docs): 19 pages, from the quickstart to the full API reference - [Quickstart](https://squiggli.billy-965.workers.dev/docs/quickstart): Get a key, send a test document, sign it, and get the signed PDF back. About five minutes. - [Connect your AI](https://squiggli.billy-965.workers.dev/docs/ai-agents): Give Claude Code, Codex, Cursor, Copilot, Windsurf, Gemini CLI, the Claude app or ChatGPT access to squiggli, then let it add signing to your app. - [How squiggli works](https://squiggli.billy-965.workers.dev/docs/concepts): Envelopes, documents, signers and fields, the statuses they move through, and the difference between test and live. - [Send a document](https://squiggli.billy-965.workers.dev/docs/sending): Upload PDFs or use templates, place fields, choose how signers get their link, and set the order, deadline and reminders. - [Templates](https://squiggli.billy-965.workers.dev/docs/templates): Reusable documents with variables: written as blocks that squiggli lays out, or a PDF with fields placed by role. - [The signing experience](https://squiggli.billy-965.workers.dev/docs/signing): What signers see, how they are identified, access codes, branding, white-label, and what happens after they sign. - [Tracking and the audit trail](https://squiggli.billy-965.workers.dev/docs/tracking): See who opened what, from which link, and every event in order. - [Webhooks](https://squiggli.billy-965.workers.dev/docs/webhooks): Have squiggli tell your server when documents are sent, viewed, signed, completed, declined, voided or expire. - [Embed signing and the editor](https://squiggli.billy-965.workers.dev/docs/embedding): Put the signing page, the template editor or an envelope view inside your own app with one script. - [Signed PDFs and certificates](https://squiggli.billy-965.workers.dev/docs/completion): What you get when everyone signs, how to download it, and how anyone can check a signed PDF. - [Authentication](https://squiggli.billy-965.workers.dev/docs/authentication): API keys, test and live, embed tokens, OAuth for the MCP server, and platform credentials. - [API reference](https://squiggli.billy-965.workers.dev/docs/api): Every endpoint of the squiggli API, with parameters, bodies and responses. - [Errors, retries and pagination](https://squiggli.billy-965.workers.dev/docs/errors): Every error code, how Idempotency-Key makes retries safe, and how to page through lists. - [MCP server](https://squiggli.billy-965.workers.dev/docs/mcp): The tools squiggli gives AI agents, generated from the server itself. - [Chesterman's apps](https://squiggli.billy-965.workers.dev/docs/platform): How Kiewa Trades, Patch and Kiewa send documents for a business, billed to that business. - [Pricing and usage](https://squiggli.billy-965.workers.dev/docs/pricing): Pay per document sent. No seats, no monthly fee. Billed on your Kiewa statement. - [Electronic signatures and the law](https://squiggli.billy-965.workers.dev/docs/compliance): How squiggli signatures meet the Electronic Transactions Act 1999 (Cth), and which documents may need more. - [Limits](https://squiggli.billy-965.workers.dev/docs/limits): Sizes, counts and timings, in one place. - [Integration guide for agents](https://squiggli.billy-965.workers.dev/llms-full.txt): copy-ready code for Node, Next.js, Python, PHP and curl - [OpenAPI document](https://squiggli.billy-965.workers.dev/openapi.json) --- # Add squiggli e-signatures to this app 1. Get a key: sign up at https://squiggli.billy-965.workers.dev/app/signup (you get a test key straight away), or create one with the squiggli_create_api_key MCP tool. Store it as SQUIGGLI_API_KEY. Never put it in client-side code. 2. Send: POST https://squiggli.billy-965.workers.dev/v1/envelopes with documents, signers and send: true. Use test: true (or a sq_test_ key) until it works. 3. Know when it's signed: register a webhook (POST /v1/webhooks) and handle envelope.completed, or poll GET /v1/envelopes/{id}. 4. Get the signed PDF: GET /v1/envelopes/{id}/signed.pdf and /certificate.pdf. ## Node.js / TypeScript (no SDK needed) ```ts // lib/squiggli.ts. Put SQUIGGLI_API_KEY in your environment (sq_test_... while building). const SQUIGGLI = 'https://squiggli.billy-965.workers.dev/v1' export async function squiggli(method: string, path: string, body?: unknown, idempotencyKey?: string): Promise { const res = await fetch(SQUIGGLI + path, { method, headers: { Authorization: `Bearer ${process.env.SQUIGGLI_API_KEY}`, 'Content-Type': 'application/json', ...(idempotencyKey ? { 'Idempotency-Key': idempotencyKey } : {}), }, body: body === undefined ? undefined : JSON.stringify(body), }) const json = await res.json() if (!res.ok) throw new Error(`${json.error?.code}: ${json.error?.message}`) return json } // Send a PDF for signing. Every write takes an Idempotency-Key: reuse it when you retry. export async function sendForSignature(pdf: Buffer, signer: { name: string; email: string }, orderId: string) { return squiggli('POST', '/envelopes', { title: `Quote ${orderId}`, documents: [{ name: `Quote ${orderId}.pdf`, file_base64: pdf.toString('base64') }], signers: [signer], // Leave out fields and squiggli adds a signature page. Or place them: points from the top-left of the page. fields: [{ signer: 0, type: 'signature', page: 1, x: 72, y: 690 }, { signer: 0, type: 'date_signed', page: 1, x: 320, y: 705 }], deadline_days: 14, metadata: { order_id: orderId }, send: true, }, `send-${orderId}`) } ``` ### Webhooks (Express shown; any framework that gives you the raw body works) ```ts import crypto from 'node:crypto' // Register once: POST /v1/webhooks { url } returns a secret (whsec_...). Store it as SQUIGGLI_WEBHOOK_SECRET. app.post('/webhooks/squiggli', express.raw({ type: 'application/json' }), (req, res) => { const header = req.header('Squiggli-Signature') ?? '' // t=1731000000,v1=hex const [t, v1] = header.split(',').map((p) => p.split('=')[1]) const expected = crypto.createHmac('sha256', process.env.SQUIGGLI_WEBHOOK_SECRET!).update(`${t}.${req.body}`).digest('hex') const fresh = Math.abs(Date.now() / 1000 - Number(t)) < 300 if (!fresh || !v1 || !crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(v1))) return res.sendStatus(400) const event = JSON.parse(req.body.toString()) // { id, type, created_at, data: { envelope, signer? } } // Dedupe on event.id (retries reuse it). type: envelope.sent | viewed | signed | completed | declined | voided | expired if (event.type === 'envelope.completed') { const orderId = event.data.envelope.metadata.order_id // event.data.envelope.files.signed_pdf_download is a link to the signed PDF (valid a week) } res.sendStatus(200) }) ``` ## Rules worth knowing - Every write takes an Idempotency-Key header. Repeat a request with the same key and you get the first answer back, so retries never send twice. - Errors are { "error": { "code", "message" } } and the message says how to fix it. 402 card_required comes with add_card_url: show it to the business owner. - Field coordinates are PDF points from the top-left corner of the page. GET /v1/envelopes/{id} returns each page's width and height. - Templates bring their own fields: block templates are laid out by squiggli, with {{variables}} filled from variables. - delivery "link" skips emails: show signers[].signing_url in your app, or embed it with https://squiggli.billy-965.workers.dev/embed.js (Squiggli.sign). - Full reference: https://squiggli.billy-965.workers.dev/openapi.json. MCP server: https://squiggli.billy-965.workers.dev/mcp. --- # Add squiggli e-signatures to this app 1. Get a key: sign up at https://squiggli.billy-965.workers.dev/app/signup (you get a test key straight away), or create one with the squiggli_create_api_key MCP tool. Store it as SQUIGGLI_API_KEY. Never put it in client-side code. 2. Send: POST https://squiggli.billy-965.workers.dev/v1/envelopes with documents, signers and send: true. Use test: true (or a sq_test_ key) until it works. 3. Know when it's signed: register a webhook (POST /v1/webhooks) and handle envelope.completed, or poll GET /v1/envelopes/{id}. 4. Get the signed PDF: GET /v1/envelopes/{id}/signed.pdf and /certificate.pdf. ## Next.js (App Router) ```ts // app/api/contracts/route.ts: send from the server, never from the browser (the key is secret). export async function POST(req: Request) { const { name, email, quoteId } = await req.json() const res = await fetch('https://squiggli.billy-965.workers.dev/v1/envelopes', { method: 'POST', headers: { Authorization: `Bearer ${process.env.SQUIGGLI_API_KEY}`, 'Content-Type': 'application/json', 'Idempotency-Key': `quote-${quoteId}` }, body: JSON.stringify({ title: `Quote ${quoteId}`, template_id: process.env.SQUIGGLI_QUOTE_TEMPLATE_ID, variables: { 'client.name': name, quote_id: quoteId }, signers: [{ name, email, delivery: 'link' }], // link: no email, you show the signing page yourself send: true, }), }) const envelope = await res.json() if (!res.ok) return Response.json(envelope, { status: res.status }) return Response.json({ signingUrl: envelope.signers[0].signing_url, envelopeId: envelope.id }) } ``` ```tsx // Embedded signing: show the signing page inside your app. 'use client' import Script from 'next/script' export function SignHere({ signingUrl }: { signingUrl: string }) { return ( <>