API & MCP
Create templates and pacts, send them for signature and collect signed documents from your own software, or let an AI assistant do it over the Model Context Protocol.
Your API keys
Log in to create a key. Each key acts as you; revoke it any time.
Sigil API (v1)
A JSON API over your account: templates (documents with their glyphs read), pacts (a document sent to people for signature), and your domains. Everything the dashboard does with a document can be done here, under the same plan limits. Using the API (and MCP) needs the Pro or Business plan; any account can create keys.
- Base URL:
https://api.signwithsigil.com - This reference:
GET https://api.signwithsigil.com/v1returns this page as Markdown (no key needed). Read it on screen athttps://www.signwithsigil.com/api; the full documentation, with examples in several languages, is at https://docs.signwithsigil.com. - Format: every response is JSON (
Content-Type: application/json), UTF-8, never cached. Times are ISO 8601 UTC (2026-10-20T14:05:00Z). - MCP: the same operations are available to AI assistants through the Model Context Protocol at
https://api.signwithsigil.com/mcp; see the end of this page.
Authentication
Create a key on https://www.signwithsigil.com/api after logging in (any number, each with a name; revoke them there too). Send it in either header:
X-Api-Key: sgk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Authorization: Bearer sgk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
A key starts with sgk_ followed by 40 letters and digits and is case-sensitive. It acts as the user who made it. 401 means the key is missing, wrong or revoked; 403 means the account's plan does not include API access (Pro and Business do).
Errors
Every error is { "error": "message" } with a status of 400 (bad input, the message says what), 401, 403, 404 (not yours or does not exist), or 409 (a plan limit, such as not enough documents left to send, or an action the pact's state does not allow).
Glyphs
Fields are read from glyphs, short codes typed into the document: \s1\ signature, \i1\ initials, \n1\ printed name, \d1\ date signed, \t1:Label\ text (\t1:Label!\ optional), \c1\ checkbox (\c1!\ optional). The number is the signer's order. A document uploaded through the API is refused if it has no glyphs, or if a signer has none. (On the website, paid plans can also place fields by hand; those come back here like any other field, at whatever size they were drawn.)
Me
GET /v1/me → { "email", "plan", "member_level", "documents": { "available" (null = unmetered), "unmetered", "monthly": { "granted", "used", "remaining", "period_start", "resets" } (null when unmetered), "packs", "signers_per_document" }, "domains": [ "sign.example.com" ] }
Documents
Sending a pact uses one document for every 3 signers, rounded up (1–3 signers: 1, 4–6: 2, …); CC recipients are free. It is charged once, when the pact is sent, and is not returned if the pact is later voided, declined or lapses; resending a link is free. Documents come from the month's allowance first (resets on the 1st, UTC; no rollover), then from document packs (bought on the Billing page; never expire). Sending without enough documents answers 409 and the pact stays a draft. A draft shows what sending it will cost in documents_cost; a sent pact shows what it cost in documents_charged.
Templates
| Call | What |
|---|---|
GET /v1/templates |
{ "data": [template…], "total" } (no fields) |
GET /v1/templates/{uid} |
one template with fields |
POST /v1/templates |
multipart: file (PDF or .docx, ≤ 10 MB), optional name. Returns 201 with the template. |
DELETE /v1/templates/{uid} |
204. Pacts made from it keep their own copy. |
A template: { "uid", "name", "source_name", "source_type": "pdf"|"docx", "page_count", "signer_count", "field_count", "date_created", "date_updated", "fields": [ { "recipient", "type", "page", "x", "y", "width", "height", "label", "required" } ] }. Positions are PDF points; y is the field's baseline (the bottom of its box) from the top of the page.
curl -H "Authorization: Bearer sgk_…" -F file=@agreement.pdf -F name="Consulting agreement" https://api.signwithsigil.com/v1/templates
Pacts
| Call | What |
|---|---|
GET /v1/pacts?status=&limit=&offset= |
{ "data": [pact…], "total", "limit", "offset" }. status: active (draft + sent), all (default), or one of draft, sent, sealed, declined, voided, lapsed. limit 1–200 (default 50). |
GET /v1/pacts/{uid} |
one pact with fields, events and links. Add ?include_links=1 to get each waiting signer's private signing_url. |
POST /v1/pacts |
create (and optionally send). JSON body below, or multipart with a file part for a one-off document (then send recipients as a JSON string). Returns 201. |
POST /v1/pacts/{uid}/send |
send a draft (uses its documents). Optional JSON { "lapse_days": 14 }. 409 when there are not enough documents left. |
POST /v1/pacts/{uid}/resend |
a new link for the signer whose turn it is (the old one dies). |
POST /v1/pacts/{uid}/void |
withdraw a sent pact. Optional JSON { "reason": "…" }. |
DELETE /v1/pacts/{uid} |
delete a draft (204). Sent pacts are voided, not deleted. |
GET /v1/pacts/{uid}/document.pdf |
the document as sent |
GET /v1/pacts/{uid}/signed.pdf |
the stamped document, once sealed |
GET /v1/pacts/{uid}/sigil.pdf |
the pact's sigil (its certificate of completion), once sealed |
Create body:
{
"template_uid": "7DXW69YJ",
"title": "Consulting agreement with Acme",
"message": "Please sign by Friday.",
"lapse_days": 14,
"domain_host": "sign.example.com",
"recipients": [
{ "name": "Avery Lane", "email": "avery@example.com", "role": "signer" },
{ "name": "Sam Ortiz", "email": "sam@example.com", "role": "signer" },
{ "name": "Records", "email": "records@example.com", "role": "cc" }
],
"send": true
}
Signers are numbered in the order given and must match the document's glyphs; CCs receive the finished copy. domain_host must be one of your verified domains; leave it out to send under the primary site. With "send": true the first signer is emailed in the same call; the response then carries "warning" if that email could not be delivered.
A pact: { "uid", "title", "status", "message", "page_count", "created_by": "web"|"api"|"mcp", "current_signer", "date_created", "date_sent", "date_lapses", "date_sealed", "date_updated", "documents_charged", "documents_cost", "sent_sha256", "signed_sha256", "recipients": [ { "order", "role", "name", "email", "status": "pending"|"viewed"|"signed"|"declined", "date_viewed", "date_signed", "typed_name", "decline_reason", "signing_url"? } ], "fields": […], "events": [ { "event", "at", "by", "to", "note" } ], "links": { "document", "signed", "sigil", "dashboard" } }.
Statuses: draft → sent → sealed, or declined, voided, lapsed (still unsigned at date_lapses). Signers are invited one at a time in order; current_signer is the order of the one whose turn it is. Poll GET /v1/pacts/{uid} for progress; outbound webhooks are not available yet.
Domains
GET /v1/domains → { "data": [ { "host", "status": "pending"|"verified"|"disabled", "brand_name", "date_verified", "date_checked" } ], "total" }. Domains are added and verified on the dashboard.
MCP
The MCP server is hosted here, so there is nothing to install: point your MCP client at the endpoint and give it an API key.
- Endpoint:
https://api.signwithsigil.com/mcp(Streamable HTTP). - Authentication: your API key in
Authorization: Bearer sgk_…(orX-Api-Key).401and403mean the same as for the API. - Tools:
get_account,list_templates,get_template,list_pacts,get_pact,get_signing_status,create_pact,send_pact,resend_pact,void_pact. They answer with the same JSON as the API calls above.
Claude Code:
claude mcp add --transport http sigil https://api.signwithsigil.com/mcp --header "Authorization: Bearer sgk_…"
A client configured with JSON (Claude Code's .mcp.json, and clients that use the same format):
{ "mcpServers": { "sigil": { "type": "http", "url": "https://api.signwithsigil.com/mcp", "headers": { "Authorization": "Bearer sgk_…" } } } }
A client that can only launch local (stdio) servers needs a local bridge that forwards to an HTTP MCP server and adds the header.