Sign

Send a PDF out for e-signature, track the envelope, and get a sealed document with an audit trail.

View as Markdown

POST /v1/sign creates a signature envelope for one PDF. Each signer verifies their email with a one-time code and signs by typing their name. When the last signer completes, the document gets a signature certificate and a tamper-evident cryptographic seal, and 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, or subscribe a webhook.
  4. Once everyone has signed, the document is sealed and 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 it is never purged while it is open. The expiresAt field is still present on the run body; for a sign run it is not enforced. The envelope has its own expiry, visible on GET /v1/runs/:id/envelope.

Quickstart

$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": "[email protected]" }],
> "message": "Please sign the master services agreement.",
> "placement": "page"
> }'

The TypeScript and Python samples are plain HTTP so they run with nothing installed. This endpoint is also a top-level method on both SDKs as of 0.3.0 — client.sign(…); that page has a full worked example.

Example response

1{
2 "object": "sign_run",
3 "id": "sgr_01KYD7A0QEMDJ7YKCNMAXFD229",
4 "status": "needs_input",
5 "statusUrl": "/v1/runs/sgr_01KYD7A0QEMDJ7YKCNMAXFD229",
6 "envelopeUrl": "/v1/runs/sgr_01KYD7A0QEMDJ7YKCNMAXFD229/envelope"
7}

envelopeUrl is on every sign response — the 202 and each GET /v1/runs/:id — and points at the envelope, the sender’s view of who has signed. A sign run never carries a tasks[] array the way a fill review does: a signing link is a bearer capability belonging to one signer, so it is emailed to them and never returned to you.

Once every signer has completed, GET /v1/runs/:id reaches processed and output carries:

FieldWhat it is
output.fileThe sealed PDF — {id, name, url}. Also at GET /v1/runs/:id/output/:name.
output.envelopeIdThe envelope this run produced.
output.signers[]{name, email, signedAt} per signer.
output.auditUrlThe machine-readable audit trail, also attached inside the sealed PDF.

Configuration

FieldTypeWhat it does
file{url, name?, processing?} or {id}Required. The PDF to be signed.
signers[]array of {name, email}, 1–50Required. Signing order follows the array.
messagestring, ≤ 2000 charsShown to signers in the invitation email.
placementpage | tagspage (default) appends a signature certificate page. tags puts each signer’s stamp where a [Signature N] placeholder already sits in the document — every signer needs one or the run fails.
metadataobjectYour own key/values, echoed back on the run body (not on webhook deliveries). Max 10 KB.
webhook{url} or {id}Where to deliver terminal events. See Webhooks.
ttlinteger seconds, 1–604800Accepted for uniformity, but not enforced while the envelope is open.

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.

1{
2 "envelope": {
3 "id": "5f791fda-7120-42f3-bed0-fd6d2589daca",
4 "docName": "msa.pdf",
5 "docSha256": "2d420cbb4123dcf1fb82595b2359cfbb5d81f00b9df9d359fcc7af361d093f53",
6 "docSize": 140815,
7 "status": "pending",
8 "signatureMode": "page",
9 "message": "Please sign the master services agreement.",
10 "outputFileId": null,
11 "createdAt": "2026-07-25T18:06:34.000Z",
12 "completedAt": null,
13 "expiresAt": "2026-08-01T18:06:34.000Z"
14 },
15 "signers": [
16 {
17 "id": "a8a1ebf8-79d8-4124-9183-33d2c8f4b61e",
18 "name": "Jane Doe",
19 "email": "[email protected]",
20 "position": 1,
21 "status": "pending",
22 "emailVerifiedAt": null,
23 "signedAt": null,
24 "lastInvitedAt": "2026-07-25T18:06:34.000Z"
25 }
26 ],
27 "events": [
28 { "id": 55, "type": "created", "actor": "system", "occurredAt": "2026-07-25T18:06:34.000Z" },
29 { "id": 56, "type": "invited", "actor": "Jane Doe", "signerId": "a8a1ebf8-79d8-4124-9183-33d2c8f4b61e", "occurredAt": "2026-07-25T18:06:37.000Z" }
30 ]
31}

The envelope never contains signing links. A signing link is a bearer capability held only by its signer — 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. 409 if that signer already signed or 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. Optional body { "reason": "…" }. 409 once the envelope is finalizing or completed.

$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

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

Sync vs async

Always asynchronous — POST /v1/sign answers 202 whatever you pass for ?wait=, because a sign run cannot reach a terminal status inside the window. With the default wait it holds until the envelope exists and reports status: "needs_input"; with ?wait=0 it returns immediately with status: "queued". Poll GET /v1/runs/:id?wait=…, watch the envelope, or subscribe run.completed on a webhook endpoint.

Save as an action

Sign is the one capability with no action reference in its request body. POST /v1/sign and {"sign": …} pipeline steps always run the built-in signing action, and they take their whole configuration inline — send message and placement on every call.

POST /v1/actions does accept "capability": "sign", so a saved sign action can be created and listed, but today no /v1 request path consumes one. Passing a saved sign action as another verb’s action — or as a bare {"action": …} pipeline step — is rejected with 400 invalid_request, because only the sign path keeps the run exempt from the TTL purge.

Next steps