Sign API Quickstart
Get your first document signed in under 5 minutes.
Prerequisites
- A Propper account with API access
- API credentials from the dashboard
- A PDF document to send for signing
Step 1: Get an Access Token
First, exchange your client credentials for an access token.
- cURL
- JavaScript
- Python
curl -X POST "https://auth.propper.ai/oauth/token" \
-H "Content-Type: application/json" \
-d '{
"grant_type": "client_credentials",
"client_id": "YOUR_CLIENT_ID",
"client_secret": "YOUR_CLIENT_SECRET",
"scope": "sign:read sign:write"
}'
const getAccessToken = async () => {
const response = await fetch('https://auth.propper.ai/oauth/token', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
grant_type: 'client_credentials',
client_id: process.env.PROPPER_CLIENT_ID,
client_secret: process.env.PROPPER_CLIENT_SECRET,
scope: 'sign:read sign:write',
}),
});
const { access_token } = await response.json();
return access_token;
};
const token = await getAccessToken();
console.log('Token:', token);
import os
import requests
def get_access_token() -> str:
response = requests.post(
"https://auth.propper.ai/oauth/token",
json={
"grant_type": "client_credentials",
"client_id": os.environ["PROPPER_CLIENT_ID"],
"client_secret": os.environ["PROPPER_CLIENT_SECRET"],
"scope": "sign:read sign:write",
},
)
response.raise_for_status()
return response.json()["access_token"]
token = get_access_token()
print(f"Token: {token}")
Response:
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "sign:read sign:write"
}
tip
Tokens expire in 1 hour. Cache them and refresh before expiry.
Step 2: Create and Send an Envelope
Create an envelope with a document and send it for signing.
- cURL
- JavaScript
- Python
# First, base64 encode your PDF
DOCUMENT_BASE64=$(base64 -i contract.pdf)
# Create and send the envelope
curl -X POST "https://api.propper.ai/restapi/v2.1/accounts/{accountId}/envelopes" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d "{
\"emailSubject\": \"Please sign: Service Agreement\",
\"emailBlurb\": \"Please review and sign this document.\",
\"status\": \"sent\",
\"documents\": [{
\"documentId\": \"1\",
\"name\": \"Service Agreement.pdf\",
\"documentBase64\": \"$DOCUMENT_BASE64\"
}],
\"recipients\": {
\"signers\": [{
\"email\": \"signer@example.com\",
\"name\": \"John Smith\",
\"recipientId\": \"1\",
\"routingOrder\": \"1\"
}]
}
}"
import fs from 'fs/promises';
const createEnvelope = async (accountId, token) => {
// Read and encode the PDF
const pdfBuffer = await fs.readFile('./contract.pdf');
const documentBase64 = pdfBuffer.toString('base64');
const response = await fetch(
`https://api.propper.ai/restapi/v2.1/accounts/${accountId}/envelopes`,
{
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
emailSubject: 'Please sign: Service Agreement',
emailBlurb: 'Please review and sign this document.',
status: 'sent', // Use 'created' for draft
documents: [{
documentId: '1',
name: 'Service Agreement.pdf',
documentBase64,
}],
recipients: {
signers: [{
email: 'signer@example.com',
name: 'John Smith',
recipientId: '1',
routingOrder: '1',
}],
},
}),
}
);
return response.json();
};
const envelope = await createEnvelope(accountId, token);
console.log('Envelope ID:', envelope.envelopeId);
console.log('Status:', envelope.status);
import base64
from pathlib import Path
def create_envelope(account_id: str, token: str) -> dict:
# Read and encode the PDF
pdf_content = Path("./contract.pdf").read_bytes()
document_base64 = base64.b64encode(pdf_content).decode("utf-8")
response = requests.post(
f"https://api.propper.ai/restapi/v2.1/accounts/{account_id}/envelopes",
headers={
"Authorization": f"Bearer {token}",
"Content-Type": "application/json",
},
json={
"emailSubject": "Please sign: Service Agreement",
"emailBlurb": "Please review and sign this document.",
"status": "sent", # Use "created" for draft
"documents": [{
"documentId": "1",
"name": "Service Agreement.pdf",
"documentBase64": document_base64,
}],
"recipients": {
"signers": [{
"email": "signer@example.com",
"name": "John Smith",
"recipientId": "1",
"routingOrder": "1",
}],
},
},
)
response.raise_for_status()
return response.json()
envelope = create_envelope(account_id, token)
print(f"Envelope ID: {envelope['envelopeId']}")
print(f"Status: {envelope['status']}")
Response:
{
"envelopeId": "abc123-def456-ghi789",
"status": "sent",
"statusDateTime": "2024-01-15T10:00:00Z",
"uri": "/restapi/v2.1/accounts/.../envelopes/abc123-def456-ghi789"
}
The signer will receive an email with a link to sign the document.
Try it in the API Reference
Step 3: Check Envelope Status
Poll the envelope status or set up webhooks for real-time updates.
- cURL
- JavaScript
- Python
curl "https://api.propper.ai/restapi/v2.1/accounts/{accountId}/envelopes/{envelopeId}?include=recipients" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
const getEnvelopeStatus = async (accountId, envelopeId, token) => {
const response = await fetch(
`https://api.propper.ai/restapi/v2.1/accounts/${accountId}/envelopes/${envelopeId}?include=recipients`,
{
headers: { 'Authorization': `Bearer ${token}` },
}
);
return response.json();
};
const status = await getEnvelopeStatus(accountId, envelopeId, token);
console.log('Status:', status.status);
console.log('Recipients:', status.recipients?.signers);
def get_envelope_status(account_id: str, envelope_id: str, token: str) -> dict:
response = requests.get(
f"https://api.propper.ai/restapi/v2.1/accounts/{account_id}/envelopes/{envelope_id}",
headers={"Authorization": f"Bearer {token}"},
params={"include": "recipients"},
)
response.raise_for_status()
return response.json()
status = get_envelope_status(account_id, envelope_id, token)
print(f"Status: {status['status']}")
print(f"Recipients: {status.get('recipients', {}).get('signers', [])}")
Response:
{
"envelopeId": "abc123-def456-ghi789",
"status": "completed",
"statusDateTime": "2024-01-15T11:30:00Z",
"sentDateTime": "2024-01-15T10:00:00Z",
"completedDateTime": "2024-01-15T11:30:00Z",
"recipients": {
"signers": [{
"email": "signer@example.com",
"name": "John Smith",
"status": "completed",
"signedDateTime": "2024-01-15T11:30:00Z"
}]
}
}
Try it in the API Reference
Step 4: Download Signed Document
Once all recipients have signed, download the completed document.
- cURL
- JavaScript
- Python
curl "https://api.propper.ai/restapi/v2.1/accounts/{accountId}/envelopes/{envelopeId}/documents/combined" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-o signed-document.pdf
echo "Downloaded: signed-document.pdf"
import fs from 'fs/promises';
const downloadSignedDocument = async (accountId, envelopeId, token, outputPath) => {
const response = await fetch(
`https://api.propper.ai/restapi/v2.1/accounts/${accountId}/envelopes/${envelopeId}/documents/combined`,
{
headers: { 'Authorization': `Bearer ${token}` },
}
);
const buffer = Buffer.from(await response.arrayBuffer());
await fs.writeFile(outputPath, buffer);
console.log(`Downloaded: ${outputPath}`);
};
await downloadSignedDocument(accountId, envelopeId, token, './signed-document.pdf');
def download_signed_document(account_id: str, envelope_id: str, token: str, output_path: str):
response = requests.get(
f"https://api.propper.ai/restapi/v2.1/accounts/{account_id}/envelopes/{envelope_id}/documents/combined",
headers={"Authorization": f"Bearer {token}"},
)
response.raise_for_status()
Path(output_path).write_bytes(response.content)
print(f"Downloaded: {output_path}")
download_signed_document(account_id, envelope_id, token, "./signed-document.pdf")
Try it in the API Reference
Common Mistakes
Don't forget these
1. Missing base64 encoding
// Wrong - raw file content
documents: [{ documentBase64: pdfFileContent }]
// Correct - base64 encoded
documents: [{ documentBase64: Buffer.from(pdfFileContent).toString('base64') }]
2. Wrong status for drafts
// Creates draft (not sent)
{ status: 'created' }
// Sends immediately
{ status: 'sent' }
3. Missing recipient fields
// Wrong - missing required fields
{ email: 'signer@example.com' }
// Correct - all required fields
{
email: 'signer@example.com',
name: 'John Smith',
recipientId: '1',
routingOrder: '1'
}