n8n Workflow: Send File to Valora Storage
This guide explains how to configure an n8n workflow to send a file to the Valora storage API endpoint. This is useful when you've already generated a file in n8n (e.g., XML, PDF, CSV) and need to store it in Valora's document system.
Prerequisites
- n8n workflow with a file already created/saved
- Valora Employee API Token
- Access to Valora API (production or sandbox)
- Your Valora API base URL (e.g.,
https://valora.spotahome.com)
Quick Reference: API Endpoints
| Environment | URL |
|---|---|
| Production | https://valora.spotahome.com/api/v1/storage/file |
| Sandbox | https://valora-testing.laravel.cloud/api/sandbox/v1/storage/file |
Step by Step: Using n8n UI
This section provides detailed instructions for configuring the upload using n8n's visual interface. No coding required.
Step 1: Add HTTP Request Node
- In your n8n workflow, click the + button to add a new node
- Search for "HTTP Request" and select it
- Connect it after the node that creates/saves your file
- Rename the node to something descriptive like "Upload to Valora Storage"
Step 2: Configure Basic Settings
- Click on the HTTP Request node to open its settings
- In the Method dropdown, select POST
- In the URL field, enter:
- Production:
https://valora.spotahome.com/api/v1/storage/file - Sandbox:
https://valora-testing.laravel.cloud/api/sandbox/v1/storage/file
- Production:
Step 3: Set Up Authentication
- Scroll down to the Authentication section
- Click the dropdown and select "Header Auth"
- In the Name field, enter:
Authorization - In the Value field, enter:
Bearer YOUR_API_TOKEN- Replace
YOUR_API_TOKENwith your actual Valora Employee API token
- Replace
Tip: For better security, consider using n8n credentials instead of hardcoding the token. Go to Credentials → Create New → Header Auth and save your token there.
Step 4: Add Request Headers
- Expand the Options section (click "Show more options" if needed)
- Find the Headers section
- Click "Add Header"
- Set:
- Name:
Accept - Value:
application/json
- Name:
Important: Do NOT add a
Content-Typeheader. n8n will automatically set this tomultipart/form-datawhen you configure the body.
Step 5: Configure Request Body
- Scroll to the Body section
- In the Body Content Type dropdown, select "Form-Data" or "Multipart-Form-Data"
- You'll see a table to add parameters. Add the following fields:
Required Fields
| Name | Type | Value |
|---|---|---|
file |
File | Click the expression icon (fx) and enter: {{ $binary.data }} |
file_name |
String | Enter your desired filename, e.g., dac7-report-2025.xml |
store_in |
String | Enter one of: documents, invoicing, reporting, or reports |
Optional Fields
| Name | Type | Value |
|---|---|---|
category |
String | e.g., dac7-reporting, invoice, report |
description |
String | Description of the document |
visibility |
String | public or private |
Step 6: Configure File Field (Important!)
The file field needs special attention:
-
Click on the Value field for the
fileparameter -
Click the expression icon (fx) to enable expressions
-
Enter one of these based on your setup:
If your previous node outputs binary data:
{{ $binary.data }}If you have a file path from previous node:
{{ $json.filePath }}If you need to read a file from disk:
- First, add a "Read Binary File" node before the HTTP Request
- Then use:
{{ $binary.data }}
Step 7: Test the Configuration
- Click "Execute Node" to test
- Check the output:
- Success: You'll see a response with
"success": trueand adocument_id - Error: Check the error message and refer to the troubleshooting section
- Success: You'll see a response with
Step 8: Add Error Handling (Recommended)
- Add an IF node after the HTTP Request node
- Configure the condition:
- Value 1:
{{ $json.success }} - Operation:
Equal - Value 2:
true
- Value 1:
- Connect success path to continue your workflow
- Connect error path to a notification node (e.g., email, Slack)
Step by Step: For Advanced Developers
This section provides code-based examples and advanced configurations for developers comfortable with n8n expressions and JavaScript.
Method 1: Using Code Node to Prepare Request
If you need to dynamically prepare the file and metadata before uploading:
// Code Node: Prepare Upload Data
const fileName = `dac7-report-${$now.toFormat('yyyy-MM-dd')}.xml`;
const storeIn = $json.reportType === 'dac7' ? 'reporting' : 'documents';
return [{
json: {
fileName: fileName,
storeIn: storeIn,
category: 'dac7-reporting',
description: `DAC7 report generated on ${$now.toFormat('yyyy-MM-dd HH:mm:ss')}`,
visibility: 'private'
},
binary: {
data: {
data: Buffer.from($json.xmlContent, 'utf8'),
mimeType: 'application/xml',
fileName: fileName
}
}
}];
Then in HTTP Request node, use expressions:
file:{{ $binary.data }}file_name:{{ $json.fileName }}store_in:{{ $json.storeIn }}category:{{ $json.category }}
Method 2: Direct HTTP Request with Expressions
Configure the HTTP Request node with dynamic expressions:
URL:
{{ ($env.ENVIRONMENT === 'sandbox' ? 'https://valora-testing.laravel.cloud/api/sandbox' : 'https://valora.spotahome.com/api') + '/v1/storage/file' }}
Body Parameters:
| Name | Expression |
|---|---|
file |
{{ $binary.data }} |
file_name |
`{{ $json.fileName |
store_in |
`{{ $json.storeIn |
category |
`{{ $json.category |
description |
`{{ $json.description |
visibility |
`{{ $json.visibility |
Method 3: Error Handling with Code Node
Add a Code node after HTTP Request to handle errors gracefully:
// Code Node: Handle Upload Response
const response = $input.item.json;
if (response.success) {
return [{
json: {
success: true,
documentId: response.data.document_id,
fileName: response.data.file_name,
sizeBytes: response.data.size_bytes,
storedIn: response.data.stored_in,
message: 'File uploaded successfully'
}
}];
} else {
// Log error and throw for error workflow path
console.error('Upload failed:', response);
throw new Error(response.message || 'Upload failed');
}
Method 4: Complete Workflow with Validation
// Code Node 1: Validate and Prepare
const fileSize = $binary.data.data.length;
const maxSize = 10 * 1024 * 1024; // 10 MB
if (fileSize > maxSize) {
throw new Error(`File size (${fileSize} bytes) exceeds maximum (${maxSize} bytes)`);
}
const fileName = $json.fileName || `upload-${$now.toFormat('yyyy-MM-dd-HHmmss')}.${$json.fileExtension || 'xml'}`;
const storeIn = ['documents', 'invoicing', 'reporting', 'reports'].includes($json.storeIn)
? $json.storeIn
: 'documents';
return [{
json: {
fileName,
storeIn,
category: $json.category || 'api-upload',
description: $json.description || null,
visibility: ['public', 'private'].includes($json.visibility) ? $json.visibility : 'private',
fileSize
},
binary: {
data: $binary.data
}
}];
Method 5: Batch Upload Multiple Files
If you need to upload multiple files:
// Code Node: Process Multiple Files
const files = $input.all();
return files.map(item => ({
json: {
fileName: item.json.fileName,
storeIn: item.json.storeIn || 'documents',
category: item.json.category || 'batch-upload',
index: item.json.index
},
binary: {
data: item.binary.data
}
}));
Then use Split In Batches node before HTTP Request to process one at a time, or use HTTP Request with "Process all items" enabled.
Storage Destinations Reference
store_in Value |
Description | Default Visibility | Use Case |
|---|---|---|---|
documents |
General document storage | private |
Any document type |
invoicing |
Invoice documents | public |
Invoices, billing documents |
reporting |
Reporting documents (DAC7, etc.) | private |
XML reports, compliance documents |
reports |
General reports (CSV, ZIP, etc.) | private |
Data exports, analytics |
Response Format
Success Response (201 Created)
{
"success": true,
"message": "File uploaded successfully.",
"data": {
"document_id": "39f6b0c8-8997-46f1-9c9d-e9f765f89447",
"file_name": "dac7-report-2025.xml",
"size_bytes": 120431,
"stored_in": "reporting"
}
}
Error Responses
422 Validation Error:
{
"success": false,
"message": "Validation failed.",
"errors": {
"store_in": ["The selected store in is invalid."]
}
}
500 Server Error:
{
"success": false,
"message": "An unexpected error occurred while uploading the file.",
"error": "Detailed error message (only in debug mode)"
}
401 Unauthorized:
{
"message": "Unauthenticated."
}
Common Issues & Solutions
Issue: "No file was uploaded"
Symptoms: API returns error about missing file
Solutions:
- Verify the file field uses
{{ $binary.data }}expression - Check that the previous node outputs binary data
- If using file path, add a Read Binary File node first
- Ensure the file parameter type is set to File (not String)
Issue: "Validation failed"
Symptoms: 422 error with validation details
Solutions:
- Check that
file_nameis provided and is a string - Verify
store_inis exactly one of:documents,invoicing,reporting,reports - Ensure file size is under 10 MB (10240 KB)
- Check that all required fields are present
Issue: "Unauthenticated"
Symptoms: 401 error
Solutions:
- Verify your API token is valid and not expired
- Check that the token has employee permissions (not customer token)
- Ensure Authorization header format is:
Bearer YOUR_TOKEN(with space after Bearer) - Test the token with a simple API call first
Issue: File Not Found or Binary Data Missing
Symptoms: Error about missing binary data
Solutions:
- If previous node outputs JSON with file path, add Read Binary File node
- Verify the binary data exists: check
{{ $binary }}in previous node output - Ensure file path is absolute or relative to n8n's working directory
- For Code nodes, make sure you're returning binary data correctly
Issue: Wrong Content-Type
Symptoms: API doesn't recognize the file
Solutions:
- Don't manually set
Content-Typeheader - let n8n handle it - Use Form-Data or Multipart-Form-Data body type
- Ensure file field is set as File type, not String
n8n Expression Examples
Dynamic File Name with Timestamp
{{ 'dac7-report-' + $now.toFormat('yyyy-MM-dd') + '.xml' }}
Conditional Storage Destination
{{ $json.reportType === 'dac7' ? 'reporting' : 'documents' }}
Extract Document ID from Response
{{ $json.data.document_id }}
File Size Check Before Upload
{{ $binary.data.data.length < 10485760 ? 'OK' : 'TOO_LARGE' }}
Generate Filename from Multiple Sources
{{ ($json.prefix || 'document') + '-' + ($json.id || $now.toFormat('yyyyMMdd')) + '.' + ($json.extension || 'xml') }}
Best Practices
-
Error Handling: Always add error handling nodes after the HTTP Request
- Use IF node to check
{{ $json.success }} - Log errors for debugging
- Send notifications on failure
- Use IF node to check
-
Logging: Log the
document_idfor future reference- Store in a database
- Include in notifications
- Save to workflow execution logs
-
File Size Validation: Check file size before uploading
- Max size: 10 MB (10240 KB)
- Add validation in Code node if needed
-
Naming Convention: Use descriptive, timestamped filenames
- Include date/time:
report-2025-01-15-143022.xml - Include source:
dac7-2025-Q1.xml - Avoid special characters
- Include date/time:
-
Metadata: Include
categoryanddescriptionfor better organization- Helps with document retrieval
- Improves audit trail
- Aids in compliance
-
Testing: Test with sandbox environment first
- Use sandbox endpoint for development
- Verify file uploads correctly
- Check document appears in system
-
Security: Use n8n credentials for API tokens
- Don't hardcode tokens in workflows
- Use credential management
- Rotate tokens regularly
-
Retry Logic: Consider adding retry for transient failures
- Use n8n's built-in retry on error
- Or implement custom retry logic in Code node
Complete Example Workflow
Scenario: Generate DAC7 XML and Upload
Workflow Structure:
- Code Node - Generate XML content
- Code Node - Convert to binary
- HTTP Request - Upload to Valora
- IF Node - Check success
- Code Node - Log result (success path)
- Code Node - Handle error (error path)
Node 1: Generate XML
const xmlContent = `<?xml version="1.0"?>
<report>
<docRefId>ES2025-001</docRefId>
<generatedAt>${$now.toISO()}</generatedAt>
<!-- Your XML content -->
</report>`;
return [{
json: {
xmlContent,
fileName: `dac7-${$now.toFormat('yyyy-MM-dd')}.xml`
}
}];
Node 2: Convert to Binary
return [{
json: $input.item.json,
binary: {
data: {
data: Buffer.from($input.item.json.xmlContent, 'utf8'),
mimeType: 'application/xml',
fileName: $input.item.json.fileName
}
}
}];
Node 3: HTTP Request (configure as shown in UI steps)
Node 4: IF Node
- Condition:
{{ $json.success === true }}
Node 5: Log Success
return [{
json: {
message: 'Upload successful',
documentId: $input.item.json.data.document_id,
timestamp: $now.toISO()
}
}];
Testing Checklist
Before deploying your workflow to production:
- [ ] Test with a small file (< 1 MB)
- [ ] Test with maximum size file (10 MB)
- [ ] Test with different file types (XML, PDF, CSV)
- [ ] Test all storage destinations (
documents,invoicing,reporting,reports) - [ ] Test error scenarios (invalid token, missing fields, oversized file)
- [ ] Verify document appears in Valora system
- [ ] Check document metadata is correct
- [ ] Test in sandbox environment first
- [ ] Verify error handling works correctly
- [ ] Test with actual production data (if safe)
© Valora n8n Integration Guide