---
name: signsail
description: >-
  Send documents for legally binding e-signature with Sign Sail. Use whenever
  the user wants to upload a PDF, place signature / date / text fields on it,
  send it to one or more signers, check signing progress, send a reminder, void
  an envelope, or download the completed sealed PDF. Also covers reusable
  document templates, prefilled sender fields, named signers, and the contact
  address book. Works over the Sign Sail REST API or MCP server and needs a Sign
  Sail Business-plan API key.
homepage: https://signsail.com
---

# Sign Sail e-signature

Sign Sail gets PDFs signed. The whole flow is programmatic: upload a PDF, drop
signature / date / text fields onto it, send it to signers, watch their
progress, and download the sealed PDF once everyone has signed. Every signed PDF
carries an audit trail (IP, timestamp, user-agent, SHA-256 hash) and is
cryptographically sealed. Anything the Sign Sail web app does, you can do here —
no dashboard step is required.

## When to use this skill

Use it when the user wants to:

- Send a PDF (contract, NDA, offer letter, waiver…) out for signature.
- Place or change signature/date/text/checkbox fields on a document.
- Check who has signed, who is still pending, or download a finished signed PDF.
- Reuse a document as a template, prefill sender fields, or void a sent envelope.
- Manage saved signing contacts.

## Setup (one time)

1. **API key.** The user needs a Sign Sail **Business** plan. Keys are created
   under **Account → Developer settings** at https://signsail.com and shown once at
   creation. A key looks like `ss_live_<prefix>_<secret>`. Free/Pro keys get
   `403 PLAN_REQUIRED`.
2. **Hold the key as a secret** (env var, e.g. `SIGNSAIL_API_KEY`). Never echo
   it back to the user or write it into files.
3. **Pick a transport:**
   - **MCP (preferred when available).** If a Sign Sail MCP server is connected
     (remote MCP at `https://signsail.com/api/mcp`, Bearer-authed with the same key),
     call its tools directly — see "MCP tools" below.
   - **REST.** Otherwise call the REST API. Base URL: `https://signsail.com/api/v1`.
     Every request sends `Authorization: Bearer $SIGNSAIL_API_KEY`.

The machine-readable contract is always at `https://signsail.com/api/v1/openapi.json`
(OpenAPI 3.1) — fetch it if you need a field or response you don't see here.

## The core flow

Upload → place fields → send → track → download.

```bash
KEY="$SIGNSAIL_API_KEY"
BASE="https://signsail.com/api/v1"

# 1. Upload a PDF — by URL (or -F file=@local.pdf for multipart,
#    or {"filename","contentBase64"} for base64). Returns status "processing".
DOC=$(curl -s -X POST "$BASE/documents" \
  -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
  -d '{"url":"https://example.com/contract.pdf","title":"NDA"}' | jq -r .id)

# 2. Place fields for signer 1. Coordinates are normalized 0..1, top-left origin.
#    This is REPLACE-ALL: send the complete field set every time.
curl -s -X PUT "$BASE/documents/$DOC/fields" \
  -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
  -d '{"placements":[
        {"recipientRef":1,"fieldType":"SIGNATURE","pageIndex":0,
         "x":0.12,"y":0.80,"w":0.25,"h":0.06,"required":true},
        {"recipientRef":1,"fieldType":"DATE_SIGNED","pageIndex":0,
         "x":0.45,"y":0.80,"w":0.18,"h":0.04,"required":true}
      ]}'

# 3. Send it. recipients must match the signer slots (here, one: ref 1).
#    Idempotency-Key makes a retried send safe (never double-sends).
ENV=$(curl -s -X POST "$BASE/envelopes" \
  -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
  -H "Idempotency-Key: nda-send-001" \
  -d "{\"documentId\":\"$DOC\",\"recipients\":[{\"email\":\"signer@acme.com\",\"name\":\"Dana\"}]}" | jq -r .id)

# 4. Track status (active | completed | voided) + per-recipient progress.
curl -s "$BASE/envelopes/$ENV" -H "Authorization: Bearer $KEY" | jq '.status, .recipients'

# 5. Download the sealed PDF once status is "completed" (409 NOT_READY before then).
curl -s "$BASE/envelopes/$ENV/pdf" -H "Authorization: Bearer $KEY" -o signed.pdf
```

### Step notes

- **Upload returns immediately** with `status:"processing"` while it scans for
  inline tags (see below). It then settles to `ready` (sendable) or `draft`.
  Poll `GET /documents/{id}/fields` until the status changes, or subscribe to
  the `document.processed` webhook. Pass `autoPlaceFields:false` to skip the
  scan and place fields yourself.
- **A document needs ≥1 signer field (recipientRef ≥ 1) to be sendable.** Sending
  one with no signer fields returns `409 NOT_READY`.
- **Optional send controls:** `ordered` (sign in sequence), `expiresAt`
  (ISO 8601), `reminderEveryDays`, `finalReminderDays`, `customMessage`.

## Field placement reference

Fields are placed with `PUT /documents/{id}/fields` (replace-all). Each
placement:

| key | meaning |
|---|---|
| `recipientRef` | **0** = sender "prefill" slot; **1..N** = signer slots |
| `fieldType` | see field types below |
| `pageIndex` | 0-based page |
| `x`, `y` | top-left corner of the box, normalized 0..1 |
| `w`, `h` | width / height, normalized 0..1 |
| `required` | boolean |
| `prefillValue` | optional default for a ref-0 field (see Prefill) |
| `label`, `fontSize`, `fontFamily`, `dateMode`, `dateFormat`, `fieldKey` | optional, see OpenAPI |

**Field types:** `SIGNATURE`, `INITIALS`, `DATE_SIGNED`, `TEXT`,
`CHECKBOX`, `NAME`, `EMAIL`, `PHONE`, `ADDRESS`, `COMPANY`,
`JOB_TITLE`, `NUMBER`.

The final PDF draws only the value, not the box — sizes are layout, not borders.

### Auto-place fields with tags (no coordinates)

Instead of computing coordinates, write tags into the PDF text where fields
should go, and Sign Sail places them on upload (the literal tag is whited out
before any signer sees it).

Grammar: `{{ type[:sN][:opt][:w=pt][:h=pt] }}`

| tag | becomes |
|---|---|
| `{{sig}}` | Signature, signer 1, required |
| `{{sig:s2}}` | Signature for signer 2 |
| `{{date:s1}}` | Date for signer 1 |
| `{{initials}}` | Initials, signer 1 |
| `{{text:s1:opt}}` | Optional text field |
| `{{    text:s1    }}` | Text field as wide as the padded tag |
| `{{name}} {{email}} {{check}}` | Name / email / checkbox |

After upload, check the document's `lastDetection.warnings` (e.g. an unparsable
tag) before auto-sending — gate on `warnings` being empty.

### Prefill fields & named signers

A field on `recipientRef: 0` belongs to **you, the sender**, not a signer —
fill it before the document goes out (a date, an amount, your own signature). It
never adds a recipient and never blocks completion.

- Give it an optional default in `prefillValue` (text, an ISO date, `"1"` for
  a checked box, or a signature image URL).
- Override per-send with `prefillData` on `POST /envelopes` — a map of
  `placementId → value`. Omit it to use the defaults.

Signer names set when preparing the document carry across sends; the `name` you
pass per recipient on `POST /envelopes` wins for that send and is what the
audit trail records.

## Reliability

- **Idempotency.** Send an `Idempotency-Key` header on `POST /documents` and
  `POST /envelopes`. A replayed request returns the original result with
  `Idempotent-Replayed: true` instead of creating a duplicate. Use a stable key
  per logical action (e.g. one per contract send).
- **Rate limits.** 1000 reads/min and 300 writes/min per key owner. Every
  response carries `X-Request-ID`, `X-RateLimit-Limit`,
  `X-RateLimit-Remaining`, `X-RateLimit-Reset`. On `429`, back off using
  `Retry-After`.
- **Errors.** Every error body is `{ "error": "...", "code": "...", "fields"?: {...} }`.
  Branch on `code`, not the prose. Validation failures add a `fields` map
  (field name → messages).

  | code | when |
  |---|---|
  | `UNAUTHENTICATED` (401) | missing / invalid / revoked key |
  | `PLAN_REQUIRED` (403) | not on the Business plan |
  | `RATE_LIMITED` (429) | too many requests — see Retry-After |
  | `VALIDATION_ERROR` (400) | body/params failed validation (see `fields`) |
  | `INVALID_JSON` (400) | body was not valid JSON |
  | `UNSUPPORTED_MEDIA_TYPE` (415) | bad Content-Type |
  | `PAYLOAD_TOO_LARGE` (413) | PDF/body over the plan cap |
  | `NOT_FOUND` (404) | missing or not owned by the caller |
  | `CONFLICT` (409) | duplicate / idempotency reuse / in-flight |
  | `NOT_READY` (409) | exists but not usable yet (no fields; PDF not done) |
  | `FETCH_FAILED` (400) | couldn't fetch the supplied URL |
  | `INTERNAL` (500) | unexpected server error |

## Webhooks (skip polling)

Subscribe a URL and Sign Sail POSTs lifecycle events: `envelope.sent`,
`recipient.signed`, `recipient.declined`, `envelope.completed`,
`envelope.voided`, `envelope.expired`, and `document.processed`. Verify the
`X-SignSail-Signature: t=<unix_ms>,v1=<hex>` header — `v1` is
HMAC-SHA256 of `"{t}.{rawBody}"` keyed by your webhook secret.

## Endpoint quick reference

| method | path | summary |
|---|---|---|
| GET | `/me` | plan, features, and limits for your key |
| POST | `/documents` | upload a PDF (multipart / url / base64) |
| GET | `/documents` | list documents (filter `status`, `search`; cursor paged) |
| GET | `/documents/{id}` | fetch one (status, templateName, lastDetection) |
| PATCH | `/documents/{id}` | set / clear the template name |
| DELETE | `/documents/{id}` | delete a document |
| GET | `/documents/{id}/fields` | read placements + page geometry |
| PUT | `/documents/{id}/fields` | replace the document's fields |
| POST | `/envelopes` | send a prepared document for signature |
| GET | `/envelopes` | list envelopes (filter `status`; cursor paged) |
| GET | `/envelopes/{id}` | track recipients + status |
| POST | `/envelopes/{id}/void` | cancel an active envelope |
| GET | `/envelopes/{id}/pdf` | download the sealed signed PDF |
| GET / POST | `/contacts` | list or create contacts |
| GET / PATCH / DELETE | `/contacts/{id}` | fetch / update / delete a contact |

## MCP tools

If connected to the MCP server (`https://signsail.com/api/mcp`), these tools mirror the
REST surface one-to-one (they call the same internals, so they never drift):

- `get_me` — plan, features, limits.
- `list_documents` — list/search uploaded PDFs.
- `upload_document` — ingest a PDF from URL or base64.
- `get_document_fields` — read placed fields + page geometry.
- `place_fields` — set the document's fields (replace-all).
- `send_for_signature` — create and send the envelope.
- `get_envelope_status` — per-recipient progress.
- `void_envelope` — cancel an envelope in flight.
- `list_contacts` — read saved contacts.
- `download_signed_pdf` — pull the sealed PDF (base64 + SHA-256) when complete.

## Tips

- Preflight with `GET /me` (or `get_me`) to confirm the plan and limits before
  a batch of work.
- Reuse a document by setting `templateName`, then find it later with
  `GET /documents?search=NDA`.
- Before auto-sending, confirm the document is `ready` and
  `lastDetection.warnings` is empty.
- Full, always-current contract: `https://signsail.com/api/v1/openapi.json`. Human
  reference: `https://signsail.com/docs/api`.
