> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.cloudraker.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.cloudraker.com/_mcp/server.

# Sign

`POST /v1/sign` creates a signature envelope for one PDF. Each signer verifies their email with a one-time code. Each signer signs by typing their name. When the last signer completes, CloudRaker adds a signature certificate and a tamper-evident cryptographic seal. The sealed document comes back as a new file.

## How it works

1. You send a **file** and one or more **signers** (`name` + `email`).
2. CloudRaker creates the envelope and emails each signer their own signing link. The call returns `202` with `status: "needs_input"`. Signing is never synchronous.
3. The run stays at `needs_input` while signatures come in. Poll `GET /v1/runs/:id`, watch the [envelope](#track-the-envelope), or subscribe a [webhook](/paperwork/developers/webhooks).
4. When all signers complete, CloudRaker seals the document. The run reaches `processed` with `output.file` and `output.auditUrl`.

Sign runs are **exempt from the run TTL**. An envelope must outlive the 7-day maximum, so CloudRaker never purges an open envelope. The `expiresAt` field is still present on the run body. For a sign run, the API does not enforce it. The envelope has its own expiry, visible on `GET /v1/runs/:id/envelope`.

## Quickstart

```bash title="curl"
curl -X POST https://api.cloudraker.com/v1/sign \
  -H "Authorization: Bearer $CLOUDRAKER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "file": { "id": "a04d6597-4e34-4a99-94ea-964c289a4c68" },
    "signers": [{ "name": "Jane Doe", "email": "jane@example.com" }],
    "message": "Please sign the master services agreement.",
    "placement": "page"
  }'
```

```ts title="TypeScript"
const res = await fetch("https://api.cloudraker.com/v1/sign", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.CLOUDRAKER_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    file: { id: "a04d6597-4e34-4a99-94ea-964c289a4c68" },
    signers: [{ name: "Jane Doe", email: "jane@example.com" }],
    message: "Please sign the master services agreement.",
  }),
});

const run = await res.json(); // 202 — status: "needs_input"
console.log(run.id, run.statusUrl);
```

```python title="Python"
import os, requests

res = requests.post(
    "https://api.cloudraker.com/v1/sign",
    headers={"Authorization": f"Bearer {os.environ['CLOUDRAKER_API_KEY']}"},
    json={
        "file": {"id": "a04d6597-4e34-4a99-94ea-964c289a4c68"},
        "signers": [{"name": "Jane Doe", "email": "jane@example.com"}],
        "message": "Please sign the master services agreement.",
    },
)

run = res.json()  # 202 — status: "needs_input"
print(run["id"], run["statusUrl"])
```

The TypeScript and Python samples use plain HTTP, so they run with no installed packages. This endpoint is also a top-level method on both [SDKs](/paperwork/developers/sdks) as of 0.3.0: `client.sign(…)`. That page has a full worked example.

## Example response

```json
{
  "object": "sign_run",
  "id": "sgr_01KYD7A0QEMDJ7YKCNMAXFD229",
  "status": "needs_input",
  "statusUrl": "/v1/runs/sgr_01KYD7A0QEMDJ7YKCNMAXFD229",
  "envelopeUrl": "/v1/runs/sgr_01KYD7A0QEMDJ7YKCNMAXFD229/envelope"
}
```

`envelopeUrl` appears on every sign response, the `202` and each `GET /v1/runs/:id`. It points at [the envelope](#track-the-envelope), the sender's view of who has signed. A signing link is a bearer capability that belongs to one signer. CloudRaker emails the link to that signer and never returns it to you.

When every signer completes, `GET /v1/runs/:id` reaches `processed`. Then `output` carries:

| Field               | What it is                                                                  |
| ------------------- | --------------------------------------------------------------------------- |
| `output.file`       | The sealed PDF: `{id, name, url}`. Also at `GET /v1/runs/:id/output/:name`. |
| `output.envelopeId` | The envelope this run produced.                                             |
| `output.signers[]`  | `{name, email, signedAt}` per signer.                                       |
| `output.auditUrl`   | The machine-readable audit trail, also attached inside the sealed PDF.      |

## Configuration

| Field       | Type                                  | What it does                                                                                                                                                             |
| ----------- | ------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `file`      | `{url, name?, processing?}` or `{id}` | **Required.** The PDF to be signed.                                                                                                                                      |
| `signers[]` | array of `{name, email}`, 1–50        | **Required.** Everyone is invited at once — nobody waits their turn. Array position sets each signer's `position`, and with `placement: "tags"`, which marker they sign. |
| `message`   | string, ≤ 2000 chars                  | Shown to signers in the invitation email.                                                                                                                                |
| `placement` | `page` \| `tags`                      | Where the stamps go — see [Placing the signatures](#placing-the-signatures). `page` is the default.                                                                      |
| `metadata`  | object                                | Your own key/values. The API echoes them back on the run body, not on webhook deliveries. Max 10 KB.                                                                     |
| `webhook`   | `{url}` or `{id}`                     | Where to deliver terminal events. See [Webhooks](/paperwork/developers/webhooks).                                                                                        |
| `ttl`       | integer seconds, 1–604800             | The API accepts it for uniformity but does not enforce it while the envelope is open.                                                                                    |

## Placing the signatures

`page` (the default) appends an **Electronic Signatures** page and puts every stamp on it. Nothing needs preparing in the document.

`tags` puts each signer's stamp on a literal `[Signature N]` in the document text. `signers[0]` signs `[Signature 1]`, `signers[1]` signs `[Signature 2]`, and so on — matched by position, never by name or email. Every signer needs a marker. If one is missing the run fails before anyone is invited, with `error.code: "signature_tags_missing"`.

A marker sets where a stamp goes, not how big it is. Each stamp grows upward into whatever space is free above it, so leave about 88pt clear of other text and form fields for a full-size stamp — a marker tucked under other content gets a smaller one.

Both placements append the audit pages, so `tags` gives you in-place signatures and the trail.

## Track the envelope

Four sub-routes hang off the run id.

### `GET /v1/runs/:id/envelope`

The sender's view: envelope status, the signer roster with per-signer state, and the event trail.

```json
{
  "envelope": {
    "id": "5f791fda-7120-42f3-bed0-fd6d2589daca",
    "docName": "msa.pdf",
    "docSha256": "2d420cbb4123dcf1fb82595b2359cfbb5d81f00b9df9d359fcc7af361d093f53",
    "docSize": 140815,
    "status": "pending",
    "signatureMode": "page",
    "message": "Please sign the master services agreement.",
    "outputFileId": null,
    "createdAt": "2026-07-25T18:06:34.000Z",
    "completedAt": null,
    "expiresAt": "2026-08-01T18:06:34.000Z"
  },
  "signers": [
    {
      "id": "a8a1ebf8-79d8-4124-9183-33d2c8f4b61e",
      "name": "Jane Doe",
      "email": "jane@example.com",
      "position": 1,
      "status": "pending",
      "emailVerifiedAt": null,
      "signedAt": null,
      "lastInvitedAt": "2026-07-25T18:06:34.000Z"
    }
  ],
  "events": [
    { "id": 55, "type": "created", "actor": "system", "occurredAt": "2026-07-25T18:06:34.000Z" },
    { "id": 56, "type": "invited", "actor": "Jane Doe", "signerId": "a8a1ebf8-79d8-4124-9183-33d2c8f4b61e", "occurredAt": "2026-07-25T18:06:37.000Z" }
  ]
}
```

The envelope **never contains signing links**. A signing link is a bearer capability that only its signer holds. To get a signer moving again, re-send their invitation.

### `POST /v1/runs/:id/signers/:signerId/resend`

Emails a pending signer a **new** signing link. Their previous link stops working immediately. Returns `409` if that signer already signed or if the envelope is no longer pending.

### `POST /v1/runs/:id/void`

Cancels a pending envelope. Every signing link dies at once, and the run terminates as failed. The body accepts an optional `{ "reason": "…" }`. Returns `409` when the envelope is finalizing or completed.

```bash
curl -X POST https://api.cloudraker.com/v1/runs/sgr_01KYD7A0QEMDJ7YKCNMAXFD229/void \
  -H "Authorization: Bearer $CLOUDRAKER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "reason": "Superseded by a new draft" }'
```

### `GET /v1/runs/:id/audit`

Returns the audit trail as JSON: creation, invitations, email verifications, signatures, and the seal. It is available before the envelope completes. The sealed PDF carries the same document as an attachment.

## Sync vs async

Sign is always asynchronous. `POST /v1/sign` answers `202` for any `?wait=` value, because a sign run cannot reach a terminal status inside the window. With the default wait, the call holds until the envelope exists and reports `status: "needs_input"`. With `?wait=0`, the call returns immediately with `status: "queued"`. Poll `GET /v1/runs/:id?wait=…`, watch the envelope, or subscribe `run.completed` on a [webhook endpoint](/paperwork/developers/webhooks).

## Save as a config

Sign is the one capability with **no config library** and **no `action` reference in its request body**. `POST /v1/sign` and `{"sign": …}` [pipeline](/paperwork/capabilities/pipeline) steps always run the built-in signing action. They take their whole configuration inline. Send `message` and `placement` on every call.

There is no `/v1/sign/configs`. `POST /v1/actions` accepts `"capability": "sign"`, so you can create and list a saved sign action. But today no `/v1` request path consumes one. The API rejects a saved sign action as another verb's `action` with `400 invalid_request`. The same applies to a bare `{"action": …}` pipeline step. Only the sign path keeps the run exempt from the TTL purge.

## Next steps

#### [Fill](/capabilities/fill)

Complete the form before you send it out for signature.

#### [Runs](/developers/runs)

Statuses, downloading the sealed document, and keeping the result.

#### [Webhooks](/developers/webhooks)

Get a notification the moment the last signer completes.

#### [Pipelines](/capabilities/pipeline)

Redact and sign the same document in one call.