# 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](/docs/esignature-api/authentication/) 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.

#### Python

### Using the Python SDK

```python
from blueink.client import Client

client = Client(api_key="your_api_key_here")

# Define bundle details
bundle_data = {
    "label": "Contract Signature Request",
    "is_test": True,
    "packets": [
        {
            "key": "signer-1",
            "name": "John Doe",
            "email": "john.doe@example.com",
            "deliver_via": "email"
        }
    ],
    "documents": [
        {
            "key": "contract-doc",
            "file_index": 0,
            "filename": "contract.pdf"
        }
    ]
}

# Open the file and send
with open("path/to/document.pdf", "rb") as f:
    files = [{"file": f, "filename": "contract.pdf"}]
    response = client.bundles.create(data=bundle_data, files=files)

if response.status == 201:
    print(f"✅ Envelope created successfully! Bundle ID: {response.data.id}")
```

### Using Requests (Raw HTTP)

```python
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,
    "packets": [
        {
            "key": "signer-1",
            "name": "John Doe",
            "email": "john.doe@example.com",
            "deliver_via": "email"
        }
    ],
    "documents": [
        {
            "key": "contract-doc",
            "file_index": 0,
            "filename": "contract.pdf"
        }
    ]
}

# Prepare multipart form data
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! Bundle ID: {bundle['id']}")
```

#### JavaScript

### Using the JavaScript SDK

```javascript
const { Client } = require('@blueink360/blueink-client-js');
const fs = require('fs');

const client = new Client('your_api_key_here');

const bundleData = {
    label: 'Contract Signature Request',
    is_test: true,
    packets: [
        {
            key: 'signer-1',
            name: 'John Doe',
            email: 'john.doe@example.com',
            deliver_via: 'email'
        }
    ],
    documents: [
        {
            key: 'contract-doc',
            file_index: 0,
            filename: 'contract.pdf'
        }
    ]
};

const files = [
    {
        file: fs.createReadStream('path/to/document.pdf'),
        filename: 'contract.pdf'
    }
];

client.bundles.create(bundleData, files)
    .then(response => {
        console.log(`✅ Envelope created successfully! Bundle ID: ${response.data.id}`);
    })
    .catch(error => {
        console.error('❌ Error creating envelope:', error);
    });
```

### Using Axios (Raw HTTP)

```javascript
const fs = require('fs');
const FormData = require('form-data');
const axios = require('axios');

// Configuration
const API_KEY = 'your_api_key_here';
const BASE_URL = 'https://api.blueink.com/api/v2';
const PDF_FILE_PATH = 'path/to/your/document.pdf';

const bundleRequest = {
    label: 'Contract Signature Request',
    is_test: true,
    packets: [
        {
            key: 'signer-1',
            name: 'John Doe',
            email: 'john.doe@example.com',
            deliver_via: 'email'
        }
    ],
    documents: [
        {
            key: 'contract-doc',
            file_index: 0,
            filename: 'contract.pdf'
        }
    ]
};

async function createEnvelope() {
    const form = new FormData();
    form.append('files[0]', fs.createReadStream(PDF_FILE_PATH));
    form.append('bundle_request', JSON.stringify(bundleRequest));

    const response = await axios.post(`${BASE_URL}/bundles/`, form, {
        headers: {
            'Authorization': `Token ${API_KEY}`,
            ...form.getHeaders()
        }
    });

    if (response.status === 201) {
        console.log(`✅ Envelope created successfully! Bundle ID: ${response.data.id}`);
    }
}

createEnvelope();
```

#### PHP

### Using the PHP SDK

```php
<?php
use Blueink\ClientSDK\Client;

$client = new Client("your_api_key_here");

$bundleData = [
    "label" => "Contract Signature Request",
    "is_test" => true,
    "packets" => [
        [
            "key" => "signer-1",
            "name" => "John Doe",
            "email" => "john.doe@example.com",
            "deliver_via" => "email"
        ]
    ],
    "documents" => [
        [
            "key" => "contract-doc",
            "file_index" => 0,
            "filename" => "contract.pdf"
        ]
    ]
];

$files = [
    [
        "file" => fopen("path/to/document.pdf", "r"),
        "filename" => "contract.pdf"
    ]
];

$response = $client->bundles->create($bundleData, $files);

if ($response->status === 201) {
    echo "✅ Envelope created successfully! Bundle ID: " . $response->data->id;
}
```

## Step 3: Understanding the Response

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

```json
{
    "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": "john.doe@example.com",
            "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)**
   ```json
   {
       "errors": [
           {
               "message": "File 0 not found in request",
               "code": "invalid"
           }
       ]
   }
   ```

2. **Authentication Error (401 Unauthorized)**
   ```json
   {
       "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

```json
{
    "packets": [
        {
            "key": "signer-1",
            "name": "John Doe",
            "email": "john.doe@example.com",
            "deliver_via": "email",
            "order": 1
        },
        {
            "key": "signer-2", 
            "name": "Jane Smith",
            "email": "jane.smith@example.com",
            "deliver_via": "email",
            "order": 2
        }
    ]
}
```

### Adding Signature Fields

```json
{
    "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](/docs/esignature-api/webhook/)
- **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](/docs/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:

<details>
<summary>Click to expand complete Python example</summary>

```python
#!/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 = "john.doe@example.com"

    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()
```

</details>

### JavaScript Complete Example

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

<details>
<summary>Click to expand complete JavaScript example</summary>

```javascript
#!/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 = 'john.doe@example.com';

    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 };
```

</details>

## Package Dependencies

### Python Requirements

Create a `requirements.txt` file:

```txt
requests>=2.28.0
```

Install with:
```bash
pip install -r requirements.txt
```

### JavaScript Dependencies

For Node.js, install the required packages:

```bash
npm install axios form-data
```

Or add to your `package.json`:

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

## Environment Setup

### Setting Environment Variables

**Linux/macOS:**
```bash
export BLUEINK_API_KEY="your_api_key_here"
```

**Windows:**
```cmd
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.
