Skip to main content
POST
https://api.fifteenth.com
/
v1beta
/
accounts
/
{account_id}
/
documents
Upload Document
curl --request POST \
  --url https://api.fifteenth.com/v1beta/accounts/{account_id}/documents \
  --header 'Authorization: <authorization>' \
  --header 'Content-Type: <content-type>' \
  --data '
{
  "tax_year": 123,
  "description": "<string>"
}
'
{
  "id": 123,
  "account_id": 123,
  "filename": "<string>",
  "tax_year": 123,
  "description": "<string>",
  "file_size": 123,
  "file_type": "<string>",
  "uploaded_at": "<string>",
  "400 Bad Request": {},
  "413 Payload Too Large": {},
  "422 Unprocessable Entity": {}
}

Overview

The Document Upload endpoint allows you to upload tax documents directly to a client’s Fifteenth account. Documents are stored securely and can be organized by tax year with custom descriptions.
Documents are stored securely and can be retrieved using the document ID.

Supported Document Types

Tax Forms

  • W-2: Wage and tax statements
  • 1099 Series: 1099-INT, 1099-DIV, 1099-B, 1099-R, etc.
  • 1098: Mortgage interest statements
  • K-1: Partnership, S-Corp, trust distributions
  • Prior Year Returns: Previous tax returns

Supporting Documents

  • Bank Statements: Account summaries and transactions
  • Investment Statements: Brokerage and retirement accounts
  • Receipts: Business expenses, charitable donations
  • Legal Documents: Divorce decrees, adoption papers
  • Business Records: P&L statements, balance sheets

File Requirements

  • Formats: PDF, PNG, JPG, JPEG, TIFF
  • Size Limit: 25MB per file
  • Quality: Minimum 300 DPI for optimal OCR
  • Pages: Up to 50 pages per document

Request

Path Parameters

account_id
number
required
The unique Fifteenth account identifier to upload documents to.Format: Numeric ID
Example: 12345

Headers

Authorization
string
required
Bearer token with your Partner API key
Content-Type
string
required
Must be multipart/form-data for file uploads

Form Data Parameters

file
file
required
The tax document file to upload.Formats: PDF, PNG, JPG, JPEG, TIFF
Size: Maximum 25MB
Quality: 300 DPI recommended for best OCR results
tax_year
integer
required
Tax year this document relates to.Range: 2020-2025
Example: 2024
description
string
Human-readable description of the document.Max: 500 characters
Example: “W-2 from ABC Corporation for 2024”

Response

Success Response

id
number
Unique document identifier.
account_id
number
The account this document belongs to.
filename
string
Original filename of the uploaded document.
tax_year
integer
Tax year for this document.
description
string
Document description.
file_size
integer
File size in bytes.
file_type
string
MIME type of the uploaded file.
uploaded_at
string
ISO 8601 timestamp when document was uploaded.

Examples

Basic Document Upload

import requests

account_id = 12345
url = f"https://api.fifteenth.com/v1beta/accounts/{account_id}/documents"

headers = {
    "Authorization": "Bearer 15th_your_api_key_here"
}

# Upload W-2 document
with open('w2_2024.pdf', 'rb') as file:
    files = {'file': file}
    data = {
        'tax_year': '2024',
        'description': 'W-2 from ABC Corporation'
    }
    
    response = requests.post(url, headers=headers, files=files, data=data)
    document = response.json()
    
print(f"Document uploaded: {document['id']}")
print(f"Tax year: {document['tax_year']}")
print(f"Description: {document['description']}")

Multiple Document Upload

import requests
import os
from concurrent.futures import ThreadPoolExecutor

def upload_document(account_id, file_path, doc_info):
    """Upload a single document"""
    url = f"https://api.fifteenth.com/v1beta/accounts/{account_id}/documents"
    headers = {"Authorization": "Bearer 15th_your_api_key_here"}
    
    with open(file_path, 'rb') as file:
        files = {'file': file}
        response = requests.post(url, headers=headers, files=files, data=doc_info)
        return response.json()

# Define documents to upload
documents = [
    {
        'file_path': 'w2_2024.pdf',
        'doc_info': {
            'tax_year': '2024',
            'description': 'W-2 from employer'
        }
    },
    {
        'file_path': '1099_div.pdf', 
        'doc_info': {
            'tax_year': '2024',
            'description': 'Dividend income from investments'
        }
    }
]

# Upload documents in parallel
with ThreadPoolExecutor(max_workers=3) as executor:
    futures = [
        executor.submit(upload_document, account_id, doc['file_path'], doc['doc_info'])
        for doc in documents
    ]
    
    results = [future.result() for future in futures]

for result in results:
    print(f"Uploaded: {result['filename']} - ID: {result['id']}")

Response Examples

Successful Upload Response

Response
{
  "id": 12345,
  "account_id": 12345,
  "filename": "w2_2024.pdf",
  "tax_year": 2024,
  "description": "W-2 from ABC Corporation",
  "file_size": 245760,
  "file_type": "application/pdf",
  "uploaded_at": "2024-01-15T10:35:00Z"
}

Error Responses

400 Bad Request
object
Invalid file or request parameters.
{
  "error": {
    "code": "INVALID_FILE_TYPE",
    "message": "Unsupported file type. Supported formats: PDF, PNG, JPG, JPEG, TIFF",
    "details": {
      "provided_type": "application/msword",
      "supported_types": ["application/pdf", "image/png", "image/jpeg", "image/tiff"]
    }
  }
}
413 Payload Too Large
object
File exceeds size limit.
{
  "error": {
    "code": "FILE_TOO_LARGE",
    "message": "File size exceeds 25MB limit",
    "details": {
      "file_size_mb": 32.5,
      "max_size_mb": 25
    }
  }
}
422 Unprocessable Entity
object
File cannot be processed (corrupted, encrypted, etc.).
{
  "error": {
    "code": "UNPROCESSABLE_FILE",
    "message": "File appears to be corrupted or encrypted",
    "details": {
      "file_analysis": "PDF appears to be password protected",
      "suggestion": "Please upload an unencrypted version"
    }
  }
}

Best Practices

File Optimization

For best results:
  • Use 300 DPI or higher resolution
  • Ensure good lighting and contrast
  • Avoid shadows and glare
  • Keep text straight and unrotated
  • Use text-based PDFs when possible (not scanned images)
  • Ensure PDFs are not password protected
  • Combine related pages into single documents
  • Compress large PDFs while maintaining quality
  • Upload related documents together
  • Use consistent naming conventions
  • Include clear descriptions for organization

Error Handling

def robust_document_upload(account_id, file_path, doc_info, max_retries=3):
    """Upload document with retry logic"""
    url = f"https://api.fifteenth.com/v1beta/accounts/{account_id}/documents"
    headers = {"Authorization": "Bearer 15th_your_api_key_here"}
    
    for attempt in range(max_retries):
        try:
            with open(file_path, 'rb') as file:
                files = {'file': file}
                response = requests.post(url, headers=headers, files=files, data=doc_info)
                
                if response.status_code == 200:
                    return response.json()
                elif response.status_code == 413:
                    # File too large - don't retry
                    raise Exception(f"File too large: {response.json()['error']['message']}")
                elif response.status_code in [500, 502, 503]:
                    # Server error - retry
                    if attempt &lt; max_retries - 1:
                        time.sleep(2 ** attempt)  # Exponential backoff
                        continue
                    else:
                        raise Exception(f"Server error after {max_retries} attempts")
                else:
                    # Client error - don't retry
                    raise Exception(f"Client error: {response.json()['error']['message']}")
                    
        except requests.exceptions.RequestException as e:
            if attempt &lt; max_retries - 1:
                time.sleep(2 ** attempt)
                continue
            else:
                raise Exception(f"Network error after {max_retries} attempts: {str(e)}")
    
    raise Exception(f"Upload failed after {max_retries} attempts")

Next Steps