# Upload a PDF invoice
curl -X POST "https://api.contazen.ro/v1/expenses/exp_abc123/attachments" \
-H "Authorization: Bearer YOUR_API_KEY" \
-F "file=@invoice_001.pdf" \
-F "description=Original supplier invoice"
# Upload an image receipt
curl -X POST "https://api.contazen.ro/v1/expenses/exp_abc123/attachments" \
-H "Authorization: Bearer YOUR_API_KEY" \
-F "file=@receipt.jpg" \
-F "description=Receipt photo"
const uploadAttachment = async (expenseId, file, description = '') => {
const formData = new FormData();
formData.append('file', file);
if (description) {
formData.append('description', description);
}
const response = await fetch(`https://api.contazen.ro/v1/expenses/${expenseId}/attachments`, {
method: 'POST',
headers: {
'Authorization': 'Bearer YOUR_API_KEY'
// Note: Don't set Content-Type header - let browser set it for multipart/form-data
},
body: formData
});
const data = await response.json();
if (data.success) {
console.log('File uploaded:', data.attachment.filename);
console.log('Download URL:', data.attachment.download_url);
return data.attachment;
} else {
throw new Error(data.error.message);
}
};
// Usage with file input
const handleFileUpload = async (expenseId, fileInputElement) => {
const file = fileInputElement.files[0];
if (!file) return;
try {
const attachment = await uploadAttachment(expenseId, file, 'Uploaded via web form');
alert('File uploaded successfully!');
} catch (error) {
alert('Upload failed: ' + error.message);
}
};
// Usage with drag-and-drop
const handleDrop = async (expenseId, event) => {
event.preventDefault();
const files = event.dataTransfer.files;
for (let i = 0; i < files.length; i++) {
try {
const attachment = await uploadAttachment(expenseId, files[i]);
console.log(`Uploaded: ${attachment.filename}`);
} catch (error) {
console.error(`Failed to upload ${files[i].name}:`, error.message);
}
}
};
function uploadExpenseAttachment($expenseId, $filePath, $description = '') {
if (!file_exists($filePath)) {
throw new Exception("File not found: $filePath");
}
$curl = curl_init();
$postFields = [
'file' => new CURLFile($filePath),
'description' => $description
];
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.contazen.ro/v1/expenses/{$expenseId}/attachments",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $postFields,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer YOUR_API_KEY'
]
]);
$response = curl_exec($curl);
$httpCode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
$data = json_decode($response, true);
curl_close($curl);
if ($httpCode == 201 && $data['success']) {
echo "Uploaded: {$data['attachment']['filename']} ({$data['attachment']['filesize']} bytes)\n";
return $data['attachment'];
} else {
throw new Exception($data['error']['message']);
}
}
// Usage examples
try {
$attachment = uploadExpenseAttachment('exp_abc123', '/path/to/invoice.pdf', 'Original invoice');
echo "Download URL: {$attachment['download_url']}\n";
} catch (Exception $e) {
echo "Upload failed: " . $e->getMessage() . "\n";
}
// Bulk upload from directory
function uploadExpenseAttachmentsFromDirectory($expenseId, $directory) {
$allowedTypes = ['pdf', 'jpg', 'jpeg', 'png', 'doc', 'docx', 'xls', 'xlsx'];
$uploadedFiles = [];
foreach (glob("$directory/*") as $filePath) {
$extension = strtolower(pathinfo($filePath, PATHINFO_EXTENSION));
if (in_array($extension, $allowedTypes)) {
try {
$filename = basename($filePath);
$attachment = uploadExpenseAttachment($expenseId, $filePath, "Auto-uploaded: $filename");
$uploadedFiles[] = $attachment;
} catch (Exception $e) {
echo "Failed to upload $filePath: " . $e->getMessage() . "\n";
}
}
}
return $uploadedFiles;
}
{
"success": true,
"attachment": {
"id": "att_def456",
"filename": "invoice_001.pdf",
"filesize": 245760,
"description": "Original supplier invoice",
"created_at": "2024-01-20 14:30:00",
"download_url": "https://api.contazen.ro/v1/files/download/att_def456"
},
"message": "Attachment uploaded successfully"
}
{
"success": false,
"error": {
"message": "No file was uploaded or upload failed",
"type": "invalid_request_error",
"code": "file_missing"
}
}
{
"success": false,
"error": {
"message": "Invalid file type. Allowed types: pdf, jpg, jpeg, png, doc, docx, xls, xlsx",
"type": "invalid_request_error",
"code": "invalid_file_type",
"allowed_types": ["pdf", "jpg", "jpeg", "png", "doc", "docx", "xls", "xlsx"]
}
}
{
"success": false,
"error": {
"message": "File size exceeds maximum allowed size",
"type": "invalid_request_error",
"code": "file_too_large",
"max_size": "10MB"
}
}
{
"success": false,
"error": {
"message": "Expense not found",
"type": "invalid_request_error",
"code": "resource_missing",
"param": "id"
}
}
{
"success": false,
"error": {
"message": "Failed to save attachment",
"type": "api_error",
"code": "upload_failed",
"details": "Server error occurred while processing the upload"
}
}
Expenses
Upload Attachment
Upload and attach files to an expense for documentation and compliance purposes
POST
/
expenses
/
{id}
/
attachments
# Upload a PDF invoice
curl -X POST "https://api.contazen.ro/v1/expenses/exp_abc123/attachments" \
-H "Authorization: Bearer YOUR_API_KEY" \
-F "file=@invoice_001.pdf" \
-F "description=Original supplier invoice"
# Upload an image receipt
curl -X POST "https://api.contazen.ro/v1/expenses/exp_abc123/attachments" \
-H "Authorization: Bearer YOUR_API_KEY" \
-F "file=@receipt.jpg" \
-F "description=Receipt photo"
const uploadAttachment = async (expenseId, file, description = '') => {
const formData = new FormData();
formData.append('file', file);
if (description) {
formData.append('description', description);
}
const response = await fetch(`https://api.contazen.ro/v1/expenses/${expenseId}/attachments`, {
method: 'POST',
headers: {
'Authorization': 'Bearer YOUR_API_KEY'
// Note: Don't set Content-Type header - let browser set it for multipart/form-data
},
body: formData
});
const data = await response.json();
if (data.success) {
console.log('File uploaded:', data.attachment.filename);
console.log('Download URL:', data.attachment.download_url);
return data.attachment;
} else {
throw new Error(data.error.message);
}
};
// Usage with file input
const handleFileUpload = async (expenseId, fileInputElement) => {
const file = fileInputElement.files[0];
if (!file) return;
try {
const attachment = await uploadAttachment(expenseId, file, 'Uploaded via web form');
alert('File uploaded successfully!');
} catch (error) {
alert('Upload failed: ' + error.message);
}
};
// Usage with drag-and-drop
const handleDrop = async (expenseId, event) => {
event.preventDefault();
const files = event.dataTransfer.files;
for (let i = 0; i < files.length; i++) {
try {
const attachment = await uploadAttachment(expenseId, files[i]);
console.log(`Uploaded: ${attachment.filename}`);
} catch (error) {
console.error(`Failed to upload ${files[i].name}:`, error.message);
}
}
};
function uploadExpenseAttachment($expenseId, $filePath, $description = '') {
if (!file_exists($filePath)) {
throw new Exception("File not found: $filePath");
}
$curl = curl_init();
$postFields = [
'file' => new CURLFile($filePath),
'description' => $description
];
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.contazen.ro/v1/expenses/{$expenseId}/attachments",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $postFields,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer YOUR_API_KEY'
]
]);
$response = curl_exec($curl);
$httpCode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
$data = json_decode($response, true);
curl_close($curl);
if ($httpCode == 201 && $data['success']) {
echo "Uploaded: {$data['attachment']['filename']} ({$data['attachment']['filesize']} bytes)\n";
return $data['attachment'];
} else {
throw new Exception($data['error']['message']);
}
}
// Usage examples
try {
$attachment = uploadExpenseAttachment('exp_abc123', '/path/to/invoice.pdf', 'Original invoice');
echo "Download URL: {$attachment['download_url']}\n";
} catch (Exception $e) {
echo "Upload failed: " . $e->getMessage() . "\n";
}
// Bulk upload from directory
function uploadExpenseAttachmentsFromDirectory($expenseId, $directory) {
$allowedTypes = ['pdf', 'jpg', 'jpeg', 'png', 'doc', 'docx', 'xls', 'xlsx'];
$uploadedFiles = [];
foreach (glob("$directory/*") as $filePath) {
$extension = strtolower(pathinfo($filePath, PATHINFO_EXTENSION));
if (in_array($extension, $allowedTypes)) {
try {
$filename = basename($filePath);
$attachment = uploadExpenseAttachment($expenseId, $filePath, "Auto-uploaded: $filename");
$uploadedFiles[] = $attachment;
} catch (Exception $e) {
echo "Failed to upload $filePath: " . $e->getMessage() . "\n";
}
}
}
return $uploadedFiles;
}
{
"success": true,
"attachment": {
"id": "att_def456",
"filename": "invoice_001.pdf",
"filesize": 245760,
"description": "Original supplier invoice",
"created_at": "2024-01-20 14:30:00",
"download_url": "https://api.contazen.ro/v1/files/download/att_def456"
},
"message": "Attachment uploaded successfully"
}
{
"success": false,
"error": {
"message": "No file was uploaded or upload failed",
"type": "invalid_request_error",
"code": "file_missing"
}
}
{
"success": false,
"error": {
"message": "Invalid file type. Allowed types: pdf, jpg, jpeg, png, doc, docx, xls, xlsx",
"type": "invalid_request_error",
"code": "invalid_file_type",
"allowed_types": ["pdf", "jpg", "jpeg", "png", "doc", "docx", "xls", "xlsx"]
}
}
{
"success": false,
"error": {
"message": "File size exceeds maximum allowed size",
"type": "invalid_request_error",
"code": "file_too_large",
"max_size": "10MB"
}
}
{
"success": false,
"error": {
"message": "Expense not found",
"type": "invalid_request_error",
"code": "resource_missing",
"param": "id"
}
}
{
"success": false,
"error": {
"message": "Failed to save attachment",
"type": "api_error",
"code": "upload_failed",
"details": "Server error occurred while processing the upload"
}
}
Overview
The Upload Attachment endpoint allows you to attach a single file to an expense record. This is essential for maintaining proper documentation, compliance with tax regulations, and audit trails. Expenses support one attachment at a time - uploading a new file replaces the previous one.Security Note: Files undergo multiple validation checks including MIME type verification and content validation to ensure security.
Each expense supports only one attachment. Uploading a new file will replace the existing attachment.
Path Parameters
string
required
The CzUid of the expense to attach the file to
Request Body
This endpoint usesmultipart/form-data encoding for file uploads:
file
required
The file to upload. See supported file types below.
Supported File Types
Files are validated at multiple levels:
- Extension validation
- MIME type verification
- Content header validation (e.g., PDFs must start with %PDF)
File Size Limits
Response
object
Information about the uploaded attachment
string
Success message confirming the upload
File Storage and Security
Storage Location
Files are stored securely on the server with:- Organized structure:
galleries/expenses/{firm_id}/{year}/{month}/ - Unique filenames: Generated to prevent conflicts and enhance security
- Access control: Only accessible to authorized users of the owning firm
Security Measures
- File validation: Content type verification beyond extension checking
- Virus scanning: Files may be scanned for malware (implementation dependent)
- Access logging: Download access is logged for audit purposes
- Firm isolation: Attachments are strictly isolated per firm
Integration with Expense Workflow
Automatic Attachment Detection
When retrieving expenses, attachments are automatically detected:{
"expense": {
"id": "exp_abc123",
"attachment": {
"url": "https://api.contazen.ro/v1/files/download/att_def456",
"type": "pdf"
}
}
}
Document Management
Attachments become part of the expense’s permanent record:- Audit trail: Preserved for compliance and audit requirements
- Version control: Multiple attachments can be added to an expense
- Integration: Can be referenced in reports and exports
Common Use Cases
Invoice Documentation
// Upload invoice PDF for expense documentation
const uploadInvoice = async (expenseId, invoiceFile) => {
const formData = new FormData();
formData.append('file', invoiceFile);
formData.append('description', 'Original supplier invoice');
const response = await uploadAttachment(expenseId, formData);
return response.attachment;
};
Receipt Management
// Upload receipt photo from mobile app
const uploadReceipt = async (expenseId, receiptPhoto) => {
const formData = new FormData();
formData.append('file', receiptPhoto);
formData.append('description', 'Receipt photo taken on mobile');
return await uploadAttachment(expenseId, formData);
};
Supporting Documentation
// Upload additional supporting documents
const uploadSupporting = async (expenseId, documents) => {
const attachments = [];
for (const doc of documents) {
const formData = new FormData();
formData.append('file', doc.file);
formData.append('description', doc.description);
const attachment = await uploadAttachment(expenseId, formData);
attachments.push(attachment);
}
return attachments;
};
# Upload a PDF invoice
curl -X POST "https://api.contazen.ro/v1/expenses/exp_abc123/attachments" \
-H "Authorization: Bearer YOUR_API_KEY" \
-F "file=@invoice_001.pdf" \
-F "description=Original supplier invoice"
# Upload an image receipt
curl -X POST "https://api.contazen.ro/v1/expenses/exp_abc123/attachments" \
-H "Authorization: Bearer YOUR_API_KEY" \
-F "file=@receipt.jpg" \
-F "description=Receipt photo"
const uploadAttachment = async (expenseId, file, description = '') => {
const formData = new FormData();
formData.append('file', file);
if (description) {
formData.append('description', description);
}
const response = await fetch(`https://api.contazen.ro/v1/expenses/${expenseId}/attachments`, {
method: 'POST',
headers: {
'Authorization': 'Bearer YOUR_API_KEY'
// Note: Don't set Content-Type header - let browser set it for multipart/form-data
},
body: formData
});
const data = await response.json();
if (data.success) {
console.log('File uploaded:', data.attachment.filename);
console.log('Download URL:', data.attachment.download_url);
return data.attachment;
} else {
throw new Error(data.error.message);
}
};
// Usage with file input
const handleFileUpload = async (expenseId, fileInputElement) => {
const file = fileInputElement.files[0];
if (!file) return;
try {
const attachment = await uploadAttachment(expenseId, file, 'Uploaded via web form');
alert('File uploaded successfully!');
} catch (error) {
alert('Upload failed: ' + error.message);
}
};
// Usage with drag-and-drop
const handleDrop = async (expenseId, event) => {
event.preventDefault();
const files = event.dataTransfer.files;
for (let i = 0; i < files.length; i++) {
try {
const attachment = await uploadAttachment(expenseId, files[i]);
console.log(`Uploaded: ${attachment.filename}`);
} catch (error) {
console.error(`Failed to upload ${files[i].name}:`, error.message);
}
}
};
function uploadExpenseAttachment($expenseId, $filePath, $description = '') {
if (!file_exists($filePath)) {
throw new Exception("File not found: $filePath");
}
$curl = curl_init();
$postFields = [
'file' => new CURLFile($filePath),
'description' => $description
];
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.contazen.ro/v1/expenses/{$expenseId}/attachments",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $postFields,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer YOUR_API_KEY'
]
]);
$response = curl_exec($curl);
$httpCode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
$data = json_decode($response, true);
curl_close($curl);
if ($httpCode == 201 && $data['success']) {
echo "Uploaded: {$data['attachment']['filename']} ({$data['attachment']['filesize']} bytes)\n";
return $data['attachment'];
} else {
throw new Exception($data['error']['message']);
}
}
// Usage examples
try {
$attachment = uploadExpenseAttachment('exp_abc123', '/path/to/invoice.pdf', 'Original invoice');
echo "Download URL: {$attachment['download_url']}\n";
} catch (Exception $e) {
echo "Upload failed: " . $e->getMessage() . "\n";
}
// Bulk upload from directory
function uploadExpenseAttachmentsFromDirectory($expenseId, $directory) {
$allowedTypes = ['pdf', 'jpg', 'jpeg', 'png', 'doc', 'docx', 'xls', 'xlsx'];
$uploadedFiles = [];
foreach (glob("$directory/*") as $filePath) {
$extension = strtolower(pathinfo($filePath, PATHINFO_EXTENSION));
if (in_array($extension, $allowedTypes)) {
try {
$filename = basename($filePath);
$attachment = uploadExpenseAttachment($expenseId, $filePath, "Auto-uploaded: $filename");
$uploadedFiles[] = $attachment;
} catch (Exception $e) {
echo "Failed to upload $filePath: " . $e->getMessage() . "\n";
}
}
}
return $uploadedFiles;
}
{
"success": true,
"attachment": {
"id": "att_def456",
"filename": "invoice_001.pdf",
"filesize": 245760,
"description": "Original supplier invoice",
"created_at": "2024-01-20 14:30:00",
"download_url": "https://api.contazen.ro/v1/files/download/att_def456"
},
"message": "Attachment uploaded successfully"
}
{
"success": false,
"error": {
"message": "No file was uploaded or upload failed",
"type": "invalid_request_error",
"code": "file_missing"
}
}
{
"success": false,
"error": {
"message": "Invalid file type. Allowed types: pdf, jpg, jpeg, png, doc, docx, xls, xlsx",
"type": "invalid_request_error",
"code": "invalid_file_type",
"allowed_types": ["pdf", "jpg", "jpeg", "png", "doc", "docx", "xls", "xlsx"]
}
}
{
"success": false,
"error": {
"message": "File size exceeds maximum allowed size",
"type": "invalid_request_error",
"code": "file_too_large",
"max_size": "10MB"
}
}
{
"success": false,
"error": {
"message": "Expense not found",
"type": "invalid_request_error",
"code": "resource_missing",
"param": "id"
}
}
{
"success": false,
"error": {
"message": "Failed to save attachment",
"type": "api_error",
"code": "upload_failed",
"details": "Server error occurred while processing the upload"
}
}
Best Practices
File Organization
- Naming conventions: Use descriptive filenames that identify the expense
- File types: Prefer PDF for official documents, JPEG/PNG for photos
- File sizes: Optimize images to reduce file size while maintaining readability
- Descriptions: Always include meaningful descriptions for better organization
Upload Workflow
- Validation: Check file type and size before uploading
- Progress indicators: Show upload progress for large files
- Error handling: Implement proper error handling and user feedback
- Backup strategy: Consider keeping local copies of important documents
Security Considerations
- File scanning: Scan uploaded files for malware before processing
- Access control: Ensure only authorized users can upload attachments
- Data privacy: Be mindful of sensitive information in uploaded files
- Retention policies: Establish policies for how long attachments are kept
Integration Tips
- Bulk uploads: For multiple files, upload them sequentially to avoid overwhelming the server
- Mobile optimization: Optimize upload process for mobile devices with potentially slower connections
- Automated uploads: Consider automated workflows that upload documents from email or document management systems
- Thumbnail generation: For images, consider generating thumbnails for better UI experience
Authorizations
Use your API key (sk_live_xxx or sk_test_xxx)
Path Parameters
Expense CzUid
Body
multipart/form-data
PDF or image file (max 10MB)
Was this page helpful?