Rutba Sign

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.

MethodPathWhat it does
POST/envelopesCreate a draft envelope
GET/envelopesList envelopes, filterable by status
GET/envelopes/{id}One envelope: parties, documents, progress
POST/envelopes/{id}/documentsAttach a document (base64; sha-256 pinned on entry)
POST/envelopes/{id}/partiesAdd a signer, approver, witness or cc party
POST/envelopes/{id}/fieldsPlace a field for a party on a document
POST/envelopes/{id}/sendFreeze documents, mint links, notify the first parties
POST/envelopes/{id}/remindChase whoever’s turn it is (evidence-logged)
POST/envelopes/{id}/voidVoid, with a reason that joins the record
POST/envelopes/{id}/documents/{docId}/reviseNew version; voids exactly the signatures it invalidates
GET/envelopes/{id}/eventsThe evidence ledger, as the tracking screen reads it
GET/envelopes/{id}/commentsThe comment thread
POST/envelopes/{id}/commentsAdd a comment
GET/templatesList reusable templates
POST/templates/{id}/useMake an envelope from a template
POST/templates/{id}/bulkOne 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.