Quickstart

Send your first sealed envelope in 5 minutes.

SwiftSign is built to be driven by agents and code, not a dashboard. One unauthenticated call gives you a sandbox key, so an agent or a terminal is productive immediately. The fastest path is the npm SDK below — there is a plain curl version right after it.

1 · Install the SDK

From any project with Node 18+:

npm install swiftsign

2 · Get a sandbox key

No account, no browser. POST /api/v1/signup is the only unauthenticated endpoint — it provisions an account and returns a sk_test_ key on the spot.

import SwiftSign from "swiftsign";

// One call, no auth — returns a sandbox key.
const { api_key } = await SwiftSign.signup({ email: "dev@acme.com" });

const ss = new SwiftSign({ apiKey: api_key }); // sk_test_…

3 · Send a sandbox envelope

Sandbox sends are free and watermarked. Pass the PDF as base64, drop a signature field where you want it, then send.

import { readFileSync } from "node:fs";

const env = await ss.envelopes.create({
  subject: "Mutual NDA for countersignature",
  documents: [
    { name: "nda.pdf", base64: readFileSync("nda.pdf").toString("base64") },
  ],
  recipients: [{ name: "Steve Park", email: "steve@acme.com" }],
  fields: [
    { recipientIndex: 0, document: 0, type: "SIGNATURE", anchor: "Party B — Signature" },
  ],
});

// Envelope is created as DRAFT — send it.
await ss.envelopes.send(env.id); // → { status: "sent" }

Steve gets an email with a signing link. When he signs, SwiftSign seals the PDF and produces a Certificate of Completion.

4 · Go live

Sandbox is free forever. To send real, un-watermarked envelopes, verify your email and add a card. POST /api/v1/billing/upgrade returns a Stripe Checkout URL.

const { checkout_url } = await ss.billing.upgrade({ plan: "PRO" });
// Open checkout_url, pay, then mint a sk_live_ key from the dashboard.

The same flow in curl

No SDK required — every SDK method is a thin wrapper over the REST API.

# 1. Get a sandbox key (no auth)
curl -s https://swiftsign.ca/api/v1/signup \
  -H "Content-Type: application/json" \
  -d '{"email":"dev@acme.com"}'
# → { "api_key": "sk_test_…", "mode": "test", ... }

# 2. Create an envelope (base64 the PDF first)
curl -s https://swiftsign.ca/api/v1/envelopes \
  -H "Authorization: Bearer sk_test_…" \
  -H "Content-Type: application/json" \
  -d '{
    "subject": "Mutual NDA",
    "documents": [{ "name": "nda.pdf", "base64": "JVBERi0…" }],
    "recipients": [{ "name": "Steve Park", "email": "steve@acme.com" }],
    "fields": [{ "recipientIndex": 0, "document": 0, "type": "SIGNATURE", "anchor": "Party B — Signature" }]
  }'
# → { "id": "…", "status": "DRAFT", ... }

# 3. Send it
curl -s https://swiftsign.ca/api/v1/envelopes/ENV_ID \
  -H "Authorization: Bearer sk_test_…" \
  -H "Content-Type: application/json" \
  -d '{"action":"send"}'

Authentication

Every endpoint except POST /api/v1/signup requires a Bearer API key in the Authorization header.

Authorization: Bearer sk_live_abc123…

Two key modes, distinguished by prefix:

PrefixModeBehaviour
sk_test_SandboxFree, watermarked sends. No quota.
sk_live_LiveReal, sealed sends. Counts against your plan quota.

Field types

Each field is placed by explicit x/y percentages (0–100, top-left origin) or by an anchor string SwiftSign finds in the PDF text.

TypeWhat the signer does
SIGNATUREDraws or adopts a signature
INITIALSApplies their initials
NAMEFull name (auto-filled)
DATESigning date (auto-filled)
TEXTFree-text input
CHECKBOXA single checkable box
RADIOOne choice from options[]
DROPDOWNOne choice from options[]
ATTACHMENTUploads a supporting file

Idempotency

Send an Idempotency-Key header on POST /api/v1/envelopes to make creates safe to retry. A repeated request with the same key and the same body replays the original response instead of creating a second envelope. Reusing a key with a different body returns 422 idempotency_key_reused.

Idempotency-Key: a1b2c3d4-e5f6-7890-abcd-ef0123456789

Pagination

List endpoints (GET /api/v1/envelopes, GET /api/v1/templates) use opaque cursor pagination. Pass limit (default 25, max 100); follow next_cursor until has_more is false.

{
  "data": [ /* … */ ],
  "has_more": true,
  "next_cursor": "eyJjIjoiMjAyNi0wMy0xNFQ…"
}

// next page:
GET /api/v1/envelopes?cursor=eyJjIjoiMjAyNi0wMy0xNFQ…&limit=25

Error codes

Errors are RFC 9457 problem documents served as application/problem+json. Branch on the machine-readable code, not on title. Every error carries a request_id for support.

{
  "type": "https://swiftsign.ca/errors/validation_error",
  "title": "Request validation failed",
  "status": 400,
  "code": "validation_error",
  "detail": "A valid email is required",
  "request_id": "req_8c2f9a1b3d4e5f6071829304"
}
HTTPcodeMeaning
400validation_errorBody or query failed validation.
401unauthorizedMissing or invalid API key.
402envelope_quota_exceededMonthly live-envelope quota reached.
403forbiddenKey lacks the required scope.
404envelope_not_foundNo envelope with that id for this account.
404template_not_foundNo template with that id for this account.
409invalid_stateResource not in a valid state for the action.
409idempotency_conflictA request with this key is still processing.
413payload_too_largeRequest body exceeds the 50 MB limit.
422anchor_unresolvedA field anchor was not found in the document.
422idempotency_key_reusedKey reused with a different request body.
429rate_limitedToo many requests; back off and retry.
503billing_unavailableBilling is temporarily unavailable.
500internal_errorSomething broke on our side.

Every endpoint, request body, and response is documented in the interactive API explorer — try calls right in the browser.

Open the API explorer