File Uploads
When creating invoices or credit notes via the Billabex API, you need to attach a document file (PDF or image). This guide explains the supported file formats, size limits, and encoding options.
Overview
File uploads are required for:
POST /api/public/v1/invoices– Create an invoicePOST /api/public/v1/credit-notes– Create a credit note
Both endpoints accept multipart/form-data requests with a file attachment.
Supported File Formats
| MIME Type | Extension | Description |
|---|---|---|
application/pdf | PDF documents (recommended) | |
image/png | .png | PNG images |
image/jpeg | .jpg, .jpeg | JPEG images |
image/webp | .webp | WebP images |
Size Limits
- Maximum file size: 10MB (decoded binary size)
- Files exceeding this limit will be rejected with a
400 Bad Requesterror
Encoding Options
Billabex supports two encoding methods for file uploads:
| Encoding | Content-Transfer-Encoding | Use Case |
|---|---|---|
| Binary | 7bit, 8bit, or binary | Default, most HTTP clients |
| Base64 | base64 | Legacy systems, NetSuite, etc. |
Binary Upload (Default)
Most HTTP clients send files as binary data by default. This is the recommended method when your platform supports it.
JavaScript Example
const formData = new FormData();
formData.append('accountId', 'ACCOUNT_ID');
formData.append('number', 'INV-2024-001');
formData.append('issuedDate.year', '2024');
formData.append('issuedDate.month', '1');
formData.append('issuedDate.day', '15');
formData.append('dueDate.year', '2024');
formData.append('dueDate.month', '2');
formData.append('dueDate.day', '15');
formData.append('totalAmount', '1500.00');
formData.append('taxAmount', '300.00');
formData.append('paidAmount', '0');
formData.append('billingAddress.street', '123 Main Street');
formData.append('billingAddress.city', 'Paris');
formData.append('billingAddress.postalCode', '75001');
formData.append('billingAddress.country', 'FR');
formData.append('file', fileBlob, 'invoice.pdf');
const response = await fetch('[baseURL]/api/public/v1/invoices', {
method: 'POST',
headers: {
Authorization: `Bearer ${accessToken}`,
},
body: formData,
});
cURL Example
curl -X POST "[baseURL]/api/public/v1/invoices" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-F "accountId=ACCOUNT_ID" \
-F "number=INV-2024-001" \
-F "issuedDate.year=2024" \
-F "issuedDate.month=1" \
-F "issuedDate.day=15" \
-F "dueDate.year=2024" \
-F "dueDate.month=2" \
-F "dueDate.day=15" \
-F "totalAmount=1500.00" \
-F "taxAmount=300.00" \
-F "paidAmount=0" \
-F "billingAddress.street=123 Main Street" \
-F "billingAddress.city=Paris" \
-F "billingAddress.postalCode=75001" \
-F "billingAddress.country=FR" \
-F "file=@invoice.pdf"
Base64 Upload
Some platforms (like NetSuite) cannot send binary data directly and must encode files as base64. Billabex automatically detects and decodes base64-encoded files.
To use base64 encoding:
- Encode your file content as base64
- Set the
Content-Transfer-Encodingheader tobase64in the file part
How Base64 Works
Base64 encoding converts binary data to ASCII text, increasing the size by approximately 33%. A 10MB file becomes ~13.3MB when base64-encoded.
Billabex handles this automatically:
- The 10MB limit applies to the decoded binary size
- Base64-encoded uploads up to ~13.7MB are accepted
- The file is decoded server-side before storage
JavaScript Example (Base64)
// Read file and encode as base64
const fileBuffer = await file.arrayBuffer();
const base64Content = btoa(String.fromCharCode(...new Uint8Array(fileBuffer)));
// Create multipart request with base64-encoded file
const boundary = '----WebKitFormBoundary' + Math.random().toString(36).slice(2);
const body = [
`--${boundary}`,
'Content-Disposition: form-data; name="accountId"',
'',
'ACCOUNT_ID',
`--${boundary}`,
'Content-Disposition: form-data; name="number"',
'',
'INV-2024-001',
// ... other fields ...
`--${boundary}`,
'Content-Disposition: form-data; name="file"; filename="invoice.pdf"',
'Content-Type: application/pdf',
'Content-Transfer-Encoding: base64',
'',
base64Content,
`--${boundary}--`,
].join('\r\n');
const response = await fetch('[baseURL]/api/public/v1/invoices', {
method: 'POST',
headers: {
Authorization: `Bearer ${accessToken}`,
'Content-Type': `multipart/form-data; boundary=${boundary}`,
},
body: body,
});
Node.js Example (Base64)
const fs = require('fs');
const path = require('path');
// Read file and encode as base64
const filePath = './invoice.pdf';
const fileContent = fs.readFileSync(filePath);
const base64Content = fileContent.toString('base64');
const filename = path.basename(filePath);
// Build multipart body manually
const boundary = '----FormBoundary' + Date.now();
const CRLF = '\r\n';
const parts = [`--${boundary}`, 'Content-Disposition: form-data; name="accountId"', '', 'ACCOUNT_ID', `--${boundary}`, 'Content-Disposition: form-data; name="number"', '', 'INV-2024-001', `--${boundary}`, 'Content-Disposition: form-data; name="issuedDate.year"', '', '2024', `--${boundary}`, 'Content-Disposition: form-data; name="issuedDate.month"', '', '1', `--${boundary}`, 'Content-Disposition: form-data; name="issuedDate.day"', '', '15', `--${boundary}`, 'Content-Disposition: form-data; name="dueDate.year"', '', '2024', `--${boundary}`, 'Content-Disposition: form-data; name="dueDate.month"', '', '2', `--${boundary}`, 'Content-Disposition: form-data; name="dueDate.day"', '', '15', `--${boundary}`, 'Content-Disposition: form-data; name="totalAmount"', '', '1500.00', `--${boundary}`, 'Content-Disposition: form-data; name="taxAmount"', '', '300.00', `--${boundary}`, 'Content-Disposition: form-data; name="paidAmount"', '', '0', `--${boundary}`, 'Content-Disposition: form-data; name="billingAddress.street"', '', '123 Main Street', `--${boundary}`, 'Content-Disposition: form-data; name="billingAddress.city"', '', 'Paris', `--${boundary}`, 'Content-Disposition: form-data; name="billingAddress.postalCode"', '', '75001', `--${boundary}`, 'Content-Disposition: form-data; name="billingAddress.country"', '', 'FR', `--${boundary}`, `Content-Disposition: form-data; name="file"; filename="${filename}"`, 'Content-Type: application/pdf', 'Content-Transfer-Encoding: base64', '', base64Content, `--${boundary}--`];
const body = parts.join(CRLF);
const response = await fetch('[baseURL]/api/public/v1/invoices', {
method: 'POST',
headers: {
Authorization: `Bearer ${accessToken}`,
'Content-Type': `multipart/form-data; boundary=${boundary}`,
},
body: body,
});
Python Example (Base64)
import base64
import requests
# Read and encode file
with open('invoice.pdf', 'rb') as f:
file_content = f.read()
base64_content = base64.b64encode(file_content).decode('ascii')
# Build multipart body
boundary = '----PythonFormBoundary'
crlf = '\r\n'
body_parts = [
f'--{boundary}',
'Content-Disposition: form-data; name="accountId"',
'',
'ACCOUNT_ID',
f'--{boundary}',
'Content-Disposition: form-data; name="number"',
'',
'INV-2024-001',
# ... other fields ...
f'--{boundary}',
'Content-Disposition: form-data; name="file"; filename="invoice.pdf"',
'Content-Type: application/pdf',
'Content-Transfer-Encoding: base64',
'',
base64_content,
f'--{boundary}--',
]
body = crlf.join(body_parts)
response = requests.post(
'[baseURL]/api/public/v1/invoices',
headers={
'Authorization': f'Bearer {access_token}',
'Content-Type': f'multipart/form-data; boundary={boundary}',
},
data=body.encode('utf-8'),
)
Error Handling
| Error | HTTP Status | Description |
|---|---|---|
| No file uploaded | 400 | The file field is missing from the request |
| File too large | 400 | File exceeds 10MB (decoded size) |
| Invalid file type | 400 | MIME type not in allowed list |
| Invalid multipart | 400 | Malformed multipart request |
Example error response:
{
"statusCode": 400,
"message": "File too large. Maximum size: 10485760 bytes",
"error": "Bad Request"
}
Best Practices
Choose the Right Encoding
- Use binary (default) whenever possible – it’s more efficient
- Use base64 only when your platform doesn’t support binary uploads
Validate Files Client-Side
Check file size and type before uploading to avoid unnecessary API calls:
const MAX_SIZE = 10 * 1024 * 1024; // 10MB
const ALLOWED_TYPES = ['application/pdf', 'image/png', 'image/jpeg', 'image/webp'];
if (file.size > MAX_SIZE) {
throw new Error('File too large');
}
if (!ALLOWED_TYPES.includes(file.type)) {
throw new Error('Invalid file type');
}
Use PDF When Possible
PDF is the recommended format for invoice documents:
- Preserves formatting across devices
- Supports text extraction
- Smaller file sizes for text-heavy documents
Next Steps
- Getting Started – API basics and authentication
- API Reference – Full endpoint documentation
Support
Questions about file uploads?
Contact us via the website contact form.