Skip to main content

How to Send an Envelope with an Uploaded PDF File

This guide demonstrates how to upload a PDF file and send it as an envelope for e-signature using Blueink's API v2. You'll learn how to use multipart form data to upload files directly to Blueink and create a complete signing workflow.

Overview

The process involves two main steps:

  1. Upload the PDF file using multipart/form-data encoding
  2. Create the envelope with the uploaded file and signer information

Blueink's API v2 supports uploading files directly in the bundle creation request, eliminating the need for separate file upload endpoints. Envelopes are automatically sent in the background after creation, though they initially show pending status while processing.

Prerequisites

Before you begin, ensure you have:

  • A Blueink account with API access
  • Your API key (available in the Blueink Dashboard under Apps)
  • A PDF file you want to send for signature
  • Basic knowledge of HTTP requests and your chosen programming language
tip

If you don't have an API key yet, visit the Authentication guide to learn how to obtain one.

Test Mode Email Restrictions

When using is_test: true, envelopes can only be sent to email addresses that belong to users in your Blueink account. Make sure the signer email addresses match existing users in your account when testing.

Step 1: Prepare Your Environment

Authentication Setup

All requests to the Blueink API require authentication using your API key in the Authorization header:

Authorization: Token YOUR_API_KEY_HERE

Required Information

For this guide, you'll need:

  • PDF file path: The local path to your PDF file
  • Signer information: Name and email address of the person who will sign
  • Bundle details: Label and any custom message (optional)

Step 2: Upload PDF and Create Envelope

The Blueink API v2 allows you to upload files and create an envelope in a single request using multipart/form-data encoding.

import requests
import json

# Configuration
API_KEY = "your_api_key_here"
BASE_URL = "https://api.blueink.com/api/v2"
PDF_FILE_PATH = "path/to/your/document.pdf"

# Headers for authentication
headers = {
"Authorization": f"Token {API_KEY}"
}

# Bundle request data (as JSON string for multipart request)
bundle_request = {
"label": "Contract Signature Request",
"is_test": True, # Set to False for production
# "status": "dr", # Uncomment to create as draft (default is pending)
"packets": [
{
"key": "signer-1",
"name": "John Doe",
"email": "[email protected]",
"deliver_via": "email"
}
],
"documents": [
{
"key": "contract-doc",
"file_index": "0", # References the uploaded file
"filename": "contract.pdf"
}
]
}

# Prepare multipart form data
try:
with open(PDF_FILE_PATH, 'rb') as pdf_file:
files = {
'files[0]': ('contract.pdf', pdf_file, 'application/pdf')
}

data = {
'bundle_request': json.dumps(bundle_request)
}

# Create the envelope with uploaded PDF
response = requests.post(
f"{BASE_URL}/bundles/",
headers=headers,
files=files,
data=data
)

if response.status_code == 201:
bundle = response.json()
print(f"✅ Envelope created successfully!")
print(f"Bundle ID: {bundle['id']}")
print(f"Status: {bundle['status']}")
print(f"� Envelope will be automatically sent to signers by background processes.")
print(f"View URL: https://app.blueink.com/bundles/{bundle['id']}")
else:
print(f"❌ Error creating envelope: {response.status_code}")
print(f"Response: {response.text}")

except FileNotFoundError:
print(f"❌ PDF file not found: {PDF_FILE_PATH}")
except requests.exceptions.RequestException as e:
print(f"❌ Request failed: {e}")
except Exception as e:
print(f"❌ Unexpected error: {e}")

Step 3: Understanding the Response

When successful, the API returns a 201 Created status with the envelope details:

{
"id": "lL7g5Hn2Ni",
"created": "2025-09-04T23:18:57.615334Z",
"sent": null,
"completed_at": null,
"label": "Contract Signature Request",
"in_order": false,
"is_test": true,
"status": "pe",
"custom_key": null,
"custom_text": "",
"packets": [
{
"id": "a6v1qN4z8T",
"key": "signer-1",
"name": "John Doe",
"email": "[email protected]",
"phone": "",
"auth_sms": false,
"auth_selfie": false,
"auth_secrets": null,
"auth_id": false,
"person_id": "db600721-d8e7-42d3-8ebe-a180b4864eb8",
"status": "ne",
"deliver_via": "email",
"completed_at": null,
"last_accessed_at": null,
"order": 1,
"signing_complete_redirect": "",
"suppress_all": false,
"suppress_docs_ready": false,
"suppress_signing": false,
"suppress_reminder": false
}
],
"documents": [],
"errors": null,
"cc_emails": [],
"email_message": "",
"email_subject": "",
"team": null,
"tags": [],
"send_reminders": false,
"reminder_offset": 0,
"reminder_interval": 0,
"reminder_expires": 0,
"expires": null,
"sms_message": "",
"requester_name": "",
"requester_email": "",
"payment": null
}

Status Codes Explained

  • Bundle Status: pe = Pending (processing for automatic sending)
  • Packet Status: ne = Not sent yet (will be sent automatically)
  • Document Status: pr = Processing (documents array may be empty initially)
info

Envelope States:

  • pe = Pending (default when created, will be automatically sent)
  • dr = Draft (add "status": "dr" to bundle request to create as draft that won't be sent)
  • se = Sent (after background processing completes)

Envelopes are automatically sent by background processes after creation unless explicitly created as drafts.

Error Handling and Best Practices

Common Error Scenarios

  1. File Not Found (400 Bad Request)

    {
    "errors": [
    {
    "message": "File 0 not found in request",
    "code": "invalid"
    }
    ]
    }
  2. Authentication Error (401 Unauthorized)

    {
    "detail": "Invalid token."
    }

Best Practices

  1. File Validation: Always validate file existence and type before uploading
  2. Error Handling: Implement comprehensive error handling for network and API errors
  3. Test Mode: Use is_test: true during development to avoid consuming credits
  4. File Size: Keep PDF files under 25MB for optimal performance
  5. Timeout Handling: Set appropriate timeouts for file upload requests

Advanced Configuration

Adding Multiple Signers

{
"packets": [
{
"key": "signer-1",
"name": "John Doe",
"email": "[email protected]",
"deliver_via": "email",
"order": 1
},
{
"key": "signer-2",
"name": "Jane Smith",
"email": "[email protected]",
"deliver_via": "email",
"order": 2
}
]
}

Adding Signature Fields

{
"documents": [
{
"key": "contract-doc",
"file_index": "0",
"filename": "contract.pdf",
"fields": [
{
"key": "signature-1",
"kind": "esignature",
"editors": ["signer-1"],
"x": 100,
"y": 200,
"w": 200,
"h": 50,
"p": 1
}
]
}
]
}

Next Steps

Now that you've successfully uploaded a PDF and created an envelope, you might want to:

  • Add signature fields and form elements - Learn how to programmatically add signature fields to your documents
  • Set up webhooks for status notifications
  • Implement embedded signing - Integrate signing directly into your application
  • Handle multiple documents in one envelope - Create complex signing workflows

Troubleshooting

If you encounter issues:

  1. Check your API key in the Blueink Dashboard
  2. Verify file path and ensure the PDF exists
  3. Review error messages in the API response
  4. Test with a simple PDF to isolate file-specific issues
  5. Check network connectivity and firewall settings

For additional support, visit our FAQ or contact Blueink support.

Complete Working Examples

Python Complete Example

Here's a complete, production-ready Python script that includes all error handling and best practices:

Click to expand complete Python example
#!/usr/bin/env python3
"""
Blueink PDF Upload and Envelope Creation Example
Complete script with error handling and validation
"""

import os
import sys
import json
import requests
from pathlib import Path

class BlueinkEnvelopeCreator:
def __init__(self, api_key, base_url="https://api.blueink.com/api/v2"):
self.api_key = api_key
self.base_url = base_url
self.headers = {"Authorization": f"Token {api_key}"}

def validate_pdf_file(self, file_path):
"""Validate that the file exists and is a PDF"""
path = Path(file_path)

if not path.exists():
raise FileNotFoundError(f"File not found: {file_path}")

if not path.suffix.lower() == '.pdf':
raise ValueError(f"File must be a PDF: {file_path}")

# Check file size (25MB limit)
file_size = path.stat().st_size
max_size = 25 * 1024 * 1024 # 25MB in bytes
if file_size > max_size:
raise ValueError(f"File too large: {file_size} bytes (max: {max_size})")

return True

def create_envelope_with_pdf(self, pdf_path, signer_name, signer_email,
label="Document for Signature", is_test=True):
"""Create an envelope with an uploaded PDF file"""

# Validate inputs
self.validate_pdf_file(pdf_path)

if not signer_email or '@' not in signer_email:
raise ValueError("Valid signer email is required")

if not signer_name:
raise ValueError("Signer name is required")

# Prepare bundle request
bundle_request = {
"label": label,
"is_test": is_test,
# "status": "dr", # Uncomment to create as draft (default is pending)
"packets": [
{
"key": "signer-1",
"name": signer_name,
"email": signer_email,
"deliver_via": "email"
}
],
"documents": [
{
"key": "uploaded-doc",
"file_index": "0",
"filename": Path(pdf_path).name
}
]
}

try:
with open(pdf_path, 'rb') as pdf_file:
files = {
'files[0]': (Path(pdf_path).name, pdf_file, 'application/pdf')
}

data = {
'bundle_request': json.dumps(bundle_request)
}

print(f"📤 Uploading {Path(pdf_path).name} and creating envelope...")

response = requests.post(
f"{self.base_url}/bundles/",
headers=self.headers,
files=files,
data=data,
timeout=60 # 60 second timeout for file uploads
)

if response.status_code == 201:
bundle = response.json()
print(f"✅ Envelope created successfully!")
print(f" Bundle ID: {bundle['id']}")
print(f" Status: {bundle['status']}")
print(f" Label: {bundle['label']}")
print(f" Dashboard URL: https://app.blueink.com/bundles/{bundle['id']}")
return bundle
else:
print(f"❌ Error creating envelope: {response.status_code}")
try:
error_data = response.json()
if 'errors' in error_data:
for error in error_data['errors']:
print(f" Error: {error.get('message', 'Unknown error')}")
else:
print(f" Response: {error_data}")
except:
print(f" Response: {response.text}")
return None

except requests.exceptions.Timeout:
print("❌ Request timed out. Please try again.")
return None
except requests.exceptions.ConnectionError:
print("❌ Connection error. Please check your internet connection.")
return None
except requests.exceptions.RequestException as e:
print(f"❌ Request failed: {e}")
return None

def main():
"""Main function to demonstrate usage"""

# Configuration - replace with your values
API_KEY = os.getenv('BLUEINK_API_KEY')
PDF_FILE_PATH = "sample-contract.pdf"
SIGNER_NAME = "John Doe"
SIGNER_EMAIL = "[email protected]"

if not API_KEY:
print("❌ Please set BLUEINK_API_KEY environment variable")
sys.exit(1)

# Create envelope creator instance
creator = BlueinkEnvelopeCreator(API_KEY)

try:
# Create envelope with uploaded PDF
bundle = creator.create_envelope_with_pdf(
pdf_path=PDF_FILE_PATH,
signer_name=SIGNER_NAME,
signer_email=SIGNER_EMAIL,
label="Contract Signature Request",
is_test=True # Set to False for production
)

if bundle:
print(f"✅ Envelope created successfully! Bundle ID: {bundle['id']}")
print("📧 The envelope will be automatically sent to signers by background processes.")

except Exception as e:
print(f"❌ Unexpected error: {e}")
sys.exit(1)

if __name__ == "__main__":
main()

JavaScript Complete Example

Here's a complete Node.js script with full error handling:

Click to expand complete JavaScript example
#!/usr/bin/env node
/**
* Blueink PDF Upload and Envelope Creation Example
* Complete Node.js script with error handling and validation
*/

const fs = require('fs').promises;
const fsSync = require('fs');
const path = require('path');
const FormData = require('form-data');
const axios = require('axios');


class BlueinkEnvelopeCreator {
constructor(apiKey, baseUrl = 'https://api.blueink.com/api/v2') {
this.apiKey = apiKey;
this.baseUrl = baseUrl;
this.headers = {
'Authorization': `Token ${apiKey}`
};
}

async validatePdfFile(filePath) {
try {
const stats = await fs.stat(filePath);

// Check if file exists and is readable
if (!stats.isFile()) {
throw new Error(`Path is not a file: ${filePath}`);
}

// Check file extension
if (path.extname(filePath).toLowerCase() !== '.pdf') {
throw new Error(`File must be a PDF: ${filePath}`);
}

// Check file size (25MB limit)
const maxSize = 25 * 1024 * 1024; // 25MB in bytes
if (stats.size > maxSize) {
throw new Error(`File too large: ${stats.size} bytes (max: ${maxSize})`);
}

return true;
} catch (error) {
if (error.code === 'ENOENT') {
throw new Error(`File not found: ${filePath}`);
}
throw error;
}
}

async createEnvelopeWithPdf(pdfPath, signerName, signerEmail,
label = 'Document for Signature', isTest = true) {
// Validate inputs
await this.validatePdfFile(pdfPath);

if (!signerEmail || !signerEmail.includes('@')) {
throw new Error('Valid signer email is required');
}

if (!signerName) {
throw new Error('Signer name is required');
}

// Prepare bundle request
const bundleRequest = {
label: label,
is_test: isTest,
// status: 'dr', // Uncomment to create as draft (default is pending)
packets: [
{
key: 'signer-1',
name: signerName,
email: signerEmail,
deliver_via: 'email'
}
],
documents: [
{
key: 'uploaded-doc',
file_index: '0',
filename: path.basename(pdfPath)
}
]
};

try {
// Create form data
const form = new FormData();

// Add the PDF file
form.append('files[0]', fsSync.createReadStream(pdfPath), {
filename: path.basename(pdfPath),
contentType: 'application/pdf'
});

// Add bundle request as JSON string
form.append('bundle_request', JSON.stringify(bundleRequest));

console.log(`📤 Uploading ${path.basename(pdfPath)} and creating envelope...`);

// Make the request with timeout
const response = await axios.post(`${this.baseUrl}/bundles/`, form, {
headers: {
...this.headers,
...form.getHeaders()
},
timeout: 60000, // 60 second timeout
maxContentLength: Infinity,
maxBodyLength: Infinity
});

if (response.status === 201) {
const bundle = response.data;
console.log('✅ Envelope created successfully!');
console.log(` Bundle ID: ${bundle.id}`);
console.log(` Status: ${bundle.status}`);
console.log(` Label: ${bundle.label}`);
console.log(` Dashboard URL: https://app.blueink.com/bundles/${bundle.id}`);
return bundle;
}

} catch (error) {
if (error.code === 'ECONNABORTED') {
console.error('❌ Request timed out. Please try again.');
} else if (error.response) {
console.error(`❌ Error creating envelope: ${error.response.status}`);
if (error.response.data && error.response.data.errors) {
error.response.data.errors.forEach(err => {
console.error(` Error: ${err.message || 'Unknown error'}`);
});
} else {
console.error(` Response: ${JSON.stringify(error.response.data, null, 2)}`);
}
} else if (error.request) {
console.error('❌ No response received. Please check your internet connection.');
} else {
console.error(`❌ Error: ${error.message}`);
}
throw error;
}
}


}

async function main() {
// Configuration - replace with your values
const API_KEY = process.env.BLUEINK_API_KEY;
const PDF_FILE_PATH = 'sample-contract.pdf';
const SIGNER_NAME = 'John Doe';
const SIGNER_EMAIL = '[email protected]';

if (!API_KEY) {
console.error('❌ Please set BLUEINK_API_KEY environment variable');
process.exit(1);
}

// Create envelope creator instance
const creator = new BlueinkEnvelopeCreator(API_KEY);

try {
// Create envelope with uploaded PDF
const bundle = await creator.createEnvelopeWithPdf(
PDF_FILE_PATH,
SIGNER_NAME,
SIGNER_EMAIL,
'Contract Signature Request',
true // Set to false for production
);

if (bundle) {
console.log(`✅ Envelope created successfully! Bundle ID: ${bundle.id}`);
console.log('📧 The envelope will be automatically sent to signers by background processes.');
}

} catch (error) {
console.error(`❌ Unexpected error: ${error.message}`);
process.exit(1);
}
}

// Run the script
if (require.main === module) {
main().catch(error => {
console.error('Script failed:', error.message);
process.exit(1);
});
}

module.exports = { BlueInkEnvelopeCreator };

Package Dependencies

Python Requirements

Create a requirements.txt file:

requests>=2.28.0

Install with:

pip install -r requirements.txt

JavaScript Dependencies

For Node.js, install the required packages:

npm install axios form-data

Or add to your package.json:

{
"dependencies": {
"axios": "^1.6.0",
"form-data": "^4.0.0"
}
}

Environment Setup

Setting Environment Variables

Linux/macOS:

export BLUEINK_API_KEY="your_api_key_here"

Windows:

set BLUEINK_API_KEY=your_api_key_here

Using .env file (recommended): Create a .env file in your project root:

BLUEINK_API_KEY=your_api_key_here

Then load it in your application using appropriate libraries (python-dotenv for Python, dotenv for Node.js).

Testing Your Integration

  1. Start with test mode: Always use is_test: true during development
  2. Use a simple PDF: Test with a basic, single-page PDF first
  3. Verify file paths: Ensure your PDF file exists and is accessible
  4. Check API responses: Log full responses to understand any errors
  5. Test error scenarios: Try invalid files, missing fields, etc.

Security Considerations

  1. API Key Protection: Never expose your API key in client-side code
  2. File Validation: Always validate file types and sizes before upload
  3. Input Sanitization: Validate all user inputs (emails, names, etc.)
  4. HTTPS Only: Always use HTTPS for API requests
  5. Error Handling: Don't expose sensitive information in error messages

This completes the comprehensive guide for uploading PDF files and creating envelopes with BlueInk's API v2. The examples include production-ready code with proper error handling, validation, and best practices.