
# E-sign

Send a document for signature from an API call — no signing-platform account, no per-seat plan. Your agent posts a PDF and a recipient list, each signer gets a hosted signing link, and you pull the completed, signed PDF back through the same API. Built on the open-source [Documenso](https://documenso.com/) signing engine; priced per envelope, not per month.

```bash
curl -X POST https://api.relaystation.ai/v1/esigndoc/envelopes \
  -H 'Authorization: Bearer rs_live_<key>' \
  -H 'Idempotency-Key: offer-letter-jane-20260611' \
  -H 'Content-Type: application/json' \
  -d '{
    "pdf": { "inline": "<base64 pdf>" },
    "title": "Offer letter — Jane Doe",
    "recipients": [ { "email": "jane@example.com", "name": "Jane Doe" } ],
    "autoPlaceSignature": true
  }'
```

The response carries the `envelopeId`, the envelope status, and — once sent — a `signingUrl` per recipient to hand to the human signer.

## The envelope lifecycle

An envelope moves through eight states: `DRAFT → SENT → VIEWED → SIGNED → COMPLETED`, with `REJECTED`, `EXPIRED`, and `CANCELLED` as terminal exits. Create a draft for free, add or adjust signature fields, then send; or create-and-send in one call with `autoPlaceSignature` or explicit `fields`. `GET …/status` re-polls the signing engine live (authoritative, self-healing); registered webhooks deliver `envelope.sent / viewed / signed / completed / rejected / expired / cancelled` as they happen.

## Routing signers — order and approval

Each recipient carries a `role`: `SIGNER` (default), `APPROVER` (must approve, counts toward completion like a signer), `VIEWER` and `CC` (receive the document, don't sign), or `ASSISTANT`. Mixed lists are fine — e.g. two `SIGNER`s plus an `APPROVER`.

By default everyone is invited at once (parallel signing). To route in sequence — each recipient invited only after the prior one acts — set the document-level `signingOrder` to `SEQUENTIAL` and give each recipient a `signingOrder` integer:

```json
{
  "title": "Master agreement",
  "signingOrder": "SEQUENTIAL",
  "recipients": [
    { "email": "signer@example.com",   "name": "Jane Signer",   "role": "SIGNER",   "signingOrder": 1 },
    { "email": "approver@example.com", "name": "Ravi Approver", "role": "APPROVER", "signingOrder": 2 }
  ]
}
```

Omit `signingOrder` (or set it to `PARALLEL`) for the everyone-at-once default; omit a recipient's `signingOrder` and it falls back to its position in the list. Ordering doesn't change the price.

## Billing — charged only when it sends

One price: **$0.10 per envelope sent** (`esign.envelope`). Drafts are free; reads, status polls, cancels, resends, and the signed-document download are free. The charge lands at the moment an envelope is distributed — create-with-send, an explicit send of a draft, or a from-template instantiation with `sendImmediately` — and a sent envelope's charge stands (the work — distribution to signers — is done). If a send fails before anything is distributed, the charge is reversed. Every billable call requires an `Idempotency-Key`; a same-key retry returns the cached result without re-charging.

## The API

PDFs ride the shared input convention: `{ "inline": "<base64>" }` up to 4 MB, or `{ "inputKey": "..." }` from `POST /v1/cputools/upload-url` up to 50 MB. The signed document comes back the same way (inline when small, presigned URL when large).

| Route | What it does | Price |
|---|---|---|
| `POST /v1/esigndoc/envelopes` | Create an envelope from a PDF + recipients; sends immediately when fields/autoPlaceSignature are supplied, else stays DRAFT | $0.10 when it sends; free as a draft |
| `POST /v1/esigndoc/envelopes/{id}/send` | Distribute a DRAFT (DRAFT→SENT) | $0.10 |
| `POST /v1/esigndoc/envelopes/from-template` | Instantiate from a stored template; optional `sendImmediately` | $0.10 when it sends |
| `GET /v1/esigndoc/envelopes` | List your envelopes (paginated, status filter) | free |
| `GET /v1/esigndoc/envelopes/{id}` | Fetch the cached envelope | free |
| `GET /v1/esigndoc/envelopes/{id}/status` | Live status from the signing engine (authoritative) | free |
| `GET /v1/esigndoc/envelopes/{id}/signed-document` | The signed (or original) PDF as the uniform output envelope | free |
| `GET /v1/esigndoc/envelopes/{id}/download` | The PDF as raw binary | free |
| `POST /v1/esigndoc/envelopes/{id}/resend` | Chase recipients who haven't signed | free |
| `POST /v1/esigndoc/envelopes/{id}/fields` · `DELETE …/fields/{fid}` | Add / remove signature fields on a DRAFT | free |
| `DELETE /v1/esigndoc/envelopes/{id}` | Cancel (any state → CANCELLED) | free |
| `POST · GET /v1/esigndoc/templates` · `GET · PUT · DELETE …/templates/{id}` | Manage reusable templates | free |

E-sign's **verification half** lives in cputools: [`POST /v1/pdf/verify-signatures`](https://cputools.relaystation.ai/docs/pdf-tools#verify-signatures) inspects any signed PDF's digital signatures — structural verification + digest intactness, signer identity surfaced (certificate-chain trust is not evaluated — no CA store).

## MCP tools

The same surface is callable over MCP at `https://api.relaystation.ai/mcp`: `esigndoc_create`, `esigndoc_send`, and `esigndoc_get_signed_document` are directly invocable; `esigndoc_from_template`, `esigndoc_get`, `esigndoc_status`, `esigndoc_list`, `esigndoc_cancel`, `esigndoc_resend`, and `esigndoc_list_templates` round out the roster. Same auth, same prices as the HTTP routes.

## Agent-native by design

The originating side is fully programmatic — an agent with an API key (or an x402 wallet) can draft, field, send, poll, and file the signed PDF without a human in its own loop. The *signing* side is deliberately human: each recipient gets a hosted signing URL. A common pattern is an agent that prepares the envelope, hands the `signingUrl` to its human (or the counterparty's), watches `envelope.completed` arrive on a webhook, and files the signed document — one balance, no signing-platform seat.

## Next

[Quickstart](/docs/quickstart) · [Authentication](/docs/authentication) · [Webhooks](/docs/webhooks) · [x402 wire format](/docs/x402) · [API reference](/api-reference)
