Documentation Index Fetch the complete documentation index at: https://apidocs.fifteenth.com/llms.txt
Use this file to discover all available pages before exploring further.
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
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
The unique Fifteenth account identifier to upload documents to. Format : Numeric ID
Example : 12345
Bearer token with your Partner API key
Must be multipart/form-data for file uploads
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 this document relates to. Range : 2020-2025
Example : 2024
Human-readable description of the document. Max : 500 characters
Example : “W-2 from ABC Corporation for 2024”
Response
Success Response
Unique document identifier.
The account this document belongs to.
Original filename of the uploaded document.
Tax year for this document.
MIME type of the uploaded file.
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
{
"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
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" ]
}
}
}
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
}
}
}
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
Retrieve Document Get document details and metadata
List Documents View all documents for an account
Project Status Monitor how documents contribute to tax projects
Authentication Learn about API keys and security best practices