Create & Send Envelope (DocuSign-Compatible)
Create and send an envelope using the DocuSign-compatible REST API v2.1. Existing DocuSign integrations only need to change the base URL to migrate to Propper Sign.
Overview
This example demonstrates:
- Creating an envelope with documents and recipients
- Positioning signature fields using XY coordinates
- Positioning signature fields using anchor text
- Sending the envelope immediately or saving as draft
- Handling the response
Prerequisites
- Propper API credentials (client ID and secret)
- A PDF document to send for signing
- Scope:
sign:read sign:write
Endpoint
POST /restapi/v2.1/accounts/{accountId}/envelopes
Set status to "sent" to create AND send immediately, or "created" for a draft.
Example 1: XY-Positioned Tabs
Position signature fields using absolute x,y coordinates on the page.
- cURL
- JavaScript (Node.js)
- Python
curl -X POST "https://api.propper.ai/restapi/v2.1/accounts/$ACCOUNT_ID/envelopes" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"emailSubject": "Please sign: Employment Contract",
"emailBlurb": "Please review and sign the attached employment contract.",
"status": "sent",
"documents": [
{
"documentId": "1",
"name": "Employment_Contract.pdf",
"documentBase64": "<base64-encoded-pdf-content>",
"fileExtension": "pdf",
"order": "1"
}
],
"recipients": {
"signers": [
{
"email": "john.smith@example.com",
"name": "John Smith",
"recipientId": "1",
"routingOrder": "1",
"tabs": {
"signHereTabs": [
{
"tabLabel": "EmployeeSignature",
"documentId": "1",
"pageNumber": "3",
"xPosition": "100",
"yPosition": "500",
"scaleValue": "1.0"
}
],
"dateSignedTabs": [
{
"tabLabel": "EmployeeSignDate",
"documentId": "1",
"pageNumber": "3",
"xPosition": "350",
"yPosition": "500"
}
],
"textTabs": [
{
"tabLabel": "EmployeeTitle",
"documentId": "1",
"pageNumber": "1",
"xPosition": "200",
"yPosition": "300",
"width": "150",
"height": "20",
"required": "true",
"value": ""
}
],
"fullNameTabs": [
{
"tabLabel": "EmployeeFullName",
"documentId": "1",
"pageNumber": "1",
"xPosition": "200",
"yPosition": "250"
}
]
}
},
{
"email": "jane.doe@company.com",
"name": "Jane Doe",
"recipientId": "2",
"routingOrder": "2",
"tabs": {
"signHereTabs": [
{
"tabLabel": "ManagerSignature",
"documentId": "1",
"pageNumber": "3",
"xPosition": "100",
"yPosition": "600"
}
],
"dateSignedTabs": [
{
"tabLabel": "ManagerSignDate",
"documentId": "1",
"pageNumber": "3",
"xPosition": "350",
"yPosition": "600"
}
]
}
}
]
}
}'
import fs from 'fs/promises';
const BASE_URL = 'https://api.propper.ai/restapi/v2.1';
const ACCOUNT_ID = process.env.PROPPER_ACCOUNT_ID;
async function createAndSendEnvelope(pdfPath) {
const token = await getAccessToken(); // See Basic Workflow example
// Read and encode PDF
const pdfBuffer = await fs.readFile(pdfPath);
const documentBase64 = pdfBuffer.toString('base64');
const response = await fetch(`${BASE_URL}/accounts/${ACCOUNT_ID}/envelopes`, {
method: 'POST',
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
emailSubject: 'Please sign: Employment Contract',
emailBlurb: 'Please review and sign the attached employment contract.',
status: 'sent', // Use 'created' for draft
documents: [
{
documentId: '1',
name: 'Employment_Contract.pdf',
documentBase64,
fileExtension: 'pdf',
order: '1',
},
],
recipients: {
signers: [
{
email: 'john.smith@example.com',
name: 'John Smith',
recipientId: '1',
routingOrder: '1',
tabs: {
signHereTabs: [
{
tabLabel: 'EmployeeSignature',
documentId: '1',
pageNumber: '3',
xPosition: '100',
yPosition: '500',
scaleValue: '1.0',
},
],
dateSignedTabs: [
{
tabLabel: 'EmployeeSignDate',
documentId: '1',
pageNumber: '3',
xPosition: '350',
yPosition: '500',
},
],
textTabs: [
{
tabLabel: 'EmployeeTitle',
documentId: '1',
pageNumber: '1',
xPosition: '200',
yPosition: '300',
width: '150',
height: '20',
required: 'true',
},
],
fullNameTabs: [
{
tabLabel: 'EmployeeFullName',
documentId: '1',
pageNumber: '1',
xPosition: '200',
yPosition: '250',
},
],
},
},
{
email: 'jane.doe@company.com',
name: 'Jane Doe',
recipientId: '2',
routingOrder: '2',
tabs: {
signHereTabs: [
{
tabLabel: 'ManagerSignature',
documentId: '1',
pageNumber: '3',
xPosition: '100',
yPosition: '600',
},
],
dateSignedTabs: [
{
tabLabel: 'ManagerSignDate',
documentId: '1',
pageNumber: '3',
xPosition: '350',
yPosition: '600',
},
],
},
},
],
},
}),
});
if (!response.ok) {
const error = await response.json();
throw new Error(`${error.errorCode}: ${error.message}`);
}
return response.json();
}
// Usage
const envelope = await createAndSendEnvelope('./contract.pdf');
console.log(`Envelope ID: ${envelope.envelopeId}`);
console.log(`Status: ${envelope.status}`);
import os
import base64
from pathlib import Path
import requests
BASE_URL = "https://api.propper.ai/restapi/v2.1"
ACCOUNT_ID = os.environ["PROPPER_ACCOUNT_ID"]
def create_and_send_envelope(pdf_path: str, token: str) -> dict:
"""Create and send an envelope with XY-positioned tabs."""
# Read and encode PDF
pdf_content = Path(pdf_path).read_bytes()
document_base64 = base64.b64encode(pdf_content).decode("utf-8")
response = requests.post(
f"{BASE_URL}/accounts/{ACCOUNT_ID}/envelopes",
headers={
"Authorization": f"Bearer {token}",
"Content-Type": "application/json",
},
json={
"emailSubject": "Please sign: Employment Contract",
"emailBlurb": "Please review and sign the attached employment contract.",
"status": "sent", # Use "created" for draft
"documents": [
{
"documentId": "1",
"name": "Employment_Contract.pdf",
"documentBase64": document_base64,
"fileExtension": "pdf",
"order": "1",
}
],
"recipients": {
"signers": [
{
"email": "john.smith@example.com",
"name": "John Smith",
"recipientId": "1",
"routingOrder": "1",
"tabs": {
"signHereTabs": [
{
"tabLabel": "EmployeeSignature",
"documentId": "1",
"pageNumber": "3",
"xPosition": "100",
"yPosition": "500",
"scaleValue": "1.0",
}
],
"dateSignedTabs": [
{
"tabLabel": "EmployeeSignDate",
"documentId": "1",
"pageNumber": "3",
"xPosition": "350",
"yPosition": "500",
}
],
"textTabs": [
{
"tabLabel": "EmployeeTitle",
"documentId": "1",
"pageNumber": "1",
"xPosition": "200",
"yPosition": "300",
"width": "150",
"height": "20",
"required": "true",
}
],
"fullNameTabs": [
{
"tabLabel": "EmployeeFullName",
"documentId": "1",
"pageNumber": "1",
"xPosition": "200",
"yPosition": "250",
}
],
},
},
{
"email": "jane.doe@company.com",
"name": "Jane Doe",
"recipientId": "2",
"routingOrder": "2",
"tabs": {
"signHereTabs": [
{
"tabLabel": "ManagerSignature",
"documentId": "1",
"pageNumber": "3",
"xPosition": "100",
"yPosition": "600",
}
],
"dateSignedTabs": [
{
"tabLabel": "ManagerSignDate",
"documentId": "1",
"pageNumber": "3",
"xPosition": "350",
"yPosition": "600",
}
],
},
},
]
},
},
)
response.raise_for_status()
return response.json()
# Usage
token = get_access_token() # See Basic Workflow example
envelope = create_and_send_envelope("./contract.pdf", token)
print(f"Envelope ID: {envelope['envelopeId']}")
print(f"Status: {envelope['status']}")
Response
{
"envelopeId": "550e8400-e29b-41d4-a716-446655440000",
"status": "sent",
"statusDateTime": "2026-02-04T10:30:00.000Z",
"uri": "/envelopes/550e8400-e29b-41d4-a716-446655440000"
}
Example 2: Anchor Text Positioning
Position tabs relative to text found in the document. This is more resilient to document layout changes than XY coordinates.
tip
Use unique anchor markers like /sig1/, /date1/, /init1/ in your document templates for reliable positioning.
- cURL
- JavaScript (Node.js)
- Python
curl -X POST "https://api.propper.ai/restapi/v2.1/accounts/$ACCOUNT_ID/envelopes" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"emailSubject": "Please sign: Employment Contract",
"emailBlurb": "Please review and sign the attached employment contract.",
"status": "sent",
"documents": [
{
"documentId": "1",
"name": "Employment_Contract.pdf",
"documentBase64": "<base64-encoded-pdf-content>",
"fileExtension": "pdf",
"order": "1"
}
],
"recipients": {
"signers": [
{
"email": "john.smith@example.com",
"name": "John Smith",
"recipientId": "1",
"routingOrder": "1",
"tabs": {
"signHereTabs": [
{
"tabLabel": "EmployeeSignature",
"anchorString": "/sig1/",
"anchorXOffset": "0",
"anchorYOffset": "-10",
"anchorUnits": "pixels",
"anchorIgnoreIfNotPresent": "false",
"scaleValue": "1.0"
}
],
"dateSignedTabs": [
{
"tabLabel": "EmployeeSignDate",
"anchorString": "/date1/",
"anchorXOffset": "0",
"anchorYOffset": "0",
"anchorUnits": "pixels"
}
],
"textTabs": [
{
"tabLabel": "EmployeeTitle",
"anchorString": "Job Title:",
"anchorXOffset": "100",
"anchorYOffset": "0",
"anchorUnits": "pixels",
"width": "150",
"height": "20",
"required": "true"
},
{
"tabLabel": "StartDate",
"anchorString": "Start Date:",
"anchorXOffset": "100",
"anchorYOffset": "0",
"anchorUnits": "pixels",
"width": "100",
"required": "true"
}
],
"initialHereTabs": [
{
"tabLabel": "EmployeeInitials",
"anchorString": "/init1/",
"anchorXOffset": "0",
"anchorYOffset": "0",
"anchorUnits": "pixels",
"scaleValue": "0.5"
}
],
"checkboxTabs": [
{
"tabLabel": "AgreeToTerms",
"anchorString": "I agree to the terms",
"anchorXOffset": "-25",
"anchorYOffset": "0",
"anchorUnits": "pixels",
"required": "true"
}
]
}
},
{
"email": "jane.doe@company.com",
"name": "Jane Doe",
"recipientId": "2",
"routingOrder": "2",
"tabs": {
"signHereTabs": [
{
"tabLabel": "ManagerSignature",
"anchorString": "/sig2/",
"anchorXOffset": "0",
"anchorYOffset": "-10",
"anchorUnits": "pixels"
}
],
"dateSignedTabs": [
{
"tabLabel": "ManagerSignDate",
"anchorString": "/date2/",
"anchorXOffset": "0",
"anchorYOffset": "0",
"anchorUnits": "pixels"
}
]
}
}
]
}
}'
import fs from 'fs/promises';
const BASE_URL = 'https://api.propper.ai/restapi/v2.1';
const ACCOUNT_ID = process.env.PROPPER_ACCOUNT_ID;
async function createEnvelopeWithAnchors(pdfPath) {
const token = await getAccessToken(); // See Basic Workflow example
const pdfBuffer = await fs.readFile(pdfPath);
const documentBase64 = pdfBuffer.toString('base64');
const response = await fetch(`${BASE_URL}/accounts/${ACCOUNT_ID}/envelopes`, {
method: 'POST',
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
emailSubject: 'Please sign: Employment Contract',
emailBlurb: 'Please review and sign the attached employment contract.',
status: 'sent',
documents: [
{
documentId: '1',
name: 'Employment_Contract.pdf',
documentBase64,
fileExtension: 'pdf',
order: '1',
},
],
recipients: {
signers: [
{
email: 'john.smith@example.com',
name: 'John Smith',
recipientId: '1',
routingOrder: '1',
tabs: {
signHereTabs: [
{
tabLabel: 'EmployeeSignature',
anchorString: '/sig1/',
anchorXOffset: '0',
anchorYOffset: '-10',
anchorUnits: 'pixels',
anchorIgnoreIfNotPresent: 'false',
scaleValue: '1.0',
},
],
dateSignedTabs: [
{
tabLabel: 'EmployeeSignDate',
anchorString: '/date1/',
anchorXOffset: '0',
anchorYOffset: '0',
anchorUnits: 'pixels',
},
],
textTabs: [
{
tabLabel: 'EmployeeTitle',
anchorString: 'Job Title:',
anchorXOffset: '100',
anchorYOffset: '0',
anchorUnits: 'pixels',
width: '150',
height: '20',
required: 'true',
},
{
tabLabel: 'StartDate',
anchorString: 'Start Date:',
anchorXOffset: '100',
anchorYOffset: '0',
anchorUnits: 'pixels',
width: '100',
required: 'true',
},
],
initialHereTabs: [
{
tabLabel: 'EmployeeInitials',
anchorString: '/init1/',
anchorXOffset: '0',
anchorYOffset: '0',
anchorUnits: 'pixels',
scaleValue: '0.5',
},
],
checkboxTabs: [
{
tabLabel: 'AgreeToTerms',
anchorString: 'I agree to the terms',
anchorXOffset: '-25',
anchorYOffset: '0',
anchorUnits: 'pixels',
required: 'true',
},
],
},
},
{
email: 'jane.doe@company.com',
name: 'Jane Doe',
recipientId: '2',
routingOrder: '2',
tabs: {
signHereTabs: [
{
tabLabel: 'ManagerSignature',
anchorString: '/sig2/',
anchorXOffset: '0',
anchorYOffset: '-10',
anchorUnits: 'pixels',
},
],
dateSignedTabs: [
{
tabLabel: 'ManagerSignDate',
anchorString: '/date2/',
anchorXOffset: '0',
anchorYOffset: '0',
anchorUnits: 'pixels',
},
],
},
},
],
},
}),
});
if (!response.ok) {
const error = await response.json();
throw new Error(`${error.errorCode}: ${error.message}`);
}
return response.json();
}
// Usage
const envelope = await createEnvelopeWithAnchors('./contract.pdf');
console.log(`Envelope ID: ${envelope.envelopeId}`);
import os
import base64
from pathlib import Path
import requests
BASE_URL = "https://api.propper.ai/restapi/v2.1"
ACCOUNT_ID = os.environ["PROPPER_ACCOUNT_ID"]
def create_envelope_with_anchors(pdf_path: str, token: str) -> dict:
"""Create and send an envelope with anchor-positioned tabs."""
pdf_content = Path(pdf_path).read_bytes()
document_base64 = base64.b64encode(pdf_content).decode("utf-8")
response = requests.post(
f"{BASE_URL}/accounts/{ACCOUNT_ID}/envelopes",
headers={
"Authorization": f"Bearer {token}",
"Content-Type": "application/json",
},
json={
"emailSubject": "Please sign: Employment Contract",
"emailBlurb": "Please review and sign the attached employment contract.",
"status": "sent",
"documents": [
{
"documentId": "1",
"name": "Employment_Contract.pdf",
"documentBase64": document_base64,
"fileExtension": "pdf",
"order": "1",
}
],
"recipients": {
"signers": [
{
"email": "john.smith@example.com",
"name": "John Smith",
"recipientId": "1",
"routingOrder": "1",
"tabs": {
"signHereTabs": [
{
"tabLabel": "EmployeeSignature",
"anchorString": "/sig1/",
"anchorXOffset": "0",
"anchorYOffset": "-10",
"anchorUnits": "pixels",
"anchorIgnoreIfNotPresent": "false",
"scaleValue": "1.0",
}
],
"dateSignedTabs": [
{
"tabLabel": "EmployeeSignDate",
"anchorString": "/date1/",
"anchorXOffset": "0",
"anchorYOffset": "0",
"anchorUnits": "pixels",
}
],
"textTabs": [
{
"tabLabel": "EmployeeTitle",
"anchorString": "Job Title:",
"anchorXOffset": "100",
"anchorYOffset": "0",
"anchorUnits": "pixels",
"width": "150",
"height": "20",
"required": "true",
},
{
"tabLabel": "StartDate",
"anchorString": "Start Date:",
"anchorXOffset": "100",
"anchorYOffset": "0",
"anchorUnits": "pixels",
"width": "100",
"required": "true",
},
],
"initialHereTabs": [
{
"tabLabel": "EmployeeInitials",
"anchorString": "/init1/",
"anchorXOffset": "0",
"anchorYOffset": "0",
"anchorUnits": "pixels",
"scaleValue": "0.5",
}
],
"checkboxTabs": [
{
"tabLabel": "AgreeToTerms",
"anchorString": "I agree to the terms",
"anchorXOffset": "-25",
"anchorYOffset": "0",
"anchorUnits": "pixels",
"required": "true",
}
],
},
},
{
"email": "jane.doe@company.com",
"name": "Jane Doe",
"recipientId": "2",
"routingOrder": "2",
"tabs": {
"signHereTabs": [
{
"tabLabel": "ManagerSignature",
"anchorString": "/sig2/",
"anchorXOffset": "0",
"anchorYOffset": "-10",
"anchorUnits": "pixels",
}
],
"dateSignedTabs": [
{
"tabLabel": "ManagerSignDate",
"anchorString": "/date2/",
"anchorXOffset": "0",
"anchorYOffset": "0",
"anchorUnits": "pixels",
}
],
},
},
]
},
},
)
response.raise_for_status()
return response.json()
# Usage
token = get_access_token() # See Basic Workflow example
envelope = create_envelope_with_anchors("./contract.pdf", token)
print(f"Envelope ID: {envelope['envelopeId']}")
Tab Types Reference
| Tab Array | Description |
|---|---|
signHereTabs | Signature field - recipient draws or types signature |
initialHereTabs | Initials field - recipient adds initials |
dateSignedTabs | Auto-populated date when recipient signs |
textTabs | Text input field for free-form text |
numberTabs | Numeric input field |
checkboxTabs | Checkbox field |
radioGroupTabs | Radio button group |
listTabs | Dropdown list selection |
emailTabs | Email input field |
emailAddressTabs | Auto-populated from recipient email |
fullNameTabs | Auto-populated from recipient name |
formulaTabs | Calculated field based on other tab values |
Positioning Options
XY Coordinates
| Field | Description |
|---|---|
documentId | Which document (matches documentId in documents array) |
pageNumber | Page number (1-indexed) |
xPosition | Horizontal position in pixels from left edge |
yPosition | Vertical position in pixels from top edge |
Anchor Text
| Field | Description |
|---|---|
anchorString | Text to search for in the document |
anchorXOffset | Horizontal offset from anchor text in pixels |
anchorYOffset | Vertical offset from anchor text in pixels |
anchorUnits | Unit of measurement (pixels, inches, mms, cms) |
anchorIgnoreIfNotPresent | If "true", tab is skipped if anchor not found |
anchorCaseSensitive | If "true", anchor search is case-sensitive |
anchorMatchWholeWord | If "true", anchor must match whole word |
Common Tab Properties
| Property | Description |
|---|---|
tabLabel | Unique identifier for the tab |
required | "true" or "false" - whether tab must be completed |
locked | "true" or "false" - whether tab value is read-only |
width / height | Tab dimensions in pixels |
font | Font name (e.g., arial, helvetica) |
fontSize | Font size (e.g., size9, size11, size14) |
fontColor | Font color (e.g., black, blue, #FF0000) |
bold / italic / underline | Text styling ("true" or "false") |
scaleValue | Scale factor for signatures/initials (e.g., "0.5", "1.0") |
Error Responses
| Status | Error Code | Message |
|---|---|---|
| 400 | INVALID_REQUEST_BODY | Email subject is required |
| 400 | FEATURE_NOT_SUPPORTED | Feature 'compositeTemplates' is not supported by Propper Sign |
| 401 | AUTHORIZATION_INVALID_TOKEN | Invalid or expired token |
Limits
| Limit | Value |
|---|---|
| Max documents per envelope | 10 |
| Max document size | 25 MB |
| Max recipients | 50 |
| Max subject length | 200 characters |
| Max message length | 2,000 characters |
Next Steps
- Create & Send Agreement (Native API) - Use the native Propper API
- Basic Signing Workflow - End-to-end workflow with webhooks
- Migration from DocuSign - Complete migration guide