File Uploads Guide¶
Step-by-step guide to upload PDF files to envelopes.
When to Use This¶
Use file uploads when your PDF files are larger than 1MB. For smaller files, use the standard base64 envelope creation method (base64 documents are limited to 5 MB). Use presigned uploads (up to 50 MB); the legacy streaming uploads (/uploads) are retired and not supported.
Legacy guide: streaming uploads are retired and not supported
This guide describes the legacy streaming upload (POST .../uploads and a PUT to the returned upload_url), retired by an owner decision (issue #1207) that retains presigned URLs. The routes still exist in the source and are documented here only with that status. The returned upload_url is not guaranteed to resolve; this is retirement evidence, not a workaround request. Do not guess a prefix or call the gateway directly. Use presigned uploads: the workflow (create the envelope, request a URL, upload, wait, verify, send) is the same, only the upload step differs and the file goes to the presigned URL without the API key.
Quick Start¶
The steps below are the legacy streaming flow, retired per issue #1207 and not supported. Follow the presigned workflow instead.
Step 1: Create Envelope (No Documents)¶
curl -X POST https://api.lexgo.cl/v1/envelopes \
-H "Authorization: YOUR_API_KEY" \
-F "name=Contract - John Doe" \
-F "recipients[0][name]=John Doe" \
-F "recipients[0][email]=john@example.com" \
-F "recipients[0][order]=1"
Save the envelope.id from the response. During upload preparation, creation returns HTTP 422 with status: ERROR until its document and required placements exist. The response still includes the persisted envelope ID; keep it to upload documents and repair placements rather than creating another envelope. Inspect errors before sending.
Step 2: Request Upload URL¶
curl -X POST https://api.lexgo.cl/v1/envelopes/ENVELOPE_ID/uploads \
-H "Authorization: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"file_name": "contract.pdf",
"order": 0
}'
Save the upload.upload_url and upload.id.
Step 3: Upload File¶
curl -X PUT "UPLOAD_URL" \
-H "Authorization: YOUR_API_KEY" \
-H "Content-Type: application/pdf" \
--data-binary @contract.pdf
Step 4: Wait for Processing¶
Option A: Poll for completion
curl https://api.lexgo.cl/v1/envelopes/ENVELOPE_ID/uploads/UPLOAD_ID \
-H "Authorization: YOUR_API_KEY"
Check upload.status in response. Repeat every 2 seconds until COMPLETED.
Option B: Use webhook (recommended for production)
Subscribe to envelope.file_uploaded event:
curl -X POST https://api.lexgo.cl/v1/webhooks \
-H "Authorization: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://your-app.com/webhooks/lexgo",
"subscriptions": "envelope.file_uploaded"
}'
Your webhook will receive notification when file is ready (use polling to reconcile missed notifications).
Step 5: Assign Placement and Verify Documents¶
Once processing completes, associate the uploaded document (order 0) with
the recipient. The upload alone does not create a valid placement.
curl -X PUT https://api.lexgo.cl/v1/envelopes/ENVELOPE_ID \
-H "Authorization: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"placements":{"signature":{"document_key":"0","recipient_key":"0","type":"SIGNATURE"}}}'
Verify Documents Attached¶
Check the response:
- envelope.documents array contains your file
- envelope.errors is empty
- Each document has status: "READY"
Step 6: Send Envelope¶
The PUT returns HTTP 202/SENDING. Track webhooks and reconcile with GET until completion or ERROR.
Complete Working Script¶
Save this as upload_contract.sh:
#!/bin/bash
set -e
# Configuration
API_BASE="https://api.lexgo.cl/v1"
API_KEY="YOUR_API_KEY"
PDF_FILE="$1"
if [ -z "$PDF_FILE" ]; then
echo "Usage: $0 <pdf_file>"
exit 1
fi
if [ ! -f "$PDF_FILE" ]; then
echo "Error: File not found: $PDF_FILE"
exit 1
fi
echo "=== Step 1: Create Envelope ==="
ENVELOPE_JSON=$(curl -s -X POST "${API_BASE}/envelopes" \
-H "Authorization: ${API_KEY}" \
-F "name=Contract - Test" \
-F "recipients[0][name]=John Doe" \
-F "recipients[0][email]=john@example.com" \
-F "recipients[0][order]=1")
ENVELOPE_ID=$(echo "$ENVELOPE_JSON" | jq -r '.envelope.id')
if [ "$ENVELOPE_ID" = "null" ]; then
echo "Error creating envelope:"
echo "$ENVELOPE_JSON" | jq '.'
exit 1
fi
echo "✓ Created envelope: $ENVELOPE_ID"
echo ""
echo "=== Step 2: Request Upload URL ==="
UPLOAD_JSON=$(curl -s -X POST "${API_BASE}/envelopes/${ENVELOPE_ID}/uploads" \
-H "Authorization: ${API_KEY}" \
-H "Content-Type: application/json" \
-d "{
\"file_name\": \"$(basename "$PDF_FILE")\",
\"order\": 0
}")
UPLOAD_URL=$(echo "$UPLOAD_JSON" | jq -r '.upload.upload_url')
UPLOAD_ID=$(echo "$UPLOAD_JSON" | jq -r '.upload.id')
if [ "$UPLOAD_URL" = "null" ]; then
echo "Error requesting upload URL:"
echo "$UPLOAD_JSON" | jq '.'
exit 1
fi
echo "✓ Got upload URL"
echo "✓ Upload ID: $UPLOAD_ID"
echo ""
echo "=== Step 3: Upload File ==="
UPLOAD_RESPONSE=$(curl -s -X PUT "$UPLOAD_URL" \
-H "Authorization: ${API_KEY}" \
-H "Content-Type: application/pdf" \
--data-binary @"$PDF_FILE")
SUCCESS=$(echo "$UPLOAD_RESPONSE" | jq -r '.success')
if [ "$SUCCESS" != "true" ]; then
echo "Error uploading file:"
echo "$UPLOAD_RESPONSE" | jq '.'
exit 1
fi
echo "✓ Uploaded file"
echo ""
echo "=== Step 4: Wait for Processing ==="
MAX_ATTEMPTS=30
ATTEMPT=0
while [ $ATTEMPT -lt $MAX_ATTEMPTS ]; do
STATUS_JSON=$(curl -s "${API_BASE}/envelopes/${ENVELOPE_ID}/uploads/${UPLOAD_ID}" \
-H "Authorization: ${API_KEY}")
STATUS=$(echo "$STATUS_JSON" | jq -r '.upload.status')
echo " Status: $STATUS (attempt $((ATTEMPT+1))/$MAX_ATTEMPTS)"
if [ "$STATUS" = "COMPLETED" ]; then
FILE_ID=$(echo "$STATUS_JSON" | jq -r '.upload.file_id')
echo "✓ File processed successfully!"
echo "✓ File ID: $FILE_ID"
break
elif [ "$STATUS" = "FAILED" ]; then
ERROR=$(echo "$STATUS_JSON" | jq -r '.upload.error_message')
echo "✗ Processing failed: $ERROR"
exit 1
fi
ATTEMPT=$((ATTEMPT+1))
sleep 2
done
if [ $ATTEMPT -eq $MAX_ATTEMPTS ]; then
echo "✗ Timeout waiting for processing"
exit 1
fi
echo ""
echo "=== Step 5: Assign Placement and Verify Envelope ==="
curl -s -X PUT "${API_BASE}/envelopes/${ENVELOPE_ID}" \
-H "Authorization: ${API_KEY}" \
-H "Content-Type: application/json" \
-d '{"placements":{"signature":{"document_key":"0","recipient_key":"0","type":"SIGNATURE"}}}' > /dev/null
ENVELOPE_JSON=$(curl -s "${API_BASE}/envelopes/${ENVELOPE_ID}" \
-H "Authorization: ${API_KEY}")
DOC_COUNT=$(echo "$ENVELOPE_JSON" | jq '.envelope.documents | length')
ERRORS=$(echo "$ENVELOPE_JSON" | jq '.envelope.errors | length')
if [ "$DOC_COUNT" -eq 0 ]; then
echo "✗ No documents attached to envelope"
exit 1
fi
if [ "$ERRORS" -gt 0 ]; then
echo "✗ Envelope has errors:"
echo "$ENVELOPE_JSON" | jq '.envelope.errors'
exit 1
fi
echo "✓ Envelope verified: $DOC_COUNT document(s), no errors"
echo ""
echo "=== Step 6: Send Envelope ==="
SEND_JSON=$(curl -s -X PUT "${API_BASE}/envelopes/${ENVELOPE_ID}/send" \
-H "Authorization: ${API_KEY}")
SUCCESS=$(echo "$SEND_JSON" | jq -r '.success')
if [ "$SUCCESS" = "true" ]; then
echo "✓ Envelope send accepted; reconcile status"
echo ""
echo "Done! Envelope ID: $ENVELOPE_ID"
else
echo "✗ Failed to send envelope:"
echo "$SEND_JSON" | jq '.'
exit 1
fi
Make it executable and run:
Python Implementation¶
Save as upload_contract.py:
#!/usr/bin/env python3
import requests
import time
import sys
import os
API_BASE = "https://api.lexgo.cl/v1"
API_KEY = "YOUR_API_KEY"
def create_envelope():
print("=== Step 1: Create Envelope ===")
response = requests.post(
f"{API_BASE}/envelopes",
headers={"Authorization": API_KEY},
data={
"name": "Contract - Test",
"recipients[0][name]": "John Doe",
"recipients[0][email]": "john@example.com",
"recipients[0][order]": "1"
}
)
# Upload preparation intentionally creates an incomplete envelope (HTTP 422).
if response.status_code not in (200, 422):
response.raise_for_status()
envelope_id = response.json()["envelope"]["id"]
print(f"✓ Created envelope: {envelope_id}")
return envelope_id
def request_upload_url(envelope_id, filename):
print("\n=== Step 2: Request Upload URL ===")
response = requests.post(
f"{API_BASE}/envelopes/{envelope_id}/uploads",
headers={
"Authorization": API_KEY,
"Content-Type": "application/json"
},
json={
"file_name": os.path.basename(filename),
"order": 0
}
)
response.raise_for_status()
data = response.json()["upload"]
print("✓ Got upload URL")
print(f"✓ Upload ID: {data['id']}")
return data["upload_url"], data["id"]
def upload_file(upload_url, pdf_path):
print("\n=== Step 3: Upload File ===")
with open(pdf_path, "rb") as f:
response = requests.put(
upload_url,
data=f,
headers={
"Authorization": API_KEY,
"Content-Type": "application/pdf"
}
)
response.raise_for_status()
print("✓ Uploaded file")
def wait_for_processing(envelope_id, upload_id):
print("\n=== Step 4: Wait for Processing ===")
max_attempts = 30
for attempt in range(max_attempts):
response = requests.get(
f"{API_BASE}/envelopes/{envelope_id}/uploads/{upload_id}",
headers={"Authorization": API_KEY}
)
response.raise_for_status()
upload = response.json()["upload"]
status = upload["status"]
print(f" Status: {status} (attempt {attempt+1}/{max_attempts})")
if status == "COMPLETED":
print("✓ File processed successfully!")
print(f"✓ File ID: {upload['file_id']}")
return True
elif status == "FAILED":
print(f"✗ Processing failed: {upload.get('error_message', 'Unknown error')}")
return False
time.sleep(2)
print("✗ Timeout waiting for processing")
return False
def verify_envelope(envelope_id):
update = requests.put(
f"{API_BASE}/envelopes/{envelope_id}",
headers={"Authorization": API_KEY},
json={"placements":{"signature":{"document_key":"0","recipient_key":"0","type":"SIGNATURE"}}}
)
update.raise_for_status()
print("\n=== Step 5: Verify Envelope ===")
response = requests.get(
f"{API_BASE}/envelopes/{envelope_id}",
headers={"Authorization": API_KEY}
)
response.raise_for_status()
envelope = response.json()["envelope"]
doc_count = len(envelope["documents"])
error_count = len(envelope.get("errors", []))
if doc_count == 0:
print("✗ No documents attached to envelope")
return False
if error_count > 0:
print(f"✗ Envelope has {error_count} error(s)")
return False
print(f"✓ Envelope verified: {doc_count} document(s), no errors")
return True
def send_envelope(envelope_id):
print("\n=== Step 6: Send Envelope ===")
response = requests.put(
f"{API_BASE}/envelopes/{envelope_id}/send",
headers={"Authorization": API_KEY}
)
response.raise_for_status()
if response.json()["success"]:
print("✓ Envelope send accepted; reconcile status")
return True
else:
print("✗ Failed to send envelope")
return False
def main():
if len(sys.argv) != 2:
print("Usage: python upload_contract.py <pdf_file>")
sys.exit(1)
pdf_path = sys.argv[1]
try:
envelope_id = create_envelope()
upload_url, upload_id = request_upload_url(envelope_id, pdf_path)
upload_file(upload_url, pdf_path)
if not wait_for_processing(envelope_id, upload_id):
sys.exit(1)
if not verify_envelope(envelope_id):
sys.exit(1)
if not send_envelope(envelope_id):
sys.exit(1)
print(f"\nDone! Envelope ID: {envelope_id}")
except requests.HTTPError as e:
print(f"\n✗ API Error: {e.response.status_code}")
print(e.response.json())
sys.exit(1)
except Exception as e:
print(f"\n✗ Error: {e}")
sys.exit(1)
if __name__ == "__main__":
main()
Run it:
Common Issues¶
"URL expired" (403 error)¶
Fix: Request a new upload URL. They expire after 1 hour.
Status stuck in PENDING¶
Fix: Verify the upload succeeded (should return 200 with success: true).
Status changes to FAILED¶
Fix: Check the error message and validate your PDF.
# Get error details
curl https://api.lexgo.cl/v1/envelopes/$ENVELOPE_ID/uploads/$UPLOAD_ID \
-H "Authorization: YOUR_API_KEY" | jq '.upload.error_message'
# Common errors:
# - "Invalid PDF format" -> corrupted file
# - "Encrypted PDF" -> remove password
# - "File too large" -> use presigned uploads (up to 50MB); the legacy /uploads limit was 10MB (HTTP 413)
Cannot create upload (422 error)¶
Fix: Another upload for this order already exists.
# Check existing uploads
curl https://api.lexgo.cl/v1/envelopes/$ENVELOPE_ID/uploads \
-H "Authorization: YOUR_API_KEY" | jq '.uploads[] | select(.order == 0)'
# Wait for completion or use different order number
Next Steps¶
- File Upload API Reference - Complete API documentation
- Envelopes API - Standard base64 upload method
- Quick Start Guide - Getting started with the API