LogoSignFastAi
  • Features
  • Pricing
  • Blog
  • API

API Reference

Send documents from your SignFastAi templates programmatically.

The SignFastAi public API lets you create and send documents from a saved template ("My Templates" in the dashboard), check a document's signing status, and download the final signed PDF — without opening a browser. It's built for automations (Zapier-style scripts, your own tooling) that need to trigger signature requests from another system.

Overview

Availability

The public API is a Pro / Lifetime feature. Every endpoint requires a valid API key from an account on one of those plans.

Base URL

All endpoints are under:

https://<your-domain>/api/v1

Replace <your-domain> with the domain you access SignFastAi at.

Quick example

curl https://<your-domain>/api/v1/presets \
  -H "x-api-key: YOUR_API_KEY"

What's not covered

This is intentionally a minimal surface — create/send, status, and download, plus webhooks so you don't have to poll. There's no endpoint (yet) to manage templates, clients, or in-person signing sessions themselves; those are dashboard-only for now.

Authentication

Creating a key

  1. Sign in and open Settings → API Keys (/settings/apikeys). This page requires a Pro or Lifetime plan — free-plan accounts see an upgrade prompt instead.
  2. Click Create API Key and give it a name.
  3. Copy the key value shown in the dialog. You can show and copy it again at any time from the API Keys list (eye and copy icons in the Key column), so there's no need to save it somewhere else first. If a key is ever exposed, delete it and create a new one.

Using a key

Send the key on every request as the x-api-key header:

curl https://<your-domain>/api/v1/presets \
  -H "x-api-key: YOUR_API_KEY"

There is no Authorization: Bearer support — use x-api-key specifically.

Rate limits

Each key is limited to 1000 requests per rolling 24 hours. This is an abuse backstop, not your real send limit — the number of documents you can actually send per month is governed by your plan (see Sign limit), independently of this request-rate cap. Exceeding it returns a 429 — see Errors.

Authentication errors

StatusCodeMeaning
401MISSING_API_KEYNo x-api-key header was sent
401INVALID_API_KEY (or another key-specific code)The key doesn't exist, was revoked, or is malformed
403UPGRADE_REQUIREDThe key is valid, but its owner's plan doesn't include API access
429RATE_LIMITEDToo many requests in the current 24-hour window

Presets

List your templates

GET /api/v1/presets

Returns every template ("preset") saved on your account, with its signer roles. You need this before calling POST /api/v1/documents — recipients are matched to a role by its id, not by its display label, so you look the ID up here first.

Why match by role ID, not by name? Two roles on the same template can share a display label (e.g. two "Signer" roles). IDs are always unique, so there's no ambiguity about which role a recipient is being assigned to.

Presets response

{
  "success": true,
  "data": {
    "presets": [
      {
        "id": "b3f6b6b0-...",
        "title": "Freelance Agreement",
        "roles": [
          {
            "id": "0e2b0f2e-...",
            "role": "Client",
            "requireAccessCode": false
          },
          {
            "id": "9a1c9d3f-...",
            "role": "Witness",
            "requireAccessCode": true
          }
        ]
      }
    ]
  }
}
  • roles is ordered the same way the template's signers sign in.
  • requireAccessCode: true means a recipient assigned to that role must also include an accessCode when you send the document — see Access codes.

There's no pagination — an account's number of templates is small (the free plan caps at one; this endpoint itself requires Pro/Lifetime anyway).

Documents

Create and send

POST /api/v1/documents

Creates one document from a template and sends it immediately — every recipient is emailed their signing link as part of this call (unless the template has signing order turned on, in which case only the first signer is notified; the rest are notified automatically as each prior signer completes, same as sending from the dashboard).

Body

{
  "presetId": "b3f6b6b0-...",
  "title": "Freelance Agreement — Acme Corp",
  "recipients": [
    {
      "roleId": "0e2b0f2e-...",
      "name": "Jane Client",
      "email": "jane@acme.example"
    },
    {
      "roleId": "9a1c9d3f-...",
      "name": "Bob Witness",
      "email": "bob@acme.example",
      "accessCode": "4471"
    }
  ]
}
FieldRequiredNotes
presetIdyesFrom GET /api/v1/presets
titlenoOverrides the template's saved title for this document
recipientsyesOne entry per role on the template — see below
recipients[].roleIdyesMust be a role ID belonging to presetId
recipients[].nameyes
recipients[].emailyes
recipients[].accessCodeconditionalRequired if that role has requireAccessCode: true

Every role on the template needs exactly one recipient — no more, no fewer. The API validates this before creating anything:

  • a roleId that doesn't belong to presetId → UNKNOWN_ROLE_ID
  • the same roleId used twice → DUPLICATE_ROLE_ID
  • a role with no recipient → MISSING_ROLE_RECIPIENT
  • a requireAccessCode role with no (or blank) accessCode → ACCESS_CODE_REQUIRED

See Errors for the full response shape of each.

Access codes

Unlike the dashboard's Bulk Send feature, which only supports single-role templates and refuses templates that require an access code, the API supports multi-role templates and lets you supply each recipient's access code directly — there's no UI step to collect it, so it has to come from you.

Sign limit

Before creating the document, the API checks your plan's monthly send limit (the same one enforced when sending from the dashboard). If sending this document would exceed it, you get a 409 SIGN_LIMIT_EXCEEDED and nothing is created.

Create response

{
  "success": true,
  "data": {
    "documentId": "c1d2e3f4-...",
    "status": "sent",
    "signers": [
      {
        "id": "...",
        "name": "Jane Client",
        "email": "jane@acme.example",
        "role": "Client",
        "status": "sent",
        "signingUrl": "https://<your-domain>/sign/s/..."
      },
      {
        "id": "...",
        "name": "Bob Witness",
        "email": "bob@acme.example",
        "role": "Witness",
        "status": "pending",
        "signingUrl": null
      }
    ]
  }
}

signingUrl is only populated for signers notified in this call — null for anyone still queued behind a signing-order template (they get a real link, and get notified, once it's their turn).

Check status

GET /api/v1/documents/:id

Returns the document's current status and each signer's status/timestamps. 404s (as DOCUMENT_NOT_FOUND) if the document doesn't exist or belongs to a different account — the API never confirms which.

{
  "success": true,
  "data": {
    "id": "c1d2e3f4-...",
    "title": "Freelance Agreement — Acme Corp",
    "status": "sent",
    "sentAt": "2026-09-23T09:00:00.000Z",
    "completedAt": null,
    "signers": [
      {
        "id": "...",
        "name": "Jane Client",
        "email": "jane@acme.example",
        "role": "Client",
        "status": "viewed",
        "viewedAt": "2026-09-23T09:05:00.000Z",
        "signedAt": null,
        "declinedAt": null,
        "declineReason": null
      }
    ]
  }
}

Download the signed PDF

GET /api/v1/documents/:id/download

Only available once status is completed. Returns the final PDF directly (Content-Type: application/pdf), not a JSON envelope. Calling it earlier returns 409 DOCUMENT_NOT_COMPLETED.

The signed PDF is the original document with a certificate-of-completion page appended. Response headers:

  • Content-Disposition — the original file name (filename* carries the UTF-8 name for non-ASCII file names).
  • X-Document-Sha256 — the SHA-256 of the file, so you can verify the download wasn't altered or truncated.

Fetching the file from storage can occasionally fail transiently (502 STORAGE_FETCH_FAILED, or a 500) — retrying the request is safe.

Webhooks

Instead of polling GET /documents/{id}, register a URL and we'll POST an event to it whenever something happens to one of your documents — whether it was created through the API, the dashboard, or a bulk send.

Manage endpoints under Settings → API Keys → Webhooks. You can add up to 5 endpoints, choose which events each one receives, send a test event, and see every delivery (with the response we got) for 30 days. Webhooks are a Pro / Lifetime feature.

Events

EventSent when
document.sentA document was sent for signature
signer.viewedA signer opened the document
signer.signedA signer finished signing
document.completedEvery signer has signed and the final PDF is ready to download
document.declinedA signer declined to sign

document.completed is sent only after the final PDF exists, so you can call GET /documents/{id}/download as soon as you receive it.

Payload

Each request is a POST with a JSON body:

{
  "id": "evt_2f6c1b0e-7a1d-4a55-9a52-2f7c0f8f1a11",
  "type": "document.signed",
  "createdAt": "2026-09-24T09:15:02.000Z",
  "data": {
    "document": {
      "id": "0b0c7a4e-...",
      "title": "Service Agreement — Acme",
      "status": "sent",
      "createdVia": "api",
      "sentAt": "2026-09-24T09:10:00.000Z",
      "completedAt": null,
      "finalFileHash": null,
      "signers": [
        { "id": "…", "name": "Ada Lovelace", "email": "ada@example.com", "status": "signed" }
      ]
    },
    "signer": { "id": "…", "name": "Ada Lovelace", "email": "ada@example.com", "status": "signed" }
  }
}

data.signer is present for signer.* events. The same event can be delivered more than once (for example after a retry), so treat id as an idempotency key.

Verifying the signature

Every request carries these headers:

HeaderValue
X-SignFast-Signaturet=<unix seconds>,v1=<hex HMAC-SHA256>
X-SignFast-EventThe event type, e.g. document.completed
X-SignFast-DeliveryA unique id for this delivery

The signature is HMAC-SHA256(secret, "<t>.<raw request body>"), where secret is your endpoint's signing secret (shown when you create it, and available any time from the endpoint list). Compute it over the raw body, compare in constant time, and reject timestamps more than a few minutes old:

import { createHmac, timingSafeEqual } from 'node:crypto';

function verify(secret, header, rawBody) {
  const parts = Object.fromEntries(header.split(',').map((p) => p.split('=')));
  if (Math.abs(Date.now() / 1000 - Number(parts.t)) > 300) return false;
  const expected = createHmac('sha256', secret)
    .update(`${parts.t}.${rawBody}`)
    .digest('hex');
  const a = Buffer.from(expected);
  const b = Buffer.from(parts.v1 ?? '');
  return a.length === b.length && timingSafeEqual(a, b);
}

Delivery and retries

Respond with any 2xx status within 8 seconds to acknowledge. Anything else (or a timeout) counts as a failure: we retry after about 5 seconds and again after about 30 seconds, then mark the delivery as failed. Failed deliveries can be re-sent from the dashboard. Redirects are not followed.

Endpoint URLs must be https:// and publicly reachable — addresses on private networks, localhost and cloud-metadata ranges are rejected.

Request log

Every request made with your API keys is recorded (method, path, status code, error code, duration) and shown under Settings → API Keys → Request log, filterable by key and by success or error. Records are kept for 30 days. The API Keys list also shows each key's last-used time and request count for the past 30 days, and documents created through the API are marked "API" in the documents list.

The Usage tab charts your requests per hour (last 24 hours) or per day (7 or 30 days), split into successes, client errors and server errors, with response-time figures and a breakdown by endpoint and by key. The request log can be exported as CSV, and you can turn on an error alert: we email you when the failure rate over the last hour crosses a threshold you choose (at most once every 6 hours).

Errors

Response shape

Every JSON response (everything except the PDF download) uses the same envelope:

{ "success": true, "data": { /* ... */ } }
{
  "success": false,
  "error": {
    "code": "MISSING_ROLE_RECIPIENT",
    "message": "Every template role needs a recipient",
    "details": { "missingRoleIds": ["9a1c9d3f-..."] }
  }
}

details is only present on errors where there's something programmatically useful to act on (which role IDs were the problem, what your plan's limit is, and so on) — check it before falling back to parsing message.

Error codes

StatusCodeWhereMeaning
401MISSING_API_KEYany endpointNo x-api-key header sent
401INVALID_API_KEY (or similar)any endpointKey doesn't exist, was revoked, or is malformed
403UPGRADE_REQUIREDany endpointKey is valid, but the account isn't Pro/Lifetime
429RATE_LIMITEDany endpointOver 1000 requests in the last 24h on this key
400INVALID_BODYPOST /documentsBody failed schema validation
404PRESET_NOT_FOUNDPOST /documentspresetId doesn't exist or isn't yours
400UNKNOWN_ROLE_IDPOST /documentsA roleId doesn't belong to this template
400DUPLICATE_ROLE_IDPOST /documentsThe same roleId was used for two recipients
400MISSING_ROLE_RECIPIENTPOST /documentsA template role has no recipient
400ACCESS_CODE_REQUIREDPOST /documentsA requireAccessCode role's recipient is missing accessCode
409SIGN_LIMIT_EXCEEDEDPOST /documentsSending this would exceed your plan's monthly send limit
404DOCUMENT_NOT_FOUNDGET /documents/:id, .../downloadDocument doesn't exist or isn't yours
409DOCUMENT_NOT_COMPLETEDGET /documents/:id/downloadNot every signer has signed yet
502STORAGE_FETCH_FAILEDGET /documents/:id/downloadTransient storage error — retry
500INTERNAL_ERRORany endpointUnexpected server error

Table of Contents

OverviewAvailabilityBase URLQuick exampleWhat's not coveredAuthenticationCreating a keyUsing a keyRate limitsAuthentication errorsPresetsList your templatesPresets responseDocumentsCreate and sendBodyAccess codesSign limitCreate responseCheck statusDownload the signed PDFWebhooksEventsPayloadVerifying the signatureDelivery and retriesRequest logErrorsResponse shapeError codes
LogoSignFastAi

Upload, mark, send. Get documents signed in minutes.

X (Twitter)
Product
  • Features
  • Pricing
  • FAQ
Resources
  • Blog
  • API Documentation
Compare
  • vs DocuSign
  • vs Adobe Acrobat Sign
  • vs PandaDoc
  • vs Dropbox Sign
  • vs SignWell
  • vs DocuSeal
Company
  • About
  • Contact
Legal
  • Cookie Policy
  • Privacy Policy
  • Terms of Service
Listed on Turbo0Featured on Findly.toolsFeatured on Twelve ToolsFeatured on Saasgrave LaunchesFazier badgeFeatured on TheDevToolsList
© 2026 SignFastAi. All Rights Reserved.