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"
const response = await fetch('https://app.documenso.com/api/v2/envelope', {
  method: 'GET',
  headers: {
    'Authorization': 'YOUR_API_TOKEN',
  },
});

const data = await response.json();
console.log(data);

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"
    }
  ],
  "pagination": {
    "page": 1,
    "perPage": 10,
    "totalPages": 1,
    "totalItems": 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"
const fs = require('fs');
const FormData = require('form-data');

const form = new FormData();

const 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,
        },
      ],
    },
  ],
};

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

const response = await fetch('https://app.documenso.com/api/v2/envelope/create', {
  method: 'POST',
  headers: {
    Authorization: 'YOUR_API_TOKEN',
  },
  body: form,
});

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

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/envelope_abc123/distribute" \
  -H "Authorization: YOUR_API_TOKEN" \
  -H "Content-Type: application/json"
const envelopeId = 'envelope_abc123';

const response = await fetch(
  `https://app.documenso.com/api/v2/envelope/${envelopeId}/distribute`,
  {
    method: 'POST',
    headers: {
      Authorization: 'YOUR_API_TOKEN',
      'Content-Type': 'application/json',
    },
  },
);

const data = await response.json();
console.log('Document sent:', data);

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/${envelope.id}/distribute`,
    {
      method: 'POST',
      headers: {
        'Authorization': API_TOKEN,
        'Content-Type': 'application/json',
      },
    }
  );

  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);
#!/bin/bash
set -e

API_TOKEN="YOUR_API_TOKEN"
BASE_URL="https://app.documenso.com/api/v2"
PDF_FILE="./contract.pdf"
RECIPIENT_EMAIL="signer@example.com"
RECIPIENT_NAME="John Smith"

# Step 1: Create the envelope with recipient and fields

echo "Creating envelope..."
ENVELOPE_RESPONSE=$(curl -s -X POST "${BASE_URL}/envelope/create" \
  -H "Authorization: ${API_TOKEN}" \
  -H "Content-Type: multipart/form-data" \
  -F "payload={
    \"type\": \"DOCUMENT\",
    \"title\": \"Service Agreement\",
    \"recipients\": [
      {
        \"email\": \"${RECIPIENT_EMAIL}\",
        \"name\": \"${RECIPIENT_NAME}\",
        \"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=@${PDF_FILE};type=application/pdf")

ENVELOPE_ID=$(echo $ENVELOPE_RESPONSE | jq -r '.id')
echo "Created envelope: ${ENVELOPE_ID}"

# Step 2: Send the document for signing

echo "Sending document..."
curl -s -X POST "${BASE_URL}/envelope/${ENVELOPE_ID}/distribute" \
  -H "Authorization: ${API_TOKEN}" \
  -H "Content-Type: application/json"

echo "Document sent for signing!"

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 limited - wait 60 seconds and retry
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 allows 1000 requests per minute per IP address. Your organisation may have its own lower rate limits. When rate limited, wait at least 60 seconds before retrying:

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

    if (response.status === 429) {
      console.log('Rate limited, waiting 60 seconds...');
      await new Promise((resolve) => setTimeout(resolve, 60000));
      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