API Documentation
Create, manage, and retrieve NDAs programmatically with the SwiftNDA REST API. Designed for developers and LLM agents.
Signing Flow
The NDA signing process follows a specific lifecycle. Understanding this flow is essential for API consumers who want to automate NDA creation and tracking.
Create NDA via API
POST /api/v1/ndas — The response includes senderSigningUrl and recipientSigningUrl. NDA status is draft.
Author signs via senderSigningUrl
The NDA creator opens the senderSigningUrl in a browser and signs the NDA. They must be authenticated as the NDA owner. NDA status changes to awaiting_client.
Share recipientSigningUrl with recipient
Send the recipientSigningUrl directly to the recipient via email, chat, or any channel. No account is required — anyone with the link can sign.
Recipient signs the NDA
The recipient opens the link and signs. No account required. NDA status changes to fully_signed.
Final PDF generated
Once both parties have signed, the final NDA PDF (with signatures) and an audit trail PDF are automatically generated. Download via GET /api/v1/ndas/:id/pdf.
Note: If the recipient visits their signing link before the sender has signed, they will see a "waiting for author" message. The sender must sign first.
Getting Started
The SwiftNDA API allows you to create, list, and download NDAs programmatically. All API requests require authentication via an API key.
Machine-readable OpenAPI spec
Prefer a machine contract over this page? The whole API is described by an OpenAPI 3.1 document — point your client generator, codegen, or LLM agent straight at it.
llms.txt is a short orientation file for LLM agents (base URL, auth, and the two quota rules).
1. Get an API Key
Sign in to your SwiftNDA account and go to your Profile page. Scroll to the “API Keys” section and create a new key. Copy it immediately — it won't be shown again.
2. Make Your First Request
Verify your API key by fetching your profile:
curl -H "Authorization: Bearer YOUR_API_KEY" \
https://swiftnda.com/api/v1/me3. List Your NDAs
Retrieve all your NDAs with pagination:
curl -H "Authorization: Bearer YOUR_API_KEY" \
https://swiftnda.com/api/v1/ndas?page=1&pageSize=104. Create an NDA
Create a new NDA programmatically (free — drafts never consume quota):
curl -X POST -H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"recipientCompany": "Partner Inc",
"purpose": "Discuss potential partnership",
"ndaTermYears": 2,
"confidentialityTermYears": 3,
"senderPrintName": "Jane Smith",
"senderTitle": "CEO",
"senderCompany": "Acme Corp",
"senderAddressCountry": "US",
"senderAddressState": "CA",
"senderAddressCity": "San Francisco",
"senderAddressZip": "94105",
"senderAddressStreet": "123 Market St",
"governingLaw": "California",
"jurisdiction": "San Francisco County, California",
"creationType": "quick"
}' \
https://swiftnda.com/api/v1/ndasAuthentication
All API requests must include your API key in the Authorization header using the Bearer token scheme.
Authorization: Bearer snda_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxAs an alternative to the Bearer scheme, you can send the key in an X-API-Key header.
Keep your API key secret
Never expose your API key in client-side code, public repositories, or browser requests. Always make API calls from your server or a secure environment. You can create up to 5 API keys per account and revoke them at any time from the dashboard (Dashboard → API Keys).
Base URL
https://swiftnda.com/api/v1Endpoints
The SwiftNDA API provides 5 endpoints for managing your NDAs and account.
/api/v1/meGET/api/v1/ndasPOST/api/v1/ndasGET/api/v1/ndas/:idPATCH/api/v1/ndas/:idDELETE/api/v1/ndas/:idGET/api/v1/ndas/:id/statusGET/api/v1/ndas/:id/signaturesPOST/api/v1/ndas/:id/sendPOST/api/v1/ndas/:id/executeGET/api/v1/ndas/:id/pdfGET/api/v1/ndas/:id/audit-pdfPOST/api/v1/webhooksGET/api/v1/webhooksPATCH/api/v1/webhooks/:idPOST/api/v1/webhooks/:id/testGET/api/v1/me
Returns the authenticated user's profile, API key metadata, and the account's subscription & quota. Useful for verifying your API key is valid and for checking remaining quota before a sender signs (quota is consumed only on the sender signature). subscription is null when the account has no subscription; remaining is null on unlimited plans.
Example Request
curl -H "Authorization: Bearer YOUR_API_KEY" \
https://swiftnda.com/api/v1/meExample Response
{
"success": true,
"user": {
"id": "user_abc123",
"name": "Jane Smith",
"email": "jane@example.com",
"senderCompany": "Acme Corp",
"image": "https://lh3.googleusercontent.com/..."
},
"apiKey": {
"id": "key_xyz789",
"name": "My Production Key"
},
"subscription": {
"planId": "plan_pro_monthly",
"planName": "Pro",
"quotaPeriod": "monthly",
"ndaQuota": 50,
"remaining": 47,
"creditBalance": 0,
"periodEnd": "2026-09-01T00:00:00.000Z"
}
}GET/api/v1/ndas
Returns a paginated list of your NDAs. Sorting and filtering are explicit: the pagination block echoes the effective sortBy / sortOrder. Defaults to most recently updated first. Invalid sortBy/sortOrder or a malformed date returns 422.
Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
| page | number | 1 | Page number |
| pageSize | number | 20 | Items per page (1-100) |
| status | string | — | Filter: draft, awaiting_client, fully_signed |
| search | string | — | Search company names, descriptions |
| sortBy | string | updatedAt | createdAt, updatedAt, status, recipientCompany |
| sortOrder | string | desc | asc or desc |
| createdAfter | string | — | ISO 8601: only NDAs created at/after |
| createdBefore | string | — | ISO 8601: only NDAs created at/before |
Example Request
curl -H "Authorization: Bearer YOUR_API_KEY" \
"https://swiftnda.com/api/v1/ndas?page=1&pageSize=10&status=draft"Example Response
{
"success": true,
"ndas": [
{
"id": "nda_abc123-def456-...",
"status": "draft",
"senderCompany": "Acme Corp",
"recipientCompany": "Partner Inc",
"projectDescription": "API integration project",
"createdAt": "2026-01-15T10:30:00.000Z",
"updatedAt": "2026-01-15T10:30:00.000Z"
}
],
"pagination": {
"page": 1,
"pageSize": 10,
"total": 42,
"totalPages": 5,
"sortBy": "updatedAt",
"sortOrder": "desc"
}
}POST/api/v1/ndas
Creates a new NDA programmatically. Creation is free: drafts never consume quota. 1 NDA is consumed from your plan when the sender signs it (via the signing URLs in the response); the counterparty signs for free. A draft PDF is generated automatically. Pass an Idempotency-Key header to make retries safe — see Retries & Idempotency.
Request Body (JSON)
| Field | Type | Required | Description |
|---|---|---|---|
| recipientCompany | string | Yes | Recipient company name (max 100) |
| projectDescription | string | No | Project description (max 500) |
| purpose | string | Yes | Purpose of the NDA (max 300) |
| ndaTermYears | number | Yes | NDA duration in years (1-10) |
| confidentialityTermYears | number | Yes | Confidentiality period (2-20, must be ≥ ndaTermYears) |
| senderPrintName | string | Yes | Your full name (max 100) |
| senderTitle | string | Yes | Your job title (max 100) |
| senderCompany | string | Yes | Your company name (max 100) |
| senderAddressCountry | string | Yes | Country code (e.g., "US") |
| senderAddressState | string | No | State/Province |
| senderAddressCity | string | Yes | City (max 100) |
| senderAddressZip | string | Yes | ZIP/Postal code (max 20) |
| senderAddressStreet | string | Yes | Street address (max 200) |
| senderAddressSuite | string | No | Suite/Apt number (max 50) |
| senderPhone | string | No | Phone number (international format) |
| governingLaw | string | Yes | Governing law (e.g., "California") |
| jurisdiction | string | Yes | Legal jurisdiction |
| creationType | string | Yes | "quick" or "detailed" |
Example Request
curl -X POST \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"recipientCompany": "Partner Inc",
"projectDescription": "API integration project",
"purpose": "Discuss potential partnership and shared technology",
"ndaTermYears": 2,
"confidentialityTermYears": 3,
"senderPrintName": "Jane Smith",
"senderTitle": "CEO",
"senderCompany": "Acme Corp",
"senderAddressCountry": "US",
"senderAddressState": "CA",
"senderAddressCity": "San Francisco",
"senderAddressZip": "94105",
"senderAddressStreet": "123 Market St",
"senderAddressSuite": "Suite 400",
"senderPhone": "+14155551234",
"governingLaw": "California",
"jurisdiction": "San Francisco County, California",
"creationType": "quick"
}' \
https://swiftnda.com/api/v1/ndasExample Response (201 Created)
{
"success": true,
"nda": {
"id": "nda_a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"status": "draft",
"senderCompany": "Acme Corp",
"recipientCompany": "Partner Inc",
"projectDescription": "API integration project",
"creationType": "quick",
"pdfUrl": "/storage/pdfs/nda_a1b2c3d4-...",
"createdAt": "2026-01-15T10:30:00.000Z",
"updatedAt": "2026-01-15T10:30:00.000Z",
"senderSigningUrl": "https://swiftnda.com/ndas/nda_a1b2c3d4-.../sign?role=sender",
"recipientSigningUrl": "https://swiftnda.com/ndas/nda_a1b2c3d4-.../sign?role=recipient"
}
}Signing URL Notes
senderSigningUrl— The URL for the NDA author to sign. Requires authentication as the NDA owner.recipientSigningUrl— The URL to share with the recipient. No account required — anyone with the link can sign.- If the sender hasn't signed yet, the recipient will see a "waiting for author" message when visiting their signing link.
Validation Error Response (422)
{
"success": false,
"error": "Validation failed",
"code": "VALIDATION_FAILED",
"details": [
{ "field": "recipientCompany", "message": "Recipient company name is required" },
{ "field": "purpose", "message": "Purpose is required" }
]
}GET/api/v1/ndas/:id
Returns full details of a specific NDA including all fields, signatures, and status. Only returns NDAs you own.
Example Request
curl -H "Authorization: Bearer YOUR_API_KEY" \
https://swiftnda.com/api/v1/ndas/nda_a1b2c3d4-e5f6-7890-abcd-ef1234567890Example Response
{
"success": true,
"nda": {
"id": "nda_a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"status": "fully_signed",
"senderCompany": "Acme Corp",
"recipientCompany": "Partner Inc",
"projectDescription": "API integration project",
"duration": 2,
"pdfUrl": "/storage/pdfs/nda_a1b2c3d4-...",
"recipientEmail": "client@partner.com",
"emailSentAt": "2026-01-15T11:00:00.000Z",
"recipientSignedAt": "2026-01-16T09:00:00.000Z",
"senderSignedAt": "2026-01-15T10:35:00.000Z",
"createdAt": "2026-01-15T10:30:00.000Z",
"updatedAt": "2026-01-16T09:00:00.000Z",
"purpose": "Discuss potential partnership",
"ndaTermYears": 2,
"confidentialityTermYears": 3,
"creationType": "quick",
"senderPrintName": "Jane Smith",
"senderTitle": "CEO",
"senderCompany": "Acme Corp",
"senderAddressCountry": "US",
"senderAddressState": "CA",
"senderAddressCity": "San Francisco",
"senderAddressZip": "94105",
"senderAddressStreet": "123 Market St",
"governingLaw": "California",
"jurisdiction": "San Francisco County, California",
"senderSigningUrl": "https://swiftnda.com/ndas/nda_a1b2c3d4-.../sign?role=sender",
"recipientSigningUrl": "https://swiftnda.com/ndas/nda_a1b2c3d4-.../sign?role=recipient"
},
"signatures": [
{
"id": "sig_abc123",
"signerType": "sender",
"signerName": "Jane Smith",
"signerEmail": "jane@acme.com",
"signedAt": "2026-01-15T10:35:00.000Z",
"ipAddress": "192.168.1.1"
},
{
"id": "sig_def456",
"signerType": "recipient",
"signerName": "Bob Johnson",
"signerEmail": "bob@partner.com",
"signedAt": "2026-01-16T09:00:00.000Z",
"ipAddress": "10.0.0.1"
}
]
}GET/api/v1/ndas/:id/pdf
Downloads the NDA as a PDF file. Returns binary PDF data withContent-Type: application/pdf.
Example Request
curl -H "Authorization: Bearer YOUR_API_KEY" \
-o nda-document.pdf \
https://swiftnda.com/api/v1/ndas/nda_a1b2c3d4-e5f6-7890-abcd-ef1234567890/pdfExample with fetch (Node.js)
const response = await fetch(
"https://swiftnda.com/api/v1/ndas/NDA_ID/pdf",
{ headers: { Authorization: "Bearer YOUR_API_KEY" } }
);
const pdfBuffer = await response.arrayBuffer();
// Save to file or process as neededResponse Headers
Content-Type: application/pdfContent-Disposition: attachment; filename="nda-Partner_Inc-nda_a1b2c3d4.pdf"
GET/api/v1/ndas/:id/status
The cheap polling target. Returns just the status and lifecycle timestamps (createdAt, senderSignedAt, emailSentAt, recipientSignedAt, firstOpenedAt) — poll it while waiting for the recipient to sign instead of fetching the full NDA.
curl -H "Authorization: Bearer YOUR_API_KEY" \
https://swiftnda.com/api/v1/ndas/NDA_ID/statusGET/api/v1/ndas/:id/signatures
Lists the signatures on the NDA. Each entry's signerType is "sender" or "recipient". Read-only: recipient signing happens in the browser, and sender signing via the API is out of scope.
curl -H "Authorization: Bearer YOUR_API_KEY" \
https://swiftnda.com/api/v1/ndas/NDA_ID/signaturesPOST/api/v1/ndas/:id/send
Emails the recipient signing invitation. The NDA must be in awaiting_client status (the sender has already signed) — otherwise 409 INVALID_STATE. An optional body { recipientEmail?, recipientName? } overrides the stored recipient; with no body the stored recipient is used. Subject to a 1-minute cooldown (429 EMAIL_COOLDOWN, includes retryAfter).
curl -X POST https://swiftnda.com/api/v1/ndas/NDA_ID/send \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"recipientEmail": "partner@example.com", "recipientName": "Jordan Partner"}'PATCH/api/v1/ndas/:id
Updates an unsigned draft and regenerates its draft PDF. Allowed only while the NDA is a draft with zero signatures — once the sender signs it is locked (409 INVALID_STATE). Send a partial of the create body (only the fields to change); the immutable creationType is ignored. Returns the full updated NDA.
curl -X PATCH https://swiftnda.com/api/v1/ndas/NDA_ID \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"recipientCompany": "Corrected Co, LLC", "purpose": "Updated evaluation purpose"}'DELETE/api/v1/ndas/:id
Deletes the NDA (at any status — completed NDAs are deletable), removes its stored PDFs, and cascades its signatures. Irreversible. Returns { success: true, deletedId }.
curl -X DELETE https://swiftnda.com/api/v1/ndas/NDA_ID \
-H "Authorization: Bearer YOUR_API_KEY"GET/api/v1/ndas/:id/audit-pdf
Downloads the audit-trail PDF. Available only for fully-signed NDAs — any other status returns 409 INVALID_STATE. Returns binary PDF data; on error the body is the standard JSON error envelope.
curl -H "Authorization: Bearer YOUR_API_KEY" \
-o nda-audit-trail.pdf \
https://swiftnda.com/api/v1/ndas/NDA_ID/audit-pdfQuota
One rule applies to both the dashboard and the API: drafts are free. 1 NDA is consumed from your plan when you (the sender) sign it. Your counterparty signs for free.
Creating & drafting
Creating an NDA — whether through the dashboard or via POST /api/v1/ndas — is free. You can draft as many NDAs as you like without affecting your quota.
Signing
1 NDA is deducted from your plan when the sender (owner) signs, via the signing URLs returned on creation. The counterparty's signature is always free. Because at most one owner signature is allowed per NDA, each NDA is charged exactly once.
Quota Exhausted
Creation never fails on quota — POST /api/v1/ndas does not return 402. When your quota is exhausted, the owner's signature is blocked with a 403 status and an upgrade prompt. A counterparty can still complete an NDA whose owner has already signed. Upgrade your plan or wait for the next billing cycle to sign more NDAs.
Error Codes
All error responses follow a consistent JSON format with a success: false field.
| Status | Meaning | When It Occurs |
|---|---|---|
| 200 | OK | Request succeeded |
| 201 | Created | NDA successfully created |
| 401 | Unauthorized | Missing, invalid, or revoked API key |
| 403 | Forbidden | API key lacks permission for this action |
| 404 | Not Found | NDA not found or doesn't belong to you |
| 409 | Conflict | Idempotency-Key reused with a different body (IDEMPOTENCY_KEY_REUSED), or the original request is still running (IDEMPOTENCY_IN_PROGRESS) |
| 422 | Unprocessable Entity | Invalid request body or validation errors |
| 429 | Too Many Requests | Rate limit exceeded (see Retry-After header) |
Error Response Format
{
"success": false,
"error": "Human-readable error message",
"code": "OPTIONAL_ERROR_CODE",
"details": [
{ "field": "fieldName", "message": "Field-specific error" }
]
}Rate Limits
API-key requests are rate-limited to 100 requests per minute per API key. This is one shared budget per key across all surfaces — both the /api/v1/* API and the key-authenticated web routes (/api/*) draw down the same per-key counter, so a key gets one budget total, not one per surface. Session-cookie (browser) requests are not rate-limited. On v1 responses the rate-limit headers below are returned on every response; on web routes they are returned on the 429 only.
Rate Limit Headers
| Header | Description |
|---|---|
| X-RateLimit-Limit | Maximum requests allowed per window (100) |
| X-RateLimit-Remaining | Requests remaining in current window |
| X-RateLimit-Reset | Unix timestamp when the window resets |
| Retry-After | Seconds until limit resets (only on 429) |
When Rate Limited (429)
{
"success": false,
"error": "Too many requests. Please slow down.",
"code": "RATE_LIMITED",
"retryAfter": 42
}Best Practices
- Check
X-RateLimit-Remainingto avoid hitting limits - Implement exponential backoff when receiving 429 responses
- Use the
Retry-Afterheader to know when to retry - Cache responses where possible to reduce API calls
Retries & Idempotency
If a POST /api/v1/ndas request times out or its connection drops, you can safely retry it without risking a duplicate NDA (and a second signing link). Send an Idempotency-Key header identifying the logical creation.
Generate a fresh UUIDv4 for each NDA you intend to create and reuse that same value on every retry of that one creation. Keys are honored for 24 hours. The header is case-insensitive and must be 1–255 characters.
Behavior
- Retry with the same key and the same body — you get back the original response, unchanged, with an
Idempotency-Replayed: trueheader. No second NDA is created. - Same key, different body — rejected with
409 IDEMPOTENCY_KEY_REUSED. Use a new key for a genuinely new request. - Retry while the original is still in flight — returns
409 IDEMPOTENCY_IN_PROGRESSwith aretryAfterhint (seconds); retry after that. - No header — the request runs normally, with no idempotency protection.
Example
curl -X POST \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 3f9c2b7a-1d4e-4a2b-8c6f-9e0a1b2c3d4e" \
-d '{ "recipientCompany": "Partner Inc", "purpose": "Discuss partnership", "ndaTermYears": 2, "confidentialityTermYears": 3, "senderPrintName": "Jane Smith", "senderTitle": "CEO", "senderCompany": "Acme Corp", "senderAddressCountry": "US", "senderAddressCity": "San Francisco", "senderAddressZip": "94105", "senderAddressStreet": "123 Market St", "governingLaw": "California", "jurisdiction": "San Francisco County, California", "creationType": "quick" }' \
https://swiftnda.com/api/v1/ndasIdempotency currently applies to POST /api/v1/ndas. Read-only endpoints (GET) are naturally safe to retry.
Automated Sender Signing
The sender signature is the one human bottleneck in an otherwise API-drivable flow. An agent can execute it via POST /api/v1/ndas/:id/execute once the account owner has authorized it. Recipient signing is always browser-based and is never executable via the API.
Legal shape (ESIGN/UETA). Intent to sign is captured once, by the human, in a dashboard grant ceremony on the profile page: they adopt a signature (type their name or draw it), read the authorization statement, tick an explicit checkbox, and choose a scope (all of their API keys, or one key). The API then executes the sender signature under that recorded, revocable, audited grant. Grants can be revoked at any time (effective immediately), and the audit-trail PDF records that the signature was executed via an authorized API key, when the authority was granted, and which version of the authorization text was accepted.
Quota. Executing the sender signature consumes one NDA of quota, exactly as signing in the browser does. If the plan's quota is exhausted, the call returns 403 QUOTA_EXHAUSTED. Check GET /api/v1/me (subscription.remaining) before executing.
If the presenting key has no active grant, the call returns 403 NO_SIGNING_AUTHORIZATION with a message telling you the owner must grant it in the dashboard (the message includes the settings URL). A key-specific grant takes precedence over an all-keys grant; a revoked grant is ignored. The call is idempotent — re-executing after the sender already signed replays the existing signature without a second quota charge.
curl -X POST \
-H "Authorization: Bearer YOUR_API_KEY" \
https://swiftnda.com/api/v1/ndas/NDA_ID/execute
# 200 -> { "success": true, "signature": { "signerType": "sender", ... }, "ndaStatus": "awaiting_client" }
# 403 -> { "success": false, "code": "NO_SIGNING_AUTHORIZATION", "error": "... grant it in the dashboard (...)" }
# 403 -> { "success": false, "code": "QUOTA_EXHAUSTED", "quotaExceeded": true, "upgradeUrl": "/pricing" }Webhooks
Subscribe your own URL to your NDAs' lifecycle events. When an event fires, SwiftNDA POSTs a signed JSON payload to every one of your active subscriptions that lists that event. Delivery is a single attempt with a 5-second timeout; 20 consecutive failures auto-disable a subscription (reactivate it with a PATCH).
Events
| Event | Fires when |
|---|---|
| sender.signed | The sender (NDA owner) signs. |
| recipient.signed | The recipient (counterparty) signs. |
| recipient.opened | The recipient first opens the signing page. |
| recipient.invitation_sent | The signing invitation email is sent. |
| nda.completed | Both parties have signed; the NDA is fully executed. |
| webhook.test | Sent only by the test endpoint below. |
Create a subscription
The response returns the signing secret in full exactly once — store it. Everywhere else it is masked (whsec_…1a2b). A maximum of 5 active subscriptions per account is enforced (403 WEBHOOK_LIMIT_REACHED). URLs must use https in production.
curl -X POST https://swiftnda.com/api/v1/webhooks \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"url": "https://your-app.example.com/hooks/swiftnda", "events": ["nda.completed", "recipient.signed"], "description": "prod receiver"}'Manage & test
GET /api/v1/webhooks— list (secrets masked).GET /api/v1/webhooks/:id— retrieve one.PATCH /api/v1/webhooks/:id— updateurl/events/description/isActive. SendingisActive: truereactivates and resets the failure counter.DELETE /api/v1/webhooks/:id— remove a subscription.POST /api/v1/webhooks/:id/test— deliver a signedwebhook.testto verify your receiver; the response reports the delivered status code or the failure reason.
Verifying the signature
Each delivery carries X-Webhook-Signature (hex HMAC-SHA256 of the raw request body, keyed with your subscription secret), plus X-Webhook-Event and X-Webhook-Subscription-Id. Compare with a constant-time check.
// Node.js (Express) — verify X-Webhook-Signature
import crypto from "crypto";
app.post("/hooks/swiftnda", express.raw({ type: "application/json" }), (req, res) => {
const secret = process.env.SWIFTNDA_WEBHOOK_SECRET; // "whsec_..."
const expected = crypto.createHmac("sha256", secret).update(req.body).digest("hex");
const received = req.header("X-Webhook-Signature") || "";
const ok =
expected.length === received.length &&
crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(received));
if (!ok) return res.status(401).end();
const event = JSON.parse(req.body.toString());
console.log(req.header("X-Webhook-Event"), event.ndaId, event.status);
res.status(200).end();
});# Python (Flask) — verify X-Webhook-Signature
import hmac, hashlib
from flask import request, abort
@app.post("/hooks/swiftnda")
def swiftnda_webhook():
secret = os.environ["SWIFTNDA_WEBHOOK_SECRET"] # "whsec_..."
expected = hmac.new(secret.encode(), request.get_data(), hashlib.sha256).hexdigest()
received = request.headers.get("X-Webhook-Signature", "")
if not hmac.compare_digest(expected, received):
abort(401)
event = request.get_json()
print(request.headers["X-Webhook-Event"], event["ndaId"], event["status"])
return "", 200LLM Agent Compatibility
The SwiftNDA API is designed to be easily consumed by LLM agents and AI assistants. Here's what makes it agent-friendly:
Consistent JSON Responses
Every response includes a success boolean. Errors include descriptive messages and field-level validation details.
Predictable Error Handling
HTTP status codes follow REST conventions. Error codes likePDF_GENERATION_FAILED enable programmatic error handling without parsing messages.
Full CRUD via API
Create NDAs, list with pagination and filtering, retrieve details, and download PDFs — all via simple REST endpoints with Bearer token auth.
Minimal Authentication
A single API key in the Authorization header is all that's needed. No OAuth flows, no token refresh — just a persistent Bearer token.
Example: Node.js / TypeScript
const API_KEY = process.env.SWIFTNDA_API_KEY;
const BASE_URL = "https://swiftnda.com/api/v1";
// List all draft NDAs
const response = await fetch(`${BASE_URL}/ndas?status=draft`, {
headers: { Authorization: `Bearer ${API_KEY}` },
});
const data = await response.json();
console.log(`Found ${data.pagination.total} draft NDAs`);
// Create a new NDA
const createResponse = await fetch(`${BASE_URL}/ndas`, {
method: "POST",
headers: {
Authorization: `Bearer ${API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
recipientCompany: "Partner Inc",
purpose: "Discuss partnership",
ndaTermYears: 2,
confidentialityTermYears: 3,
senderPrintName: "Jane Smith",
senderTitle: "CEO",
senderCompany: "Acme Corp",
senderAddressCountry: "US",
senderAddressCity: "San Francisco",
senderAddressZip: "94105",
senderAddressStreet: "123 Market St",
governingLaw: "California",
jurisdiction: "San Francisco County, California",
creationType: "quick",
}),
});
if (createResponse.status === 201) {
const newNda = await createResponse.json();
console.log(`Created NDA: ${newNda.nda.id}`);
// Creation is free; send the sender's signing URL to sign (which consumes quota).
console.log(`Sign here: ${newNda.nda.senderSigningUrl}`);
}Example: Python
import requests
API_KEY = "YOUR_API_KEY"
BASE_URL = "https://swiftnda.com/api/v1"
headers = {"Authorization": f"Bearer {API_KEY}"}
# List NDAs
response = requests.get(f"{BASE_URL}/ndas", headers=headers)
data = response.json()
print(f"Found {data['pagination']['total']} NDAs")
# Download a PDF
pdf_response = requests.get(
f"{BASE_URL}/ndas/{nda_id}/pdf", headers=headers
)
with open("nda.pdf", "wb") as f:
f.write(pdf_response.content)MCP Server
SwiftNDA is also a hosted MCP (Model Context Protocol) server, so an agent in Claude Code, Claude Desktop, or claude.ai can drive your NDAs as native tools — no glue code. It is the same API behind this page: every tool maps 1:1 onto a v1 endpoint and is authenticated with the same API key. The server adds no new permissions — a tool can do exactly what your API key can do.
Endpoint
https://swiftnda.com/mcpTransport: Streamable HTTP, stateless, POST only (no SSE, no session id — every request is self-contained). The server identifies itself as swiftnda.
Authentication
Present the same API key you use for the REST API, in the transport header — as a bearer token (primary) or the X-API-Key header. Never put a key in a tool argument. A missing, malformed, invalid, or revoked key returns 401 (with a WWW-Authenticate: Bearer challenge); a guest-account key returns 403 — the same ladder the REST API uses.
Authorization: Bearer snda_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxTools
The server exposes these 18 tools, each a pass-through to the matching v1 endpoint documented above:
| Tool | Purpose |
|---|---|
| me | Read your account profile and subscription quota (check remaining before signing). |
| nda_list | List your NDAs (paginated; filter by status/search, sort). |
| nda_get | Fetch one NDA with its full details and signatures. |
| nda_create | Create a draft NDA and its draft PDF (free; optional idempotencyKey). |
| nda_update_draft | Edit an unsigned draft and regenerate its PDF (draft-only). |
| nda_status | Cheap poll of an NDA's status and lifecycle timestamps. |
| nda_signatures | List an NDA's signatures (read-only). |
| nda_send | Email the recipient their signing invitation. |
| nda_delete | Delete an NDA, its stored PDFs, and its signatures. |
| nda_download_pdf | Download the NDA PDF as a base64 blob (8 MB inline cap, else a URL). |
| nda_download_audit_pdf | Download the audit-trail PDF (fully-signed NDAs only). |
| nda_execute_sender_signature | Execute the sender signature under a dashboard-granted authorization (consumes 1 quota; relay the settings URL on NO_SIGNING_AUTHORIZATION). |
| webhook_create | Create a webhook to your own backend (returns the full secret once; max 5 active). |
| webhook_list | List your webhook subscriptions (secrets masked). |
| webhook_get | Fetch one webhook subscription (secret masked). |
| webhook_update | Update a webhook (url/events/description/isActive; isActive:true reactivates + resets failures). |
| webhook_delete | Delete a webhook subscription. |
| webhook_test | Deliver a signed webhook.test to verify your receiver (does not touch the failure counter). |
This includes agent-executed sender signing (nda_execute_sender_signature) and webhook management (webhook_*). Webhooks deliver to your own backend, not the MCP client, and nda_execute_sender_signature consumes 1 NDA of quota under a dashboard-granted signing authorization.
Connect a client
Claude Code
Add the server with the CLI (one line):
claude mcp add --transport http swiftnda https://swiftnda.com/mcp \
--header "Authorization: Bearer snda_..."Claude Desktop
Add an entry to mcpServers in your claude_desktop_config.json:
{
"mcpServers": {
"swiftnda": {
"type": "http",
"url": "https://swiftnda.com/mcp",
"headers": {
"Authorization": "Bearer snda_..."
}
}
}
}claude.ai custom connector
In claude.ai go to Settings → Connectors → Add custom connector, name it SwiftNDA, and use the remote server URL below. In the connector's advanced settings, add the Authorization header carrying your key:
Remote MCP server URL: https://swiftnda.com/mcp
HTTP header: Authorization: Bearer snda_...Any other MCP client
Point any Streamable-HTTP MCP client at https://swiftnda.com/mcp and send your key on every request via the Authorization: Bearer snda_... (or X-API-Key) header. The server is stateless and POST-only — there is no SSE stream or session to manage.
Two rules carry over from the API. Creating NDAs and editing drafts is free — 1 NDA of quota is consumed only when the sender signs (via the signing URLs on the created NDA). Recipient signing is free and browser-based via recipientSigningUrl — there is deliberately no tool (and no API endpoint) for it.