Sign

Send a PDF for e-signature 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. 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, or subscribe a webhook.
  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

$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 use plain HTTP, so they run with no installed packages. 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 appears on every sign response, the 202 and each GET /v1/runs/:id. It points at 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:

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. Everyone is invited at once — nobody waits their turn. Array position sets each signer’s position, and with placement: "tags", which marker they sign.
messagestring, ≤ 2000 charsShown to signers in the invitation email.
placementpage | tagsWhere the stamps go — see Placing the signatures. page is the default.
metadataobjectYour 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.
ttlinteger seconds, 1–604800The 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.

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 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.

$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.

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 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