Documenso

First API Call

Create and send a document for signing using the Documenso API, from uploading a PDF to adding recipients and distributing for signature.

Documents and templates are being deprecated and replaced by envelopes. Read the migration guide here.

Prerequisites

Before starting, you need:

API tokens have full access to your account. Store them securely and never commit them to version control.

Limitations

The API cannot:

  • Sign documents on behalf of recipients (recipients must sign themselves)
  • Convert non-PDF files to PDF (you must upload PDFs)
  • Retrieve the signed PDF until all recipients have completed signing

Base URL

All API requests use the following base URLs:

EnvironmentBase URL
Productionhttps://app.documenso.com/api/v2
Staginghttps://stg-app.documenso.com/api/v2

Example 1: List Your Documents

Start with a simple GET request to verify your API token works.

curl -X GET "https://app.documenso.com/api/v2/envelope" \
  -H "Authorization: YOUR_API_TOKEN"

A successful response returns a list of your documents (envelopes):

{
  "data": [
    {
      "id": "envelope_abc123",
      "status": "DRAFT",
      "title": "Contract Agreement",
      "createdAt": "2025-01-15T10:30:00.000Z"
    }
  ],
  "count": 1,
  "currentPage": 1,
  "perPage": 10,
  "totalPages": 1
}

If you receive a 401 Unauthorized error, verify your API token is correct and includes the api_ prefix.

Example 2: Create a Document with Recipient and Signature Field

The V2 API uses a single endpoint to create a document with recipients and fields in one request. This is the most common pattern for sending documents.

curl -X POST "https://app.documenso.com/api/v2/envelope/create" \
  -H "Authorization: YOUR_API_TOKEN" \
  -H "Content-Type: multipart/form-data" \
  -F 'payload={
    "type": "DOCUMENT",
    "title": "Service Agreement",
    "recipients": [
      {
        "email": "signer@example.com",
        "name": "John Smith",
        "role": "SIGNER",
        "fields": [
          {
            "identifier": 0,
            "type": "SIGNATURE",
            "page": 1,
            "positionX": 10,
            "positionY": 80,
            "width": 30,
            "height": 5
          },
          {
            "identifier": 0,
            "type": "DATE",
            "page": 1,
            "positionX": 50,
            "positionY": 80,
            "width": 20,
            "height": 3
          }
        ]
      }
    ]
  }' \
  -F "files=@./contract.pdf;type=application/pdf"

Understanding Field Positioning

Field positions use percentage values (0-100) relative to the PDF page dimensions:

ParameterDescription
positionXHorizontal position from left edge (0 = left, 100 = right)
positionYVertical position from top edge (0 = top, 100 = bottom)
widthField width as percentage of page width
heightField height as percentage of page height
pagePage number (1-indexed)
identifierIndex of the file (0 for first file, 1 for second, etc.)

To place a signature near the bottom-left of the page, use positionX: 10 and positionY: 80.

Recipient Roles

RoleDescription
SIGNERMust sign the document
APPROVERMust approve before signers can sign
CCReceives a copy but doesn't sign
VIEWERCan view the document but takes no action

See the recipient roles page for more information.

Example 3: Send the Document for Signing

After creating a document, it's in DRAFT status. To send it to recipients, use the distribute endpoint:

curl -X POST "https://app.documenso.com/api/v2/envelope/distribute" \
  -H "Authorization: YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "envelopeId": "envelope_abc123"
  }'

After distribution, recipients receive an email with a link to sign the document. The document status changes from DRAFT to PENDING.

Full Workflow Example

Here's a complete script that creates and sends a document:

const fs = require('fs');
const FormData = require('form-data');

const API_TOKEN = process.env.DOCUMENSO_API_TOKEN;
const BASE_URL = 'https://app.documenso.com/api/v2';

async function createAndSendDocument(pdfPath, recipientEmail, recipientName) {
  // Step 1: Create the envelope with recipient and fields
  const form = new FormData();

  const payload = {
    type: 'DOCUMENT',
    title: 'Service Agreement',
    recipients: [
      {
        email: recipientEmail,
        name: recipientName,
        role: 'SIGNER',
        fields: [
          {
            identifier: 0,
            type: 'SIGNATURE',
            page: 1,
            positionX: 10,
            positionY: 80,
            width: 30,
            height: 5,
          },
          {
            identifier: 0,
            type: 'NAME',
            page: 1,
            positionX: 10,
            positionY: 75,
            width: 30,
            height: 3,
          },
          {
            identifier: 0,
            type: 'DATE',
            page: 1,
            positionX: 50,
            positionY: 80,
            width: 20,
            height: 3,
          },
        ],
      },
    ],
  };

  form.append('payload', JSON.stringify(payload));
  form.append('files', fs.createReadStream(pdfPath), {
    contentType: 'application/pdf',
  });

  const createResponse = await fetch(`${BASE_URL}/envelope/create`, {
    method: 'POST',
    headers: {
      'Authorization': API_TOKEN,
    },
    body: form,
  });

  if (!createResponse.ok) {
    const error = await createResponse.json();
    throw new Error(`Failed to create envelope: ${JSON.stringify(error)}`);
  }

  const envelope = await createResponse.json();
  console.log('Created envelope:', envelope.id);

  // Step 2: Send the document for signing
  const distributeResponse = await fetch(`${BASE_URL}/envelope/distribute`, {
    method: 'POST',
    headers: {
      'Authorization': API_TOKEN,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ envelopeId: envelope.id }),
  });

  if (!distributeResponse.ok) {
    const error = await distributeResponse.json();
    throw new Error(`Failed to distribute envelope: ${JSON.stringify(error)}`);
  }

  console.log('Document sent for signing!');
  return envelope.id;
}

// Usage
createAndSendDocument(
  './contract.pdf',
  'signer@example.com',
  'John Smith'
).catch(console.error);

Error Handling

The API returns standard HTTP status codes and JSON error responses:

Status CodeMeaning
400Bad request - check your request payload
401Unauthorized - invalid or missing API token
404Not found - resource doesn't exist
429Rate limit or plan quota. If Retry-After is present, retry the request.
500Server error - retry or contact support

Error Response Format

{
  "error": "Description of what went wrong",
  "code": "ERROR_CODE",
  "statusCode": 400
}

Common Errors

Invalid file type:

{
  "error": "Invalid file type. Only PDF files are supported.",
  "statusCode": 400
}

Missing required field:

{
  "error": "Recipient email is required",
  "statusCode": 400
}

Envelope not found:

{
  "error": "Envelope not found",
  "statusCode": 404
}

Handling Rate Limits

The API has a limit of 1000 requests per minute for each IP address, and your organisation can have a lower limit. Each response includes X-RateLimit-Remaining and X-RateLimit-Reset (an epoch timestamp in seconds). A windowed rate-limit 429 response includes Retry-After. Wait for that number of seconds before you send the request again. A quota 429 response does not include Retry-After because a wait cannot correct the quota error. If Retry-After is not present, do not send the request again automatically.

Refer to Error Handling Patterns for more retry information.

async function fetchWithRetry(url, options, maxRetries = 3) {
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    const response = await fetch(url, options);

    if (response.status === 429) {
      const retryAfter = response.headers.get('Retry-After');

      if (!retryAfter) {
        return response;
      }

      const retryAfterSeconds = Number.parseInt(retryAfter, 10);
      console.log(`Rate limit. Wait ${retryAfterSeconds} seconds...`);
      await new Promise((resolve) => setTimeout(resolve, retryAfterSeconds * 1000));
      continue;
    }

    return response;
  }

  throw new Error('Max retries exceeded');
}

Next Steps

  • API Reference - Full endpoint documentation with request/response schemas
  • Webhooks - Get notified when documents are signed
  • Templates - Create reusable document templates
  • SDKs - Use typed client libraries

Official SDKs

For production applications, consider using the official SDKs:

On this page