Skip to main content
SwiftNDA
Developer Reference

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.

1

Create NDA via API

POST /api/v1/ndas — The response includes senderSigningUrl and recipientSigningUrl. NDA status is draft.

2

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.

3

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.

4

Recipient signs the NDA

The recipient opens the link and signs. No account required. NDA status changes to fully_signed.

5

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:

bash
curl -H "Authorization: Bearer YOUR_API_KEY" \
  https://swiftnda.com/api/v1/me

3. List Your NDAs

Retrieve all your NDAs with pagination:

bash
curl -H "Authorization: Bearer YOUR_API_KEY" \
  https://swiftnda.com/api/v1/ndas?page=1&pageSize=10

4. Create an NDA

Create a new NDA programmatically (free — drafts never consume quota):

bash
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/ndas

Authentication

All API requests must include your API key in the Authorization header using the Bearer token scheme.

http
Authorization: Bearer snda_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

As 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

text
https://swiftnda.com/api/v1

Endpoints

The SwiftNDA API provides 5 endpoints for managing your NDAs and account.

GET/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

bash
curl -H "Authorization: Bearer YOUR_API_KEY" \
  https://swiftnda.com/api/v1/me

Example Response

json
{
  "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

ParameterTypeDefaultDescription
pagenumber1Page number
pageSizenumber20Items per page (1-100)
statusstring—Filter: draft, awaiting_client, fully_signed
searchstring—Search company names, descriptions
sortBystringupdatedAtcreatedAt, updatedAt, status, recipientCompany
sortOrderstringdescasc or desc
createdAfterstring—ISO 8601: only NDAs created at/after
createdBeforestring—ISO 8601: only NDAs created at/before

Example Request

bash
curl -H "Authorization: Bearer YOUR_API_KEY" \
  "https://swiftnda.com/api/v1/ndas?page=1&pageSize=10&status=draft"

Example Response

json
{
  "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)

FieldTypeRequiredDescription
recipientCompanystringYesRecipient company name (max 100)
projectDescriptionstringNoProject description (max 500)
purposestringYesPurpose of the NDA (max 300)
ndaTermYearsnumberYesNDA duration in years (1-10)
confidentialityTermYearsnumberYesConfidentiality period (2-20, must be ≥ ndaTermYears)
senderPrintNamestringYesYour full name (max 100)
senderTitlestringYesYour job title (max 100)
senderCompanystringYesYour company name (max 100)
senderAddressCountrystringYesCountry code (e.g., "US")
senderAddressStatestringNoState/Province
senderAddressCitystringYesCity (max 100)
senderAddressZipstringYesZIP/Postal code (max 20)
senderAddressStreetstringYesStreet address (max 200)
senderAddressSuitestringNoSuite/Apt number (max 50)
senderPhonestringNoPhone number (international format)
governingLawstringYesGoverning law (e.g., "California")
jurisdictionstringYesLegal jurisdiction
creationTypestringYes"quick" or "detailed"

Example Request

bash
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/ndas

Example Response (201 Created)

json
{
  "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)

json
{
  "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

bash
curl -H "Authorization: Bearer YOUR_API_KEY" \
  https://swiftnda.com/api/v1/ndas/nda_a1b2c3d4-e5f6-7890-abcd-ef1234567890

Example Response

json
{
  "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

bash
curl -H "Authorization: Bearer YOUR_API_KEY" \
  -o nda-document.pdf \
  https://swiftnda.com/api/v1/ndas/nda_a1b2c3d4-e5f6-7890-abcd-ef1234567890/pdf

Example with fetch (Node.js)

javascript
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 needed

Response Headers

  • Content-Type: application/pdf
  • Content-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.

bash
curl -H "Authorization: Bearer YOUR_API_KEY" \
  https://swiftnda.com/api/v1/ndas/NDA_ID/status

GET/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.

bash
curl -H "Authorization: Bearer YOUR_API_KEY" \
  https://swiftnda.com/api/v1/ndas/NDA_ID/signatures

POST/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).

bash
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.

bash
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 }.

bash
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.

bash
curl -H "Authorization: Bearer YOUR_API_KEY" \
  -o nda-audit-trail.pdf \
  https://swiftnda.com/api/v1/ndas/NDA_ID/audit-pdf

Quota

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.

StatusMeaningWhen It Occurs
200OKRequest succeeded
201CreatedNDA successfully created
401UnauthorizedMissing, invalid, or revoked API key
403ForbiddenAPI key lacks permission for this action
404Not FoundNDA not found or doesn't belong to you
409ConflictIdempotency-Key reused with a different body (IDEMPOTENCY_KEY_REUSED), or the original request is still running (IDEMPOTENCY_IN_PROGRESS)
422Unprocessable EntityInvalid request body or validation errors
429Too Many RequestsRate limit exceeded (see Retry-After header)

Error Response Format

json
{
  "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

HeaderDescription
X-RateLimit-LimitMaximum requests allowed per window (100)
X-RateLimit-RemainingRequests remaining in current window
X-RateLimit-ResetUnix timestamp when the window resets
Retry-AfterSeconds until limit resets (only on 429)

When Rate Limited (429)

json
{
  "success": false,
  "error": "Too many requests. Please slow down.",
  "code": "RATE_LIMITED",
  "retryAfter": 42
}

Best Practices

  • Check X-RateLimit-Remaining to avoid hitting limits
  • Implement exponential backoff when receiving 429 responses
  • Use the Retry-After header 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: true header. 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_PROGRESS with a retryAfter hint (seconds); retry after that.
  • No header — the request runs normally, with no idempotency protection.

Example

bash
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/ndas

Idempotency 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.

bash
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

EventFires when
sender.signedThe sender (NDA owner) signs.
recipient.signedThe recipient (counterparty) signs.
recipient.openedThe recipient first opens the signing page.
recipient.invitation_sentThe signing invitation email is sent.
nda.completedBoth parties have signed; the NDA is fully executed.
webhook.testSent 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.

bash
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 — update url/events/description/isActive. Sending isActive: true reactivates and resets the failure counter.
  • DELETE /api/v1/webhooks/:id — remove a subscription.
  • POST /api/v1/webhooks/:id/test — deliver a signed webhook.test to 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.

javascript
// 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
# 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 "", 200

LLM 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

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

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

text
https://swiftnda.com/mcp

Transport: 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.

http
Authorization: Bearer snda_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Tools

The server exposes these 18 tools, each a pass-through to the matching v1 endpoint documented above:

ToolPurpose
meRead your account profile and subscription quota (check remaining before signing).
nda_listList your NDAs (paginated; filter by status/search, sort).
nda_getFetch one NDA with its full details and signatures.
nda_createCreate a draft NDA and its draft PDF (free; optional idempotencyKey).
nda_update_draftEdit an unsigned draft and regenerate its PDF (draft-only).
nda_statusCheap poll of an NDA's status and lifecycle timestamps.
nda_signaturesList an NDA's signatures (read-only).
nda_sendEmail the recipient their signing invitation.
nda_deleteDelete an NDA, its stored PDFs, and its signatures.
nda_download_pdfDownload the NDA PDF as a base64 blob (8 MB inline cap, else a URL).
nda_download_audit_pdfDownload the audit-trail PDF (fully-signed NDAs only).
nda_execute_sender_signatureExecute the sender signature under a dashboard-granted authorization (consumes 1 quota; relay the settings URL on NO_SIGNING_AUTHORIZATION).
webhook_createCreate a webhook to your own backend (returns the full secret once; max 5 active).
webhook_listList your webhook subscriptions (secrets masked).
webhook_getFetch one webhook subscription (secret masked).
webhook_updateUpdate a webhook (url/events/description/isActive; isActive:true reactivates + resets failures).
webhook_deleteDelete a webhook subscription.
webhook_testDeliver 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):

bash
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:

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:

text
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.