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.

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 100 requests per minute per IP address. 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