Create & Send Agreement
Create and send an agreement using the Propper Sign API. This API uses a simpler, flat tab structure compared to the DocuSign-compatible API.
Overview
This example demonstrates:
- Creating an agreement with a basic request (no tabs)
- Creating an agreement with XY-positioned tabs
- Creating an agreement with anchor text-positioned tabs
- Handling draft vs. sent status
- Using the separate send endpoint for drafts
Prerequisites
- Propper API credentials (client ID and secret)
- A PDF document to send for signing
- Scope:
sign:write
Endpoint
POST /v1/sign/agreements
Set status to "SENT" to create AND send immediately, or "CREATED" for a draft.
Example 1: Basic Agreement (No Tabs)
The simplest case - recipients sign wherever they choose on the document.
- cURL
- JavaScript (Node.js)
- Python
curl -X POST https://api.propper.ai/v1/sign/agreements \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Employment Contract - John Smith",
"status": "SENT",
"emailSubject": "Please sign: Employment Contract",
"emailMessage": "Please review and sign the attached employment contract.",
"type": "SEQUENTIAL",
"recipients": [
{
"email": "john.smith@example.com",
"name": "John Smith",
"role": "Employee",
"order": 1
},
{
"email": "jane.doe@company.com",
"name": "Jane Doe",
"role": "Manager",
"order": 2
}
],
"documents": [
{
"name": "Employment_Contract.pdf",
"documentBase64": "<base64-encoded-pdf-content>",
"mimeType": "application/pdf",
"order": 1
}
]
}'
import fs from 'fs/promises';
const BASE_URL = 'https://api.propper.ai/v1/sign';
async function createBasicAgreement(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}/agreements`, {
method: 'POST',
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
name: 'Employment Contract - John Smith',
status: 'SENT', // Use 'CREATED' for draft
emailSubject: 'Please sign: Employment Contract',
emailMessage: 'Please review and sign the attached employment contract.',
type: 'SEQUENTIAL',
recipients: [
{
email: 'john.smith@example.com',
name: 'John Smith',
role: 'Employee',
order: 1,
},
{
email: 'jane.doe@company.com',
name: 'Jane Doe',
role: 'Manager',
order: 2,
},
],
documents: [
{
name: 'Employment_Contract.pdf',
documentBase64,
mimeType: 'application/pdf',
order: 1,
},
],
}),
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.error);
}
return response.json();
}
// Usage
const result = await createBasicAgreement('./contract.pdf');
console.log(`Agreement ID: ${result.agreement.id}`);
console.log(`Status: ${result.agreement.status}`);
console.log(`Emails sent: ${result.emailsSent}`);
import os
import base64
from pathlib import Path
import requests
BASE_URL = "https://api.propper.ai/v1/sign"
def create_basic_agreement(pdf_path: str, token: str) -> dict:
"""Create and send a basic agreement without tabs."""
pdf_content = Path(pdf_path).read_bytes()
document_base64 = base64.b64encode(pdf_content).decode("utf-8")
response = requests.post(
f"{BASE_URL}/agreements",
headers={
"Authorization": f"Bearer {token}",
"Content-Type": "application/json",
},
json={
"name": "Employment Contract - John Smith",
"status": "SENT", # Use "CREATED" for draft
"emailSubject": "Please sign: Employment Contract",
"emailMessage": "Please review and sign the attached employment contract.",
"type": "SEQUENTIAL",
"recipients": [
{
"email": "john.smith@example.com",
"name": "John Smith",
"role": "Employee",
"order": 1,
},
{
"email": "jane.doe@company.com",
"name": "Jane Doe",
"role": "Manager",
"order": 2,
},
],
"documents": [
{
"name": "Employment_Contract.pdf",
"documentBase64": document_base64,
"mimeType": "application/pdf",
"order": 1,
}
],
},
)
response.raise_for_status()
return response.json()
# Usage
token = get_access_token() # See Basic Workflow example
result = create_basic_agreement("./contract.pdf", token)
print(f"Agreement ID: {result['agreement']['id']}")
print(f"Status: {result['agreement']['status']}")
Response (Sent)
{
"agreement": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "Employment Contract - John Smith",
"status": "SENT",
"type": "SEQUENTIAL",
"emailSubject": "Please sign: Employment Contract",
"emailMessage": "Please review and sign the attached employment contract.",
"expiresAt": null,
"createdAt": "2026-02-04T10:30:00.000Z",
"updatedAt": "2026-02-04T10:30:00.000Z",
"completedAt": null,
"recipients": [
{
"id": "recipient-uuid-1",
"email": "john.smith@example.com",
"name": "John Smith",
"role": "Employee",
"order": 1,
"status": "PENDING",
"signedAt": null
}
],
"sourceDocuments": [
{
"id": "document-uuid-1",
"filename": "Employment_Contract.pdf",
"mimeType": "application/pdf",
"pageCount": 3,
"order": 1
}
]
},
"emailsSent": 1,
"signingUrls": [
{
"recipientId": "recipient-uuid-1",
"url": "https://sign.propper.ai/sign/abc123..."
}
]
}
Response (Draft)
When status is "CREATED":
{
"agreement": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "Employment Contract - John Smith",
"status": "CREATED",
"type": "SEQUENTIAL",
"emailSubject": "Please sign: Employment Contract",
"emailMessage": "Please review and sign the attached employment contract.",
"expiresAt": null,
"createdAt": "2026-02-04T10:30:00.000Z",
"updatedAt": "2026-02-04T10:30:00.000Z",
"completedAt": null
}
}
Send a draft agreement later using POST /v1/sign/agreements/{agreementId}/send (requires sign:send scope).
Example 2: Agreement with XY-Positioned Tabs
Position tabs using absolute x,y coordinates in PDF points (1/72 inch). Note that page indices are 0-based in the native API.
- cURL
- JavaScript (Node.js)
- Python
curl -X POST https://api.propper.ai/v1/sign/agreements \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Employment Contract with Tabs",
"status": "SENT",
"emailSubject": "Please sign: Employment Contract",
"emailMessage": "Please review and sign the attached contract.",
"type": "SEQUENTIAL",
"recipients": [
{
"email": "john.smith@example.com",
"name": "John Smith",
"role": "Employee",
"order": 1,
"tabs": [
{
"type": "SIGNATURE",
"pageIndex": 2,
"rect": { "x": 100, "y": 500, "width": 150, "height": 40 },
"isRequired": true,
"label": "EmployeeSignature"
},
{
"type": "DATE_SIGNED",
"pageIndex": 2,
"rect": { "x": 350, "y": 500, "width": 100, "height": 20 },
"label": "EmployeeSignDate"
},
{
"type": "TEXT",
"pageIndex": 0,
"rect": { "x": 200, "y": 300, "width": 150, "height": 20 },
"isRequired": true,
"label": "EmployeeTitle",
"placeholder": "Enter your job title"
},
{
"type": "FULL_NAME",
"pageIndex": 0,
"rect": { "x": 200, "y": 250, "width": 200, "height": 20 },
"label": "EmployeeFullName"
}
]
},
{
"email": "jane.doe@company.com",
"name": "Jane Doe",
"role": "Manager",
"order": 2,
"tabs": [
{
"type": "SIGNATURE",
"pageIndex": 2,
"rect": { "x": 100, "y": 600, "width": 150, "height": 40 },
"isRequired": true,
"label": "ManagerSignature"
},
{
"type": "DATE_SIGNED",
"pageIndex": 2,
"rect": { "x": 350, "y": 600, "width": 100, "height": 20 },
"label": "ManagerSignDate"
}
]
}
],
"documents": [
{
"name": "Employment_Contract.pdf",
"documentBase64": "<base64-encoded-pdf-content>",
"mimeType": "application/pdf",
"order": 1
}
]
}'
import fs from 'fs/promises';
const BASE_URL = 'https://api.propper.ai/v1/sign';
async function createAgreementWithTabs(pdfPath) {
const token = await getAccessToken();
const pdfBuffer = await fs.readFile(pdfPath);
const documentBase64 = pdfBuffer.toString('base64');
const response = await fetch(`${BASE_URL}/agreements`, {
method: 'POST',
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
name: 'Employment Contract with Tabs',
status: 'SENT',
emailSubject: 'Please sign: Employment Contract',
emailMessage: 'Please review and sign the attached contract.',
type: 'SEQUENTIAL',
recipients: [
{
email: 'john.smith@example.com',
name: 'John Smith',
role: 'Employee',
order: 1,
tabs: [
{
type: 'SIGNATURE',
pageIndex: 2,
rect: { x: 100, y: 500, width: 150, height: 40 },
isRequired: true,
label: 'EmployeeSignature',
},
{
type: 'DATE_SIGNED',
pageIndex: 2,
rect: { x: 350, y: 500, width: 100, height: 20 },
label: 'EmployeeSignDate',
},
{
type: 'TEXT',
pageIndex: 0,
rect: { x: 200, y: 300, width: 150, height: 20 },
isRequired: true,
label: 'EmployeeTitle',
placeholder: 'Enter your job title',
},
{
type: 'FULL_NAME',
pageIndex: 0,
rect: { x: 200, y: 250, width: 200, height: 20 },
label: 'EmployeeFullName',
},
],
},
{
email: 'jane.doe@company.com',
name: 'Jane Doe',
role: 'Manager',
order: 2,
tabs: [
{
type: 'SIGNATURE',
pageIndex: 2,
rect: { x: 100, y: 600, width: 150, height: 40 },
isRequired: true,
label: 'ManagerSignature',
},
{
type: 'DATE_SIGNED',
pageIndex: 2,
rect: { x: 350, y: 600, width: 100, height: 20 },
label: 'ManagerSignDate',
},
],
},
],
documents: [
{
name: 'Employment_Contract.pdf',
documentBase64,
mimeType: 'application/pdf',
order: 1,
},
],
}),
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.error);
}
return response.json();
}
// Usage
const result = await createAgreementWithTabs('./contract.pdf');
console.log(`Agreement ID: ${result.agreement.id}`);
import os
import base64
from pathlib import Path
import requests
BASE_URL = "https://api.propper.ai/v1/sign"
def create_agreement_with_tabs(pdf_path: str, token: str) -> dict:
"""Create and send an agreement with XY-positioned tabs."""
pdf_content = Path(pdf_path).read_bytes()
document_base64 = base64.b64encode(pdf_content).decode("utf-8")
response = requests.post(
f"{BASE_URL}/agreements",
headers={
"Authorization": f"Bearer {token}",
"Content-Type": "application/json",
},
json={
"name": "Employment Contract with Tabs",
"status": "SENT",
"emailSubject": "Please sign: Employment Contract",
"emailMessage": "Please review and sign the attached contract.",
"type": "SEQUENTIAL",
"recipients": [
{
"email": "john.smith@example.com",
"name": "John Smith",
"role": "Employee",
"order": 1,
"tabs": [
{
"type": "SIGNATURE",
"pageIndex": 2,
"rect": {"x": 100, "y": 500, "width": 150, "height": 40},
"isRequired": True,
"label": "EmployeeSignature",
},
{
"type": "DATE_SIGNED",
"pageIndex": 2,
"rect": {"x": 350, "y": 500, "width": 100, "height": 20},
"label": "EmployeeSignDate",
},
{
"type": "TEXT",
"pageIndex": 0,
"rect": {"x": 200, "y": 300, "width": 150, "height": 20},
"isRequired": True,
"label": "EmployeeTitle",
"placeholder": "Enter your job title",
},
{
"type": "FULL_NAME",
"pageIndex": 0,
"rect": {"x": 200, "y": 250, "width": 200, "height": 20},
"label": "EmployeeFullName",
},
],
},
{
"email": "jane.doe@company.com",
"name": "Jane Doe",
"role": "Manager",
"order": 2,
"tabs": [
{
"type": "SIGNATURE",
"pageIndex": 2,
"rect": {"x": 100, "y": 600, "width": 150, "height": 40},
"isRequired": True,
"label": "ManagerSignature",
},
{
"type": "DATE_SIGNED",
"pageIndex": 2,
"rect": {"x": 350, "y": 600, "width": 100, "height": 20},
"label": "ManagerSignDate",
},
],
},
],
"documents": [
{
"name": "Employment_Contract.pdf",
"documentBase64": document_base64,
"mimeType": "application/pdf",
"order": 1,
}
],
},
)
response.raise_for_status()
return response.json()
# Usage
token = get_access_token()
result = create_agreement_with_tabs("./contract.pdf", token)
print(f"Agreement ID: {result['agreement']['id']}")
Example 3: Agreement with Anchor-Positioned Tabs
Position tabs relative to text found in the document. More resilient to layout changes than XY coordinates.
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/v1/sign/agreements \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Employment Contract with Anchor Tabs",
"status": "SENT",
"emailSubject": "Please sign: Employment Contract",
"emailMessage": "Please review and sign the attached contract.",
"type": "SEQUENTIAL",
"recipients": [
{
"email": "john.smith@example.com",
"name": "John Smith",
"role": "Employee",
"order": 1,
"tabs": [
{
"type": "SIGNATURE",
"pageIndex": 0,
"anchorString": "/sig1/",
"anchorXOffset": 0,
"anchorYOffset": -10,
"isRequired": true,
"label": "EmployeeSignature"
},
{
"type": "DATE_SIGNED",
"pageIndex": 0,
"anchorString": "/date1/",
"anchorXOffset": 0,
"anchorYOffset": 0,
"label": "EmployeeSignDate"
},
{
"type": "TEXT",
"pageIndex": 0,
"anchorString": "Job Title:",
"anchorXOffset": 100,
"anchorYOffset": 0,
"isRequired": true,
"label": "EmployeeTitle"
},
{
"type": "INITIAL",
"pageIndex": 0,
"anchorString": "/init1/",
"anchorXOffset": 0,
"anchorYOffset": 0,
"label": "EmployeeInitials"
},
{
"type": "CHECKBOX",
"pageIndex": 0,
"anchorString": "I agree to the terms",
"anchorXOffset": -25,
"anchorYOffset": 0,
"isRequired": true,
"label": "AgreeToTerms"
}
]
},
{
"email": "jane.doe@company.com",
"name": "Jane Doe",
"role": "Manager",
"order": 2,
"tabs": [
{
"type": "SIGNATURE",
"pageIndex": 0,
"anchorString": "/sig2/",
"anchorXOffset": 0,
"anchorYOffset": -10,
"isRequired": true,
"label": "ManagerSignature"
},
{
"type": "DATE_SIGNED",
"pageIndex": 0,
"anchorString": "/date2/",
"anchorXOffset": 0,
"anchorYOffset": 0,
"label": "ManagerSignDate"
}
]
}
],
"documents": [
{
"name": "Employment_Contract.pdf",
"documentBase64": "<base64-encoded-pdf-content>",
"mimeType": "application/pdf",
"order": 1
}
]
}'
import fs from 'fs/promises';
const BASE_URL = 'https://api.propper.ai/v1/sign';
async function createAgreementWithAnchors(pdfPath) {
const token = await getAccessToken();
const pdfBuffer = await fs.readFile(pdfPath);
const documentBase64 = pdfBuffer.toString('base64');
const response = await fetch(`${BASE_URL}/agreements`, {
method: 'POST',
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
name: 'Employment Contract with Anchor Tabs',
status: 'SENT',
emailSubject: 'Please sign: Employment Contract',
emailMessage: 'Please review and sign the attached contract.',
type: 'SEQUENTIAL',
recipients: [
{
email: 'john.smith@example.com',
name: 'John Smith',
role: 'Employee',
order: 1,
tabs: [
{
type: 'SIGNATURE',
pageIndex: 0,
anchorString: '/sig1/',
anchorXOffset: 0,
anchorYOffset: -10,
isRequired: true,
label: 'EmployeeSignature',
},
{
type: 'DATE_SIGNED',
pageIndex: 0,
anchorString: '/date1/',
anchorXOffset: 0,
anchorYOffset: 0,
label: 'EmployeeSignDate',
},
{
type: 'TEXT',
pageIndex: 0,
anchorString: 'Job Title:',
anchorXOffset: 100,
anchorYOffset: 0,
isRequired: true,
label: 'EmployeeTitle',
},
{
type: 'INITIAL',
pageIndex: 0,
anchorString: '/init1/',
anchorXOffset: 0,
anchorYOffset: 0,
label: 'EmployeeInitials',
},
{
type: 'CHECKBOX',
pageIndex: 0,
anchorString: 'I agree to the terms',
anchorXOffset: -25,
anchorYOffset: 0,
isRequired: true,
label: 'AgreeToTerms',
},
],
},
{
email: 'jane.doe@company.com',
name: 'Jane Doe',
role: 'Manager',
order: 2,
tabs: [
{
type: 'SIGNATURE',
pageIndex: 0,
anchorString: '/sig2/',
anchorXOffset: 0,
anchorYOffset: -10,
isRequired: true,
label: 'ManagerSignature',
},
{
type: 'DATE_SIGNED',
pageIndex: 0,
anchorString: '/date2/',
anchorXOffset: 0,
anchorYOffset: 0,
label: 'ManagerSignDate',
},
],
},
],
documents: [
{
name: 'Employment_Contract.pdf',
documentBase64,
mimeType: 'application/pdf',
order: 1,
},
],
}),
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.error);
}
return response.json();
}
// Usage
const result = await createAgreementWithAnchors('./contract.pdf');
console.log(`Agreement ID: ${result.agreement.id}`);
import os
import base64
from pathlib import Path
import requests
BASE_URL = "https://api.propper.ai/v1/sign"
def create_agreement_with_anchors(pdf_path: str, token: str) -> dict:
"""Create and send an agreement 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}/agreements",
headers={
"Authorization": f"Bearer {token}",
"Content-Type": "application/json",
},
json={
"name": "Employment Contract with Anchor Tabs",
"status": "SENT",
"emailSubject": "Please sign: Employment Contract",
"emailMessage": "Please review and sign the attached contract.",
"type": "SEQUENTIAL",
"recipients": [
{
"email": "john.smith@example.com",
"name": "John Smith",
"role": "Employee",
"order": 1,
"tabs": [
{
"type": "SIGNATURE",
"pageIndex": 0,
"anchorString": "/sig1/",
"anchorXOffset": 0,
"anchorYOffset": -10,
"isRequired": True,
"label": "EmployeeSignature",
},
{
"type": "DATE_SIGNED",
"pageIndex": 0,
"anchorString": "/date1/",
"anchorXOffset": 0,
"anchorYOffset": 0,
"label": "EmployeeSignDate",
},
{
"type": "TEXT",
"pageIndex": 0,
"anchorString": "Job Title:",
"anchorXOffset": 100,
"anchorYOffset": 0,
"isRequired": True,
"label": "EmployeeTitle",
},
{
"type": "INITIAL",
"pageIndex": 0,
"anchorString": "/init1/",
"anchorXOffset": 0,
"anchorYOffset": 0,
"label": "EmployeeInitials",
},
{
"type": "CHECKBOX",
"pageIndex": 0,
"anchorString": "I agree to the terms",
"anchorXOffset": -25,
"anchorYOffset": 0,
"isRequired": True,
"label": "AgreeToTerms",
},
],
},
{
"email": "jane.doe@company.com",
"name": "Jane Doe",
"role": "Manager",
"order": 2,
"tabs": [
{
"type": "SIGNATURE",
"pageIndex": 0,
"anchorString": "/sig2/",
"anchorXOffset": 0,
"anchorYOffset": -10,
"isRequired": True,
"label": "ManagerSignature",
},
{
"type": "DATE_SIGNED",
"pageIndex": 0,
"anchorString": "/date2/",
"anchorXOffset": 0,
"anchorYOffset": 0,
"label": "ManagerSignDate",
},
],
},
],
"documents": [
{
"name": "Employment_Contract.pdf",
"documentBase64": document_base64,
"mimeType": "application/pdf",
"order": 1,
}
],
},
)
response.raise_for_status()
return response.json()
# Usage
token = get_access_token()
result = create_agreement_with_anchors("./contract.pdf", token)
print(f"Agreement ID: {result['agreement']['id']}")
Tab Types Reference
| Type | Description |
|---|---|
SIGNATURE | Signature field - recipient draws or types signature |
INITIAL | Initials field - recipient adds initials |
DATE_SIGNED | Auto-populated date when recipient signs |
TEXT | Text input field for free-form text |
NUMBER | Numeric input field |
CHECKBOX | Checkbox field |
RADIO | Radio button (use with groupName for radio groups) |
DROPDOWN | Dropdown list selection (use with options) |
EMAIL | Email input field |
FULL_NAME | Auto-populated from recipient name |
ATTACHMENT | File attachment field |
FORMULA | Calculated field based on other tab values |
Positioning Options
XY Coordinates
| Field | Description |
|---|---|
pageIndex | 0-based page index |
rect.x | Horizontal position in PDF points from left edge |
rect.y | Vertical position in PDF points from top edge |
rect.width | Width in PDF points |
rect.height | Height in PDF points |
Anchor Text
| Field | Description |
|---|---|
pageIndex | 0-based page index (first page where anchor is found) |
anchorString | Text to search for in the document |
anchorXOffset | Horizontal offset from anchor in PDF points |
anchorYOffset | Vertical offset from anchor in PDF points |
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 |
|---|---|
type | Tab type (required) - see table above |
pageIndex | 0-based page index (required) |
isRequired | Whether tab must be completed (default: true) |
label | Unique identifier for the tab |
placeholder | Placeholder text for input fields |
value | Pre-filled value |
fontFamily | Font family name |
fontSize | Font size in points |
fontColor | Font color (hex or named) |
bold / italic / underline | Text styling (true/false) |
options | Array of {label, value} for dropdown/radio fields |
groupName | Group name for radio buttons |
Status Options
| Status | Description |
|---|---|
CREATED | Creates a draft agreement (default) |
SENT | Creates and immediately sends to recipients |
Error Responses
| Scenario | Response |
|---|---|
| Missing requirements | {"error": "Agreement created but failed to send. Check that all required fields are present (recipients, documents)."} |
| Invalid document | {"error": "Failed to upload document: Contract.pdf"} |
| Unauthorized | {"error": "Unauthorized"} |
Limits
| Limit | Value |
|---|---|
| Max documents | 10 |
| Max document size | 25 MB |
| Max name length | 255 characters |
| Max subject length | 255 characters |
| Max message length | 2,000 characters |
| Max tabs per recipient | 100 |
Next Steps
- Create & Send Envelope (DocuSign-Compatible) - Use the DocuSign-compatible API
- Basic Signing Workflow - End-to-end workflow with webhooks
- Templates Guide - Create reusable templates