Developers
The API the incumbents taught you to expect
Everything the app does when sending — envelopes, parties, templates, bulk send, tracking, the evidence ledger — under your own API key, against your organization's own instance. One thing no key can do: sign. The ceremony belongs to each party through their own link, because a signature your integration could fake would prove nothing.
Authentication
A sign_admin mints keys in the consumer console (Sign settings → Integrator API keys). The key is shown once, stored only as a salted hash, and revocable per key. Send it as a bearer token; the base URL is your instance’s origin.
curl https://YOUR-INSTANCE/api/sign/v1/envelopes \
-H "Authorization: Bearer sgk_..." \
-H "Content-Type: application/json" \
-d '{"title": "Mutual NDA"}'Requests and responses are JSON; results arrive under a data key and errors as { error: { message } } with the usual status codes. An unknown key and a revoked key get the same refusal — a key that enumerates is a key that leaks.
Endpoints
All under /api/sign/v1. Sending an envelope through the API is billed exactly like sending it from the app — one envelope, one charge, whatever the door.
| Method | Path | What it does |
|---|---|---|
| POST | /envelopes | Create a draft envelope |
| GET | /envelopes | List envelopes, filterable by status |
| GET | /envelopes/{id} | One envelope: parties, documents, progress |
| POST | /envelopes/{id}/documents | Attach a document (base64; sha-256 pinned on entry) |
| POST | /envelopes/{id}/parties | Add a signer, approver, witness or cc party |
| POST | /envelopes/{id}/fields | Place a field for a party on a document |
| POST | /envelopes/{id}/send | Freeze documents, mint links, notify the first parties |
| POST | /envelopes/{id}/remind | Chase whoever’s turn it is (evidence-logged) |
| POST | /envelopes/{id}/void | Void, with a reason that joins the record |
| POST | /envelopes/{id}/documents/{docId}/revise | New version; voids exactly the signatures it invalidates |
| GET | /envelopes/{id}/events | The evidence ledger, as the tracking screen reads it |
| GET | /envelopes/{id}/comments | The comment thread |
| POST | /envelopes/{id}/comments | Add a comment |
| GET | /templates | List reusable templates |
| POST | /templates/{id}/use | Make an envelope from a template |
| POST | /templates/{id}/bulk | One envelope per row, up to 100, refused whole on a bad row |
| GET | /verify/{digest} | Public verification: valid, tampered or unknown |
The SDK
@rutba/sign-sdk is a dependency-free Node client — every method maps to exactly one endpoint above, so an integration is readable in one sitting and owns no transitive risk.
const { SignClient } = require('@rutba/sign-sdk');
const sign = new SignClient({ baseUrl: 'https://YOUR-INSTANCE', key: process.env.SIGN_API_KEY });
const draft = await sign.createEnvelope({ title: 'Mutual NDA' });
await sign.addDocument(draft.documentId, { name: 'nda.pdf', content: pdfBuffer });
await sign.addParty(draft.documentId, { role: 'signer', name: 'Ada Osei', email: 'ada@example.com' });
await sign.send(draft.documentId);Webhooks
Set a URL and a signing secret in the console and every envelope event — sign.envelope.*, sign.party.*, sign.document.* — is POSTed to it, at-least-once with retries. Each delivery is signed with an HMAC over `${timestamp}.${body}`; verify it constant-time and timestamp-bounded, or let the SDK do it:
// X-Rutba-Sign-Signature: t=1756800000000,v1=<hex hmac>
const { verifyWebhook } = require('@rutba/sign-sdk');
app.post('/webhooks/rutba-sign', (req, res) => {
const ok = verifyWebhook({
body: req.rawBody, // the RAW body string
signature: req.get('x-rutba-sign-signature'),
secret: process.env.SIGN_WEBHOOK_SECRET, // set beside the URL in the console
});
if (!ok) return res.status(400).end();
const { event, envelopeId, occurredAt, payload } = JSON.parse(req.rawBody);
// sign.envelope.sent | sign.envelope.completed | sign.party.signed | ...
res.status(200).end(); // non-2xx is retried
});Verification, without trusting us
GET /verify/{digest} answers valid, tampered or unknown for any sealed record, with no key and no account. The sealed manifest is an Ed25519 JWS over the whole evidence chain, countersigned by Rutba with an RFC 3161 timestamp — so a verifier can check the mathematics without asking anyone’s permission, including ours.
Send your first envelope from code
Create an account, mint a key in the console, and the curl above works as written.