Docs /External File Upload API

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

ParameterTypeLocationRequiredDescription
fileFileForm DataYesThe data file to upload (Excel or Parquet preferred)
businessUnitIdGUIDQuery StringYesThe 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 2026
  • acme_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, or YYYY-Www) so files order chronologically.
  • On upload, Deducta automatically appends a processing timestamp to the stored file name (see the fileName field 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

  1. Error Handling: Always implement proper error handling to catch validation errors, authentication failures, and server errors.
  2. 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
  3. Retry Logic: Implement exponential backoff retry logic for transient failures (5xx errors).
  4. Logging: Log all upload attempts, including file names, sizes, and response data for troubleshooting.
  5. Security:
    • Never hardcode API keys in source code
    • Use environment variables or secure configuration management
  6. 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.

We use cookies to analyse site traffic. See our cookie policy for details.