Data Management

File Uploads

Upload invoice and credit note documents using binary or base64 encoding.

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 invoice
  • POST /api/public/v1/credit-notes – Create a credit note

Both endpoints accept multipart/form-data requests with a file attachment.

Supported File Formats

MIME TypeExtensionDescription
application/pdf.pdfPDF documents (recommended)
image/png.pngPNG images
image/jpeg.jpg, .jpegJPEG images
image/webp.webpWebP images

Size Limits

  • Maximum file size: 10MB (decoded binary size)
  • Files exceeding this limit will be rejected with a 400 Bad Request error

Encoding Options

Billabex supports two encoding methods for file uploads:

EncodingContent-Transfer-EncodingUse Case
Binary7bit, 8bit, or binaryDefault, most HTTP clients
Base64base64Legacy 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:

  1. Encode your file content as base64
  2. Set the Content-Transfer-Encoding header to base64 in 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

ErrorHTTP StatusDescription
No file uploaded400The file field is missing from the request
File too large400File exceeds 10MB (decoded size)
Invalid file type400MIME type not in allowed list
Invalid multipart400Malformed 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

Support

Questions about file uploads?
Contact us via the website contact form.