PandaDoc Sales Manager
Streamline sales document workflows with PandaDoc API automation. Create proposals from templates, send documents to customers, track signing status, and manage bulk operations - all from the command line or automated scripts.
Prerequisites
IMPORTANT: You need a PandaDoc account with API access (Enterprise plan for production).
First-Time Setup
Set your API key: export PANDADOC_API_KEY="a1f67376274f328788df8bd7da0560b62191a358"
Verify authentication:
python ~/.claude/skills/pandadoc-sales-manager/scripts/template_manager.py listConfigure API settings:
# Copy the template config cp ~/.claude/skills/pandadoc-sales-manager/templates/api_config.yml ~/pandadoc_config.yml # Edit with your settings (optional - defaults work for most cases) # The config handles rate limits, timeouts, and retry logic
Environment:
- Sandbox: 10 requests/minute (for testing)
- Production: 500 requests/minute for creates, 100/min for downloads
Quick Start
# Create a sales proposal from a template
python ~/.claude/skills/pandadoc-sales-manager/scripts/create_proposal.py \
--template-id "abc123-template-id" \
--customer-name "Acme Corp" \
--customer-email "john@acme.com" \
--fields '{"company_name": "Acme Corp", "proposal_amount": "50000"}'
# Check document status
python ~/.claude/skills/pandadoc-sales-manager/scripts/check_document_status.py doc_xyz789
# Send document to customer
python ~/.claude/skills/pandadoc-sales-manager/scripts/send_document.py doc_xyz789 \
--subject "Your Sales Proposal" \
--message "Please review and sign this proposal"
# Watch status in real-time
python ~/.claude/skills/pandadoc-sales-manager/scripts/check_document_status.py doc_xyz789 --watch
Core Tasks
Create Documents from Templates
# Basic proposal creation
python ~/.claude/skills/pandadoc-sales-manager/scripts/create_proposal.py \
--template-id "YOUR_TEMPLATE_ID" \
--customer-name "John Doe" \
--customer-email "john@example.com"
# With custom field mapping
python ~/.claude/skills/pandadoc-sales-manager/scripts/create_proposal.py \
--template-id "YOUR_TEMPLATE_ID" \
--customer-name "Acme Corp" \
--customer-email "jane@acme.com" \
--fields '{"company_name": "Acme Corporation", "deal_value": "100000", "start_date": "2024-02-01"}' \
--tokens '{"Sales.Rep": "Alice Smith", "Sales.Territory": "West Coast"}' \
--config ~/pandadoc_config.yml
# From JSON customer data file
python ~/.claude/skills/pandadoc-sales-manager/scripts/create_proposal.py \
--template-id "YOUR_TEMPLATE_ID" \
--customer-data customer.json
# customer.json format:
# {
# "name": "John Doe",
# "email": "john@example.com",
# "company": "Acme Corp",
# "fields": {"proposal_amount": "50000"},
# "tokens": {"Sales.Rep": "Alice"}
# }
The script automatically:
- Validates template exists
- Polls status until
document.draft(CRITICAL - prevents 404 errors) - Respects rate limits (6 second delays)
- Returns document ID for next steps
Send Documents
# Send with custom message
python ~/.claude/skills/pandadoc-sales-manager/scripts/send_document.py doc_xyz789 \
--subject "Q1 Sales Proposal for Review" \
--message "Hi John, please review this proposal and let me know if you have questions."
# Send silently (no email notification - for testing)
python ~/.claude/skills/pandadoc-sales-manager/scripts/send_document.py doc_xyz789 --silent
# Send multiple documents
for doc_id in doc_1 doc_2 doc_3; do
python ~/.claude/skills/pandadoc-sales-manager/scripts/send_document.py $doc_id
sleep 6 # Rate limit compliance
done
Track Document Status
# Check single document
python ~/.claude/skills/pandadoc-sales-manager/scripts/check_document_status.py doc_xyz789
# Check multiple documents
python ~/.claude/skills/pandadoc-sales-manager/scripts/check_document_status.py \
doc_1 doc_2 doc_3
# Watch mode (real-time updates every 10 seconds)
python ~/.claude/skills/pandadoc-sales-manager/scripts/check_document_status.py doc_xyz789 \
--watch --interval 10
# Export to JSON for processing
python ~/.claude/skills/pandadoc-sales-manager/scripts/check_document_status.py doc_xyz789 \
--format json > status.json
# Export to CSV for reporting
python ~/.claude/skills/pandadoc-sales-manager/scripts/check_document_status.py \
doc_1 doc_2 doc_3 --format csv > report.csv
Status values:
document.uploaded- Just created, processing (wait for draft)document.draft- Ready to senddocument.sent- Sent to recipientsdocument.viewed- Recipient vieweddocument.completed- All signatures collecteddocument.declined- Recipient declined
Manage Templates
# List all templates
python ~/.claude/skills/pandadoc-sales-manager/scripts/template_manager.py list
# Search by name
python ~/.claude/skills/pandadoc-sales-manager/scripts/template_manager.py search "Sales Proposal"
# Show detailed template info
python ~/.claude/skills/pandadoc-sales-manager/scripts/template_manager.py show YOUR_TEMPLATE_ID
# Show template fields and types
python ~/.claude/skills/pandadoc-sales-manager/scripts/template_manager.py fields YOUR_TEMPLATE_ID
# Export template schema to JSON
python ~/.claude/skills/pandadoc-sales-manager/scripts/template_manager.py export YOUR_TEMPLATE_ID \
> template_schema.json
Manage Product Catalog
The product catalog API allows you to retrieve and search catalog items for use in quotes and documents.
Get Catalog Item Details:
# Get a specific catalog item by UUID
curl -X GET "https://api.pandadoc.com/public/v2/product-catalog/items/{item_uuid}" \
-H "Authorization: API-Key $PANDADOC_API_KEY" \
-H "Content-Type: application/json"
Search Catalog Items:
# Search by query (searches title, SKU, description, category name, custom fields)
curl -X GET "https://api.pandadoc.com/public/v2/product-catalog/items/search?query=product_name" \
-H "Authorization: API-Key $PANDADOC_API_KEY" \
-H "Content-Type: application/json"
# Search with filters
curl -X GET "https://api.pandadoc.com/public/v2/product-catalog/items/search?type=product&category_id=123" \
-H "Authorization: API-Key $PANDADOC_API_KEY" \
-H "Content-Type: application/json"
# Search with ordering (by SKU, name, price, or modification date)
curl -X GET "https://api.pandadoc.com/public/v2/product-catalog/items/search?order_by=price&order=desc" \
-H "Authorization: API-Key $PANDADOC_API_KEY" \
-H "Content-Type: application/json"
# Exclude specific items from search
curl -X GET "https://api.pandadoc.com/public/v2/product-catalog/items/search?exclude_uuids=uuid1,uuid2" \
-H "Authorization: API-Key $PANDADOC_API_KEY" \
-H "Content-Type: application/json"
Search Parameters:
query- Search in title, SKU, description, category name, custom fields name and valuetype- Filter by item typebilling_type- Filter by billing typecategory_id- Filter by category IDorder_by- Sort by:sku,name,price, ormodified_dateorder- Sort direction:ascordescexclude_uuids- Comma-separated list of UUIDs to exclude
Use Cases:
- Retrieve product details for quote generation
- Search products by name or SKU for document creation
- Filter products by category for organized proposals
- Get pricing information for sales calculations
API Reference:
Bulk Operations
# Create 100 proposals from CSV
python ~/.claude/skills/pandadoc-sales-manager/scripts/bulk_create_documents.py \
--template-id "YOUR_TEMPLATE_ID" \
--customers customers.csv \
--batch-size 50
# CSV format: name,email,company,proposal_amount
# John Doe,john@example.com,Acme Corp,50000
# Jane Smith,jane@company.com,Beta Inc,75000
# Resume interrupted batch
python ~/.claude/skills/pandadoc-sales-manager/scripts/bulk_create_documents.py \
--template-id "YOUR_TEMPLATE_ID" \
--customers customers.csv \
--resume
# From JSON file
python ~/.claude/skills/pandadoc-sales-manager/scripts/bulk_create_documents.py \
--template-id "YOUR_TEMPLATE_ID" \
--customers customers.json
The script automatically:
- Respects rate limits with delays
- Saves progress to
.batch_progress.json - Tracks errors and successes
- Can resume from interruptions
- Generates completion report
Download Completed Documents
# Download all completed documents from date range
python ~/.claude/skills/pandadoc-sales-manager/scripts/download_completed.py \
--start-date 2024-01-01 \
--end-date 2024-01-31 \
--output-dir ~/documents/completed/
# Download with custom filename pattern
python ~/.claude/skills/pandadoc-sales-manager/scripts/download_completed.py \
--start-date 2024-01-01 \
--filename-pattern "{date}_{name}.pdf" \
--output-dir ~/documents/
# Skip existing files
python ~/.claude/skills/pandadoc-sales-manager/scripts/download_completed.py \
--start-date 2024-01-01 \
--skip-existing \
--output-dir ~/documents/
Filename pattern variables:
{name}- Document name{id}- Document ID{date}- Completion date (YYYY-MM-DD){status}- Document status
API Patterns Reference
Authentication:
headers = {
"Authorization": f"API-Key {os.environ['PANDADOC_API_KEY']}",
"Content-Type": "application/json"
}
Rate Limiting:
# CRITICAL: Enforce 6+ second delays between requests
# Sandbox: 10 requests/minute
# Production: 500/min creates, 100/min downloads
import time
time.sleep(6) # Wait between requests
Status Polling Pattern:
# CRITICAL: Always poll for draft status before operations
max_retries = 30 # 60 seconds total
for attempt in range(max_retries):
status = get_document_status(doc_id)
if status == "document.draft":
break
elif status == "document.error":
raise Exception("Document creation failed")
time.sleep(2)
else:
raise TimeoutError("Document not ready after 60 seconds")
Catalog API Patterns:
import requests
import os
# Get catalog item by UUID
def get_catalog_item(item_uuid):
url = f"https://api.pandadoc.com/public/v2/product-catalog/items/{item_uuid}"
headers = {
"Authorization": f"API-Key {os.environ['PANDADOC_API_KEY']}",
"Content-Type": "application/json"
}
response = requests.get(url, headers=headers)
response.raise_for_status()
return response.json()
# Search catalog items
def search_catalog_items(query=None, item_type=None, category_id=None,
order_by="name", order="asc", exclude_uuids=None):
url = "https://api.pandadoc.com/public/v2/product-catalog/items/search"
headers = {
"Authorization": f"API-Key {os.environ['PANDADOC_API_KEY']}",
"Content-Type": "application/json"
}
params = {
"order_by": order_by,
"order": order
}
if query:
params["query"] = query
if item_type:
params["type"] = item_type
if category_id:
params["category_id"] = category_id
if exclude_uuids:
params["exclude_uuids"] = ",".join(exclude_uuids) if isinstance(exclude_uuids, list) else exclude_uuids
response = requests.get(url, headers=headers, params=params)
response.raise_for_status()
return response.json()
# Example usage
items = search_catalog_items(query="laptop", order_by="price", order="desc")
item_details = get_catalog_item(items["results"][0]["uuid"])
See also: references/endpoints-reference.md for complete API documentation.
Configuration Files
API Configuration
Location in the skill: templates/api_config.yml
api:
base_url: https://api.pandadoc.com/public/v1
timeout: 30
rate_limit:
requests_per_minute: 10 # Sandbox limit
delay_seconds: 6
async_processing:
max_wait_seconds: 60
poll_interval_seconds: 2
Copy and customize:
cp ~/.claude/skills/pandadoc-sales-manager/templates/api_config.yml ~/pandadoc_config.yml
Template Mappings
Location: templates/template_mappings.yml
Maps your business fields to PandaDoc template fields:
templates:
sales_proposal:
template_id: "your-template-id"
fields:
company_name: "Company Name"
proposal_amount: "Proposal Amount"
start_date: "Start Date"
tokens:
sales_rep: "Sales.Rep"
territory: "Sales.Territory"
Usage:
python scripts/create_proposal.py \
--config ~/pandadoc_config.yml \
--template-mapping sales_proposal \
--customer-data customer.json
Field Validation
Location: templates/field_validation.yml
Defines validation rules for fields:
global_rules:
required:
- customer_email
- customer_name
validations:
customer_email:
type: email
pattern: "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$"
proposal_amount:
type: number
min: 0
max: 10000000
See also:
- templates/webhook_config.yml - Webhook subscriptions
- templates/batch_config.yml - Batch processing settings
Proven Patterns
Pattern: Complete Proposal Workflow
⚠️ MANDATORY: Always follow this workflow for document creation:
- Create document from template (script polls until ready)
- Wait for
document.draftstatus (automatic in scripts) - Send document (only after draft status confirmed)
- Track status (monitor for completion)
# Step 1: Create (script handles status polling automatically)
DOC_ID=$(python scripts/create_proposal.py \
--template-id "YOUR_TEMPLATE_ID" \
--customer-name "Acme Corp" \
--customer-email "john@acme.com" \
--fields '{"proposal_amount": "50000"}' | grep -o 'doc_[a-zA-Z0-9]*')
# Step 2: Status is already draft (script waited for it)
# Step 3: Send document
python scripts/send_document.py $DOC_ID \
--subject "Sales Proposal for Review" \
--message "Please review and sign"
# Step 4: Track status
python scripts/check_document_status.py $DOC_ID --watch
Why this prevents failures:
- Documents are NOT ready immediately after creation (async processing)
- Attempting to send before
document.draft= 404 error - Status polling is built into all scripts to prevent this
- Rate limiting prevents 429 errors
Pattern: Bulk Document Creation with Error Recovery
# Create batch with progress tracking
python scripts/bulk_create_documents.py \
--template-id "YOUR_TEMPLATE_ID" \
--customers customers.csv \
--batch-size 50 \
--verbose
# If interrupted, resume from saved progress
python scripts/bulk_create_documents.py \
--template-id "YOUR_TEMPLATE_ID" \
--customers customers.csv \
--resume
# Check progress file
cat .batch_progress.json
Features:
- Automatic progress saving after each document
- Resume from interruption point
- Error tracking with details
- Rate limit compliance
- Final completion report
Pattern: CRM Integration
# 1. Export opportunities from CRM
# 2. Transform to PandaDoc format
# 3. Bulk create proposals
# Example: Salesforce export → PandaDoc
cat salesforce_opportunities.json | \
jq '[.[] | {
name: .ContactName,
email: .Email,
company: .AccountName,
proposal_amount: .Amount
}]' > pandadoc_customers.json
# Create proposals
python scripts/bulk_create_documents.py \
--template-id "YOUR_TEMPLATE_ID" \
--customers pandadoc_customers.json
# Monitor completion
python scripts/check_document_status.py $(cat .batch_progress.json | jq -r '.successes[].document_id') \
--format csv > status_report.csv
See also: references/workflows.md for complete workflow patterns.
Troubleshooting
Quick fixes for common issues:
| Issue | Solution |
|---|---|
| 401 Unauthorized | Check PANDADOC_API_KEY environment variable. Verify key is active in PandaDoc settings. |
| 404 Document Not Found | Wait for document.draft status before operations. Use scripts that auto-poll. |
| 429 Rate Limit | Sandbox: 10 req/min. Add 6+ second delays. Production: 500/min creates. |
| Document Stuck in "uploaded" | Normal - wait 2-5 seconds. Scripts poll automatically. Max wait: 60 seconds. |
| Fields Not Pre-filled | Check field names match template exactly. Signature fields cannot be pre-filled. |
| Invalid Email Format | Must contain @. Scripts validate automatically. |
| Template Not Found | Verify template ID. Use template_manager.py list to see available templates. |
| Empty Recipients | At least one recipient required. Check email format. |
| Webhook Not Firing | Verify URL is HTTPS. Check shared key signature. Test with webhook_tester.py. |
| CSV Import Fails | Check headers: name,email,company. No spaces in column names. |
For detailed solutions: references/troubleshooting.md
Diagnostic commands:
# Test authentication
python scripts/template_manager.py list
# Verify template exists
python scripts/template_manager.py show YOUR_TEMPLATE_ID
# Check field names
python scripts/template_manager.py fields YOUR_TEMPLATE_ID
# Test webhook
python scripts/webhook_tester.py test https://your-webhook-url.com/webhook
Helper Scripts
All scripts located in: ~/.claude/skills/pandadoc-sales-manager/scripts/
| Script | Purpose | Key Features |
|---|---|---|
| create_proposal.py | Create documents from templates | Status polling, field validation, rate limiting, JSON/YAML support |
| send_document.py | Send documents to recipients | Status verification, custom messages, silent mode, batch support |
| check_document_status.py | Monitor document status | Watch mode, export (JSON/CSV), multi-document, recipient status |
| bulk_create_documents.py | Batch create documents | CSV/JSON import, progress tracking, resume capability, error recovery |
| download_completed.py | Download completed documents | Date range filtering, custom filenames, skip existing, batch download |
| template_manager.py | Manage templates | List, search, inspect, field mapping, schema export |
| webhook_tester.py | Test webhook endpoints | Local server, signature validation, event simulation, payload testing |
Usage examples:
# See detailed help for any script
python scripts/create_proposal.py --help
python scripts/send_document.py --help
# Enable verbose logging
python scripts/create_proposal.py --verbose ...
# All scripts support:
# - Environment variable: PANDADOC_API_KEY
# - Config file: --config path/to/config.yml
# - Verbose output: --verbose or -v
See also: scripts/README.md for comprehensive script documentation.
Workflow Requirements
⚠️ MANDATORY: Always follow this workflow when creating and sending documents:
Create document using
create_proposal.py- Script automatically polls for
document.draftstatus - Waits up to 60 seconds (2-second intervals)
- Fails if timeout or error status
- Script automatically polls for
Verify status is "document.draft" before sending
- Built into all helper scripts
- Manual check:
check_document_status.py DOC_ID
Send document using
send_document.py- Script verifies draft status first
- Only sends if ready
Track completion using
check_document_status.py- Watch mode for real-time updates
- Export for reporting
Why this prevents failures:
- Async Processing: Documents are NOT ready immediately after creation
- Race Condition: Attempting operations before
document.draft= 404 error - Rate Limits: Proper delays prevent 429 errors
- State Validation: Scripts enforce correct status transitions
Manual verification (if not using scripts):
# After creating document, ALWAYS poll status
DOC_ID="doc_xyz789"
while true; do
STATUS=$(curl -s "https://api.pandadoc.com/public/v1/documents/$DOC_ID" \
-H "Authorization: API-Key $PANDADOC_API_KEY" | jq -r '.status')
echo "Status: $STATUS"
if [ "$STATUS" = "document.draft" ]; then
echo "Ready to send!"
break
elif [ "$STATUS" = "document.error" ]; then
echo "Error creating document"
exit 1
fi
sleep 2
done
Common Workflows
Workflow 1: Send Proposal to Single Customer
# 1. Find your template
python scripts/template_manager.py list | grep "Sales Proposal"
# Output: abc123... | Sales Proposal | ...
# 2. Create document with customer data
python scripts/create_proposal.py \
--template-id "abc123..." \
--customer-name "John Doe" \
--customer-email "john@acme.com" \
--fields '{"company_name": "Acme Corp", "proposal_amount": "50000"}'
# Output: Created document: doc_xyz789 (Status: document.draft)
# 3. Send to customer
python scripts/send_document.py doc_xyz789 \
--subject "Sales Proposal for Acme Corp" \
--message "Hi John, please review and sign this proposal."
# Output: Document sent successfully!
# 4. Track status
python scripts/check_document_status.py doc_xyz789 --watch
# Real-time updates until completed
Workflow 2: Bulk Proposal Campaign
# 1. Export leads from CRM to CSV
# Format: name,email,company,proposal_amount
cat > customers.csv <<EOF
name,email,company,proposal_amount
John Doe,john@acme.com,Acme Corp,50000
Jane Smith,jane@beta.com,Beta Inc,75000
Bob Johnson,bob@gamma.com,Gamma LLC,100000
EOF
# 2. Bulk create proposals
python scripts/bulk_create_documents.py \
--template-id "YOUR_TEMPLATE_ID" \
--customers customers.csv \
--batch-size 50 \
--verbose
# Progress is automatically saved to .batch_progress.json
# Can interrupt and resume at any time
# 3. Check progress
cat .batch_progress.json | jq '{
total: .total_documents,
completed: (.successes | length),
failed: (.errors | length)
}'
# 4. Send all created documents
cat .batch_progress.json | jq -r '.successes[].document_id' | while read doc_id; do
python scripts/send_document.py $doc_id --silent
sleep 6 # Rate limit compliance
done
# 5. Monitor completion
python scripts/check_document_status.py \
$(cat .batch_progress.json | jq -r '.successes[].document_id') \
--format csv > completion_report.csv
Workflow 3: Download All Completed Q1 Documents
# Download all documents completed in Q1 2024
python scripts/download_completed.py \
--start-date 2024-01-01 \
--end-date 2024-03-31 \
--output-dir ~/Documents/PandaDoc/Q1_2024/ \
--filename-pattern "{date}_{name}.pdf" \
--skip-existing
# Output:
# Downloaded: 2024-01-15_Proposal_Acme_Corp.pdf
# Downloaded: 2024-01-20_Agreement_Beta_Inc.pdf
# Skipped (exists): 2024-02-01_Contract_Gamma_LLC.pdf
# ...
# Total: 45 downloaded, 3 skipped, 0 errors
Workflow 4: Webhook Integration for Real-Time Updates
# 1. Test your webhook endpoint
python scripts/webhook_tester.py test https://your-app.com/webhook
# 2. Create webhook subscription using config
# Edit templates/webhook_config.yml with your URL
# 3. Subscribe to events
curl -X POST "https://api.pandadoc.com/public/v1/webhook-subscriptions" \
-H "Authorization: API-Key $PANDADOC_API_KEY" \
-H "Content-Type: application/json" \
-d @templates/webhook_config.yml
# 4. Verify webhook receives events
python scripts/webhook_tester.py server --port 8000
# Keep running to receive webhook events locally for testing
Workflow 5: Template Field Mapping and Testing
# 1. Inspect template fields
python scripts/template_manager.py fields YOUR_TEMPLATE_ID
# Output shows all fields:
# company_name (Text) - REQUIRED
# proposal_amount (Text) - REQUIRED
# start_date (Date) - OPTIONAL
# signature (Signature) - REQUIRED (cannot pre-fill)
# 2. Export full template schema
python scripts/template_manager.py export YOUR_TEMPLATE_ID > schema.json
# 3. Test with sample data
python scripts/create_proposal.py \
--template-id "YOUR_TEMPLATE_ID" \
--customer-name "Test Customer" \
--customer-email "test@example.com" \
--fields '{"company_name": "Test Corp", "proposal_amount": "1000", "start_date": "2024-02-01"}'
# 4. Verify in watch mode
python scripts/check_document_status.py DOC_ID --watch
# 5. If successful, save mapping to config
# Edit templates/template_mappings.yml
Tips
API Efficiency:
- Use
bulk_create_documents.pyfor creating multiple documents - handles rate limiting automatically - Enable verbose mode (
--verbose) for debugging to see API requests/responses - Use watch mode for status tracking instead of manual polling
- Cache template IDs and field mappings in
template_mappings.yml
Error Prevention:
- Always use helper scripts - they implement required guardrails (status polling, rate limits)
- Never send documents immediately after creation - scripts handle the mandatory wait
- Validate customer data before bulk operations - saves API calls on errors
- Test with small batches first (10-20 documents) before large campaigns
Rate Limiting:
- Sandbox: 10 requests/minute = 6 second minimum delays between requests
- Production: 500/min creates = 0.12 second delays (scripts default to 1 second)
- Scripts automatically handle rate limiting with
RateLimiterclass - If hitting limits, increase batch delays in
batch_config.yml
Field Mapping:
- Use
template_manager.py fieldsto discover exact field names - Field names are case-sensitive and must match template exactly
- Use
fieldsparameter for form fields (no square brackets) - Use
tokensparameter for template variables (have square brackets like[Sales.Rep]) - Signature fields cannot be pre-filled - recipients must sign
Status Tracking:
- Use watch mode for real-time updates:
--watch --interval 10 - Export to CSV for spreadsheet analysis:
--format csv - Export to JSON for programmatic processing:
--format json - Status transitions: uploaded → draft → sent → viewed → completed
Webhook Integration:
- Use webhooks for real-time status updates instead of polling
- Test locally first with
webhook_tester.py server - Always verify HMAC signatures for security
- Have polling fallback for missed webhook events
See also:
- references/workflows.md - Complete workflow patterns with code
- references/field-mapping-guide.md - Field vs token mapping
- references/troubleshooting.md - Diagnostic commands and solutions
- references/webhook-integration-guide.md - Webhook setup and testing
- scripts/README.md - Complete script documentation