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 swiftsign2 · 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:
| Prefix | Mode | Behaviour |
|---|---|---|
sk_test_ | Sandbox | Free, watermarked sends. No quota. |
sk_live_ | Live | Real, 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.
| Type | What the signer does |
|---|---|
SIGNATURE | Draws or adopts a signature |
INITIALS | Applies their initials |
NAME | Full name (auto-filled) |
DATE | Signing date (auto-filled) |
TEXT | Free-text input |
CHECKBOX | A single checkable box |
RADIO | One choice from options[] |
DROPDOWN | One choice from options[] |
ATTACHMENT | Uploads 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-ef0123456789Pagination
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=25Error 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"
}| HTTP | code | Meaning |
|---|---|---|
| 400 | validation_error | Body or query failed validation. |
| 401 | unauthorized | Missing or invalid API key. |
| 402 | envelope_quota_exceeded | Monthly live-envelope quota reached. |
| 403 | forbidden | Key lacks the required scope. |
| 404 | envelope_not_found | No envelope with that id for this account. |
| 404 | template_not_found | No template with that id for this account. |
| 409 | invalid_state | Resource not in a valid state for the action. |
| 409 | idempotency_conflict | A request with this key is still processing. |
| 413 | payload_too_large | Request body exceeds the 50 MB limit. |
| 422 | anchor_unresolved | A field anchor was not found in the document. |
| 422 | idempotency_key_reused | Key reused with a different request body. |
| 429 | rate_limited | Too many requests; back off and retry. |
| 503 | billing_unavailable | Billing is temporarily unavailable. |
| 500 | internal_error | Something 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