External File Upload API
Automatically upload data files to Deducta for processing via a secure, encrypted, API-key authenticated endpoint. Excel and Parquet are preferred.
Overview
This API endpoint allows external third-party clients to upload data files for processing. Excel (.xlsx), CSV (.csv), and Parquet (.parquet) files are accepted — Excel and Parquet are preferred. Files are uploaded over an encrypted (HTTPS) connection using a secure API key authentication mechanism and are processed asynchronously.
Authentication
All requests must include a valid API key in the request headers.
Base URL
https://api-data-ingestion.deducta.ai/m2m
Header Required:
X-API-Key: your-api-key-here
Contact your Deducta representative to obtain an API key for your organization.
Endpoint Details
- URL:
/api/file/upload - Method:
PUT - Content-Type:
multipart/form-data
Request Parameters
| Parameter | Type | Location | Required | Description |
|---|---|---|---|---|
file | File | Form Data | Yes | The data file to upload (Excel or Parquet preferred) |
businessUnitId | GUID | Query String | Yes | The business unit identifier |
Validation Requirements
Your file upload must meet the following requirements:
File Requirements
- File Format: Accepted formats are Excel (
.xlsx), CSV (.csv), and Parquet (.parquet). Excel and Parquet are preferred for the best performance and data fidelity. - File Size: 50 MB by default. Higher limits can be arranged per request — contact your Deducta representative.
- File Content: File must not be empty (size > 0 bytes)
Parameter Requirements
- businessUnitId: Must be a valid GUID. It cannot be empty or the all-zero GUID:
00000000-0000-0000-0000-000000000000
File Naming
The API accepts any file name, but we strongly recommend a consistent, descriptive name rather than a generic or random one. A good name identifies who the data is from and which period it covers, which makes uploads easy to trace, reconcile, and troubleshoot.
Use a structure such as:
{company}_{dataset}_{period}.xlsx
Examples:
acme_spend_2026-05.xlsx— monthly extract for May 2026acme_invoices_2026-W21.xlsx— weekly extract (ISO week 21)acme_transactions_2026-05-19.xlsx— daily extract
Tips:
- Use lowercase and separate parts with underscores or hyphens — avoid spaces.
- Use a sortable date format (
YYYY-MM,YYYY-MM-DD, orYYYY-Www) so files order chronologically. - On upload, Deducta automatically appends a processing timestamp to the stored file name (see the
fileNamefield in the success response), so you don't need to add one yourself.
Response Format
Success Response (200 OK)
{
"fileId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"fileName": "your_file_uploaded_20260119T143025.xlsx",
"businessUnitId": "7d9e4b2a-8c3f-4e1d-9a6b-5f8c2d1e3a4b"
}
Error Responses
400 Bad Request
{
"error": "Error message describing the validation failure"
}
Common validation errors:
"businessUnitId is required and cannot be empty""No file provided or file is empty""File size exceeds maximum allowed size of 50MB""Only .xlsx, .csv, and .parquet files are accepted"
401 Unauthorized
Authentication failed - invalid or missing API key.
500 Internal Server Error
{
"error": "Failed to process file",
"details": "Detailed error message"
}
Sample Scripts
cURL Example
#!/bin/bash
API_KEY="your-api-key-here"
BUSINESS_UNIT_ID="your-business-unit-id-here"
FILE_PATH="/path/to/your/file.xlsx"
API_URL="https://api-data-ingestion.deducta.ai/m2m/api/file/upload"
curl -X PUT "${API_URL}?businessUnitId=${BUSINESS_UNIT_ID}" \
-H "X-API-Key: ${API_KEY}" \
-F "file=@${FILE_PATH}"
Python Example
import mimetypes
import requests
from pathlib import Path
# Configuration
API_KEY = "your-api-key-here"
BUSINESS_UNIT_ID = "your-business-unit-id-here"
FILE_PATH = "/path/to/your/file.xlsx"
API_URL = "https://api-data-ingestion.deducta.ai/m2m/api/file/upload"
def upload_file(file_path, business_unit_id, api_key):
"""
Upload a data file to the Deducta Data Ingestion API.
Excel (.xlsx) and Parquet (.parquet) are preferred, but any standard
data file format is accepted.
Args:
file_path (str): Path to the file to upload
business_unit_id (str): GUID of the business unit
api_key (str): API key for authentication
Returns:
dict: Response containing fileId, fileName, and businessUnitId
Raises:
requests.exceptions.HTTPError: If the request fails
"""
# Validate file exists
file = Path(file_path)
if not file.exists():
raise FileNotFoundError(f"File not found: {file_path}")
# Check file size (50MB default limit)
file_size_mb = file.stat().st_size / (1024 * 1024)
if file_size_mb > 50:
raise ValueError(f"File size ({file_size_mb:.2f}MB) exceeds maximum allowed size of 50MB")
# Prepare the request
headers = {
"X-API-Key": api_key
}
params = {
"businessUnitId": business_unit_id
}
# Open and upload the file (content type is inferred from the file name)
content_type = mimetypes.guess_type(file.name)[0] or 'application/octet-stream'
with open(file_path, 'rb') as f:
files = {
'file': (file.name, f, content_type)
}
response = requests.put(
API_URL,
headers=headers,
params=params,
files=files
)
# Raise exception for HTTP errors
response.raise_for_status()
return response.json()
# Example usage
if __name__ == "__main__":
try:
result = upload_file(FILE_PATH, BUSINESS_UNIT_ID, API_KEY)
print("Upload successful!")
print(f"File ID: {result['fileId']}")
print(f"File Name: {result['fileName']}")
print(f"Business Unit ID: {result['businessUnitId']}")
except FileNotFoundError as e:
print(f"Error: {e}")
except ValueError as e:
print(f"Validation Error: {e}")
except requests.exceptions.HTTPError as e:
print(f"HTTP Error: {e}")
if e.response.status_code == 400:
print(f"Validation failed: {e.response.json()}")
elif e.response.status_code == 401:
print("Authentication failed - check your API key")
elif e.response.status_code == 500:
print(f"Server error: {e.response.json()}")
except Exception as e:
print(f"Unexpected error: {e}")
C# Example
using System;
using System.IO;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
using System.Text.Json;
public class FileUploadClient
{
private readonly HttpClient _httpClient;
private readonly string _apiKey;
private const long MaxFileSizeBytes = 50 * 1024 * 1024; // 50MB
public FileUploadClient(string baseUrl, string apiKey)
{
_httpClient = new HttpClient { BaseAddress = new Uri(baseUrl) };
_apiKey = apiKey;
}
public async Task<FileUploadResponse> UploadFileAsync(
string filePath,
Guid businessUnitId)
{
// Validate file exists
if (!File.Exists(filePath))
{
throw new FileNotFoundException("File not found", filePath);
}
// Validate file size
var fileInfo = new FileInfo(filePath);
if (fileInfo.Length > MaxFileSizeBytes)
{
throw new ArgumentException(
$"File size ({fileInfo.Length / 1024 / 1024}MB) exceeds maximum allowed size of 50MB");
}
// Validate businessUnitId
if (businessUnitId == Guid.Empty)
{
throw new ArgumentException("businessUnitId cannot be empty");
}
// Prepare the multipart form data
using var content = new MultipartFormDataContent();
using var fileStream = File.OpenRead(filePath);
using var streamContent = new StreamContent(fileStream);
streamContent.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
content.Add(streamContent, "file", Path.GetFileName(filePath));
// Prepare the request
var requestUri = $"/api/file/upload?businessUnitId={businessUnitId}";
var request = new HttpRequestMessage(HttpMethod.Put, requestUri)
{
Content = content
};
request.Headers.Add("X-API-Key", _apiKey);
// Send the request
var response = await _httpClient.SendAsync(request);
// Parse response
var responseContent = await response.Content.ReadAsStringAsync();
if (!response.IsSuccessStatusCode)
{
throw new HttpRequestException(
$"Upload failed with status {response.StatusCode}: {responseContent}");
}
return JsonSerializer.Deserialize<FileUploadResponse>(responseContent);
}
}
public class FileUploadResponse
{
public Guid FileId { get; set; }
public string FileName { get; set; }
public Guid BusinessUnitId { get; set; }
}
// Example usage
public class Program
{
public static async Task Main(string[] args)
{
var apiKey = "your-api-key-here";
var businessUnitId = Guid.Parse("your-business-unit-id-here");
var filePath = @"C:\path\to\your\file.xlsx";
var apiUrl = "https://api-data-ingestion.deducta.ai/m2m";
var client = new FileUploadClient(apiUrl, apiKey);
try
{
var result = await client.UploadFileAsync(filePath, businessUnitId);
Console.WriteLine("Upload successful!");
Console.WriteLine($"File ID: {result.FileId}");
Console.WriteLine($"File Name: {result.FileName}");
Console.WriteLine($"Business Unit ID: {result.BusinessUnitId}");
}
catch (FileNotFoundException ex)
{
Console.WriteLine($"Error: {ex.Message}");
}
catch (ArgumentException ex)
{
Console.WriteLine($"Validation Error: {ex.Message}");
}
catch (HttpRequestException ex)
{
Console.WriteLine($"HTTP Error: {ex.Message}");
}
catch (Exception ex)
{
Console.WriteLine($"Unexpected error: {ex.Message}");
}
}
}
Best Practices
- Error Handling: Always implement proper error handling to catch validation errors, authentication failures, and server errors.
- File Validation: Validate files on the client side before uploading to avoid unnecessary network traffic:
- Use an accepted format: Excel (
.xlsx), CSV (.csv), or Parquet (.parquet) — Excel and Parquet are preferred - Verify file size is within your agreed limit (50 MB by default)
- Ensure file is not empty
- Use an accepted format: Excel (
- Retry Logic: Implement exponential backoff retry logic for transient failures (5xx errors).
- Logging: Log all upload attempts, including file names, sizes, and response data for troubleshooting.
- Security:
- Never hardcode API keys in source code
- Use environment variables or secure configuration management
- Timeouts: Set appropriate timeouts for large file uploads (recommended: 5-10 minutes for 50MB files).
Support
For API key requests, technical issues, or questions, please contact your Deducta representative.
