API Reference
A REST API for electronic signatures. Create an envelope, attach PDF documents, place signature and data fields, and send it — LimeSign emails each signer a secure link, collects their input in order, and seals the finished PDF with a certificate of completion.
/v1 and speaks
JSON (documents are the one exception — you upload raw PDF bytes). Take the base URL
from the next section and prepend it to each path.Base URL
All API traffic goes to a single host over HTTPS. Prepend it to every path in this reference.
https://app.lime-sign.comEvery path below is relative to this base URL — for example, POST /v1/envelopes means
POST https://api.lime-sign.com/v1/envelopes.
Authentication
The API authenticates with a secret API key sent as a bearer token. Keys are organization-scoped: one key represents your whole organization, and every request it makes reads and writes that organization's data.
Getting a key
Sign in to the dashboard and open Organization Settings → Developer
(/settings). Create a key, give it a name, and copy the secret:
# A key looks like this — shown in full only once, at creation:
lsk_live_9f2c…<44 url-safe characters>
Making an authenticated request
Send the key in the Authorization header on every /v1 request:
curl https://api.lime-sign.com/v1/envelopes \ -H "Authorization: Bearer lsk_live_9f2c…"
A missing or malformed token returns 401 missing bearer token; an unknown or revoked key
returns 401 invalid api key. The /healthz and /readyz probes are the
only unauthenticated endpoints.
Conventions
| Topic | Detail |
|---|---|
| Format | Requests and responses are JSON (Content-Type: application/json), except document upload, which takes a raw application/pdf body. |
| Identifiers | All resource IDs are UUIDs. A malformed ID in a path returns 404, never 400. |
| Lists | Collection endpoints wrap results in { "data": [ … ] }. |
| Field coordinates | Field positions are page-ratios in the range [0,1] — fractions of the page width/height, so they render correctly at any zoom or page size. See Field types. |
| Errors | Non-2xx responses return { "error": "message" }. See Errors. |
| Tenancy | Every request is scoped to the key's organization automatically. You never pass an org ID. |
The signing workflow
Sending a document for signature is a four-step sequence. Each step is a single API call, and the order matters — you place fields after the document exists, and you can only send once every signer has at least one field.
1 · Create an envelope
An envelope is the unit that gets signed: a title, one or more signers, and (optionally)
a signing order. POST /v1/envelopes returns the envelope plus its signers, each with a generated ID.
2 · Add a document
Upload a PDF with POST /v1/envelopes/:id/documents. LimeSign records it, hashes it, and
rasterizes each page to an image so signers can view it on any device. The response includes the
document's id and pageCount.
3 · Place fields
Tell LimeSign where each signer signs. POST /v1/envelopes/:id/fields takes a batch of
fields, each tying a signerId and documentId to a type and a position on a page.
4 · Send
POST /v1/envelopes/:id/send validates the envelope, flips it to sent, and emails
the first signing group a secure link. As each signer finishes, the next group is notified automatically.
draft, have at
least one document, and have every signer covered by at least one field. Otherwise send returns
400 with a message naming what's missing.Full example
# 1. Create an envelope with two signers ENV=$(curl -s https://api.lime-sign.com/v1/envelopes \ -H "Authorization: Bearer $KEY" \ -H "Content-Type: application/json" \ -d '{"title":"Mutual NDA","signers":[ {"name":"Ada Lovelace","email":"ada@example.com","signingOrder":1}, {"name":"Alan Turing","email":"alan@example.com","signingOrder":2}]}') ENV_ID=$(echo "$ENV" | jq -r .id) SIGNER1=$(echo "$ENV" | jq -r '.signers[0].id') # 2. Upload the PDF (raw bytes) DOC=$(curl -s https://api.lime-sign.com/v1/envelopes/$ENV_ID/documents \ -H "Authorization: Bearer $KEY" \ -H "Content-Type: application/pdf" \ -H "X-Document-Name: nda.pdf" \ --data-binary @nda.pdf) DOC_ID=$(echo "$DOC" | jq -r .id) # 3. Place a signature field for the first signer on page 1 curl -s https://api.lime-sign.com/v1/envelopes/$ENV_ID/fields \ -H "Authorization: Bearer $KEY" \ -H "Content-Type: application/json" \ -d "{\"fields\":[{\"documentId\":\"$DOC_ID\",\"signerId\":\"$SIGNER1\", \"type\":\"signature\",\"page\":1,\"x\":0.6,\"y\":0.1,\"w\":0.25,\"h\":0.06}]}" # 4. Send it curl -s https://api.lime-sign.com/v1/envelopes/$ENV_ID/send \ -H "Authorization: Bearer $KEY" -X POST # → {"status":"sent","notified":1}
Field types
A field is a rectangle on a page, assigned to one signer. Its position is given as page-ratios:
x/y is the top-left corner and w/h the size, each a
fraction of the page. All four are in [0,1], and x+w and y+h must
not exceed 1 (the field has to fit on the page).
| type | What the signer does |
|---|---|
signature | Applies their adopted signature (typed or drawn). |
initial | Applies their initials. |
date | Auto-fills the date signed. |
text | Types free text. |
dropdown | Selects from a list. |
checkbox | Checks or clears a box. |
attachment | Uploads a supporting file. |
Each field also takes an optional required boolean (defaults to true).
Envelope lifecycle
| status | Meaning |
|---|---|
draft | Being assembled. Documents and fields can be added; not yet sent. |
sent | Out for signature. One or more signers have been notified. |
completed | Every signer has signed. The sealed PDF and certificate are available. |
declined | A signer declined. The envelope is stopped. |
voided | The sender cancelled it. Outstanding signing links no longer work. |
Signing order. Each signer has a signingOrder integer. Signers who share a
number are notified together (parallel); distinct numbers go one group at a time (sequential). When you
omit signingOrder, signers are numbered in the order you supply them — i.e. sequential by default.
Accounts & branding
An account is a sending profile inside your organization — it controls the branding a signer sees: the logo and accent color on the signing screen, and the sender name, reply-to address, subject, and body of the notification emails.
Pass an accountId when creating an envelope to send under that profile. Omit it and LimeSign
uses your organization's default account. Accounts are created and branded in the dashboard; the API
selects among them per envelope.
{ "title": "Order form", "accountId": "a1b2c3d4-…", "signers": [ … ] }
Webhooks
Instead of polling, let LimeSign notify your server when an envelope changes. You register an HTTPS endpoint in the dashboard, and LimeSign POSTs a signed JSON event to it as things happen.
Configuring endpoints
Webhooks are managed in the dashboard under Organization Settings → Webhooks. Endpoints you add there are your organization defaults — every account uses them. You almost never need more than that. If a particular account needs to route elsewhere, add a webhook on that account to override the default for its envelopes; accounts with no webhook of their own keep inheriting the org default.
Events
| Event | Fires when |
|---|---|
envelope.sent | An envelope is sent for signature. |
signer.signed | An individual signer completes their part. |
envelope.completed | All signers are done; the sealed PDF + certificate are ready. |
envelope.declined | A signer declines; the envelope stops. |
envelope.voided | The sender voids the envelope. |
A webhook.ping event is also sent when you click Send test in the dashboard.
Payload
The body carries the event, an envelope summary (status + signers), and authenticated API links to pull the full documents, sealed PDF, and certificate — LimeSign never puts file bytes in the webhook.
{
"id": "3f1a…", // unique delivery id — dedupe on this
"type": "envelope.completed",
"createdAt": "2026-08-19T14:03:22.000Z",
"envelope": {
"id": "e7c1…", "title": "Mutual NDA", "status": "completed", "accountId": null,
"signers": [ { "id": "9a3b…", "name": "Ada", "email": "ada@x.com",
"status": "signed", "signingOrder": 1, "signedAt": "2026-08-19T14:03:20Z" } ]
},
"signer": null, // set for signer.* events
"links": {
"self": "https://api.lime-sign.com/v1/envelopes/e7c1…",
"documents": "https://api.lime-sign.com/v1/envelopes/e7c1…/documents",
"certificate": "https://api.lime-sign.com/v1/envelopes/e7c1…/certificate"
}
}
Verifying the signature
Every request carries an X-LimeSign-Signature: t=<unix>,v1=<hmac> header. The
signature is HMAC-SHA256 over "{timestamp}.{rawBody}" using your endpoint's signing
secret (whsec_…, shown once when you create or rotate the endpoint). Recompute it and compare
in constant time; reject if the timestamp is old to prevent replays.
import crypto from "node:crypto"; function verify(rawBody, header, secret) { const { t, v1 } = Object.fromEntries(header.split(",").map(kv => kv.split("="))); if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return false; // replay guard const expected = crypto.createHmac("sha256", secret) .update(`${t}.${rawBody}`).digest("hex"); return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(v1)); }
Other headers: X-LimeSign-Event (the event type) and X-LimeSign-Delivery
(the delivery id, same as payload.id).
Delivery & retries
- Respond
2xxwithin 10 seconds to acknowledge. Any other status (or a timeout) is a failure. - Failures are retried with exponential backoff (up to 6 attempts, ~2m → 6h) before the delivery is marked failed. The endpoint's last status and error are shown in the dashboard.
- Delivery is at-least-once — the same event may arrive more than once, so make your
handler idempotent by keying on
payload.id. - Endpoints must be
https://. Return quickly and do slow work asynchronously.
MCP server
LimeSign ships a Model Context Protocol server, so an AI assistant (Claude Desktop, Claude Code, or any MCP client) can create, prepare, and send envelopes on your behalf — using the same API key and permissions as everything else here.
Configure
Create an API key under Organization Settings → Developer, then add the server to your
client. For Claude Desktop, edit claude_desktop_config.json:
{
"mcpServers": {
"limesign": {
"command": "npx",
"args": ["-y", "@limesign/mcp"],
"env": { "LIMESIGN_API_KEY": "lsk_live_…" }
}
}
}
That's it — npx fetches @limesign/mcp
from npm, no local checkout needed. Set LIMESIGN_API_BASE_URL too if you're pointing at a non-default host.
Tools
| Tool | What it does |
|---|---|
list_envelopes · get_envelope | Browse your envelopes and their status. |
create_envelope | Create a draft with a title and signers. |
add_document | Upload a local PDF to a draft. |
place_fields · list_fields | Place and review signature/date/text fields. |
send_envelope | Send a draft for signature. |
download_sealed · download_certificate | Save the sealed PDF and Certificate of Completion. |
API reference
Every endpoint — its request, response, status codes, and field‑level schemas — lives in the interactive reference, generated from our OpenAPI spec (the single source of truth), so it stays in lockstep with the live API.
Open the API reference → Download the OpenAPI spec
Import openapi.json into Postman, Insomnia, or your codegen tool to scaffold a client in minutes.
Errors
Errors return a non-2xx status and a JSON body of the shape { "error": "message" }.
| Status | Meaning |
|---|---|
400 | The request was malformed or failed a rule (bad field, missing document, coverage gap). The message says what. |
401 | Missing, malformed, or invalid/revoked API key. |
404 | Resource not found in your organization — or a malformed ID in the path. |
409 | Conflict with current state, e.g. sending an envelope that's already sent. |
500 | An unexpected server error. Safe to retry idempotent reads. |