Import via URL (Pre-signed Flow)

The Pre-signed URL workflow allows you to upload large files directly to our secure cloud storage. This ensures optimal performance and prevents timeouts associated with large request bodies.

Why use this method?

  • Large Scale: Ideal for processing massive data loads that would otherwise exceed HTTP request size limits.
  • Performance: Offloads the heavy lifting of file transfers to optimized storage servers, preventing timeouts and reducing the load on your application server.
  • Data Integrity: Mandatory MD5 checksum validation ensures that not a single byte is corrupted or altered during the transfer process.
  • Asynchronous Processing: Designed for background execution, allowing your system to submit data and check results later without waiting for a long-running request to finish.

Recommendation: Use this method for high-volume files or recurring monthly data synchronizations. For real-time updates of single records or small changes, prefer the Import via JSON.


Technical Implementation

This flow consists of three distinct steps: Checksum Generation, Authorization, and Binary Transfer.


Step 1: Generating the MD5 Checksum

Before calling the API, you must calculate an MD5 checksum of your file.
This checksum is used to verify that the file content was not altered during the upload process.

⚠️ The checksum must be generated before requesting the upload URL.

Implementation Examples (MD5):

Node.js

const crypto = require('crypto');
const fs = require('fs');

const fileBuffer = fs.readFileSync('payroll_january.csv');
const checksum = crypto.createHash('md5').update(fileBuffer).digest('hex');

console.log(`MD5 (hex): ${checksum}`);

C#

using System.Security.Cryptography;

public static string GetMD5Checksum(string filePath)
{
    using var md5 = MD5.Create();
    using var stream = File.OpenRead(filePath);
    var hash = md5.ComputeHash(stream);

    return BitConverter.ToString(hash)
        .Replace("-", "")
        .ToLowerInvariant();
}

Python

import hashlib

def get_md5_checksum(filename):
    hash_md5 = hashlib.md5()
    with open(filename, "rb") as f:
        for chunk in iter(lambda: f.read(4096), b""):
            hash_md5.update(chunk)
    return hash_md5.hexdigest()

Step 2: Requesting the Upload URL

Send the file metadata along with the MD5 checksum generated in the previous step to the /submissions endpoint.

Endpoint: POST /api/v1/submissions

Authentication:

This API is secured by Bearer Token authentication. To access protected resources, the client must provide a valid access token in the HTTP Authorization header of every request (Bearer ).

Header Value Description
Authorization Bearer {your token} Authentication bearer token

⚠️ Ensure your token is valid and hasn't expired. Requests without a valid Bearer token will return a 401 Unauthorized error.

Request Body (Array of Objects):

Parameter Type Required Description
FileName string Yes The original name of the file (e.g., census_2024.csv).
FileType integer Yes The document category: 1 for Receipt, 2 for Census.
Checksum string Yes The MD5 checksum of the file in hexadecimal format.
ReferenceDate datetime Yes The payroll period date in ISO 8601 format (e.g., 2024-05-01T00:00:00Z).

Json Example:

[
  {
    "FileName": "census_2024.csv",
    "FileType": 2,
    "Checksum": "e10adc3949ba59abbe56e057f20f883e",
    "ReferenceDate": "2024-05-01T00:00:00Z"
  }
]

Implementation Examples (Submission POST):

Node.js

const axios = require('axios');

async function requestUploadUrl() {
  const token = 'YOUR_BEARER_TOKEN';
  const url = 'https://sandbox.bmgmoney.com/submissions';

  const payload = [{
    FileName: "census_2024.csv",
    Checksum: "e10adc3949ba59abbe56e057f20f883e", // Example MD5
    FileType: 2, // 2 -> Census
    ReferenceDate: "2024-05-01T00:00:00Z"
  }];

  var response = await axios.post(url, payload, {
      headers: {
        'Authorization': `Bearer ${token}`,
        'Content-Type': 'application/json'
      }
    });
}

C#

using System.Net.Http.Headers;
using System.Text;  
using System.Text.Json;

var client = new HttpClient();
var token = "YOUR_BEARER_TOKEN";

var payload = new[]
{
    new
    {
        FileName = "census_2024.csv",
        Checksum = "e10adc3949ba59abbe56e057f20f883e",
        FileType = 2,
        ReferenceDate = "2024-05-01T00:00:00Z"
    }
};

var request = new HttpRequestMessage(HttpMethod.Post, "https://sandbox.bmgmoney.com/submissions");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
request.Content = new StringContent(JsonSerializer.Serialize(payload), Encoding.UTF8, "application/json");

var response = await client.SendAsync(request);

Python

import requests

token = "YOUR_BEARER_TOKEN"
    url = "https://sandbox.bmgmoney.com/submissions"
    
    headers = {
        "Authorization": f"Bearer {token}",
        "Content-Type": "application/json"
    }
    
    payload = [{
        "FileName": "census_2024.csv",
        "Checksum": "e10adc3949ba59abbe56e057f20f883e",
        "FileType": 2,
        "ReferenceDate": "2024-05-01T00:00:00Z"
    }]

    response = requests.post(url, json=payload, headers=headers)

Common Responses:

  • 201 Created: The batch was accepted and all submission URLs were generated.
  • 400 Bad Request: Validation error in the submitted data (e.g., invalid date format).
  • 401 Unauthorized:: Bearer token is missing or invalid.
  • 403 Forbidden: The user don't have permission to proceed.

Response Body (Array of Objects):

Parameter Type Description
FileName string Matches the name from your request.
FileType string Matches the type from your request.
Url string A unique, time-sensitive URL to download the original file.
Expiration datetime The ISO 8601 timestamp when the Url will expire.

Json Example:

[
  {
    "FileName": "census_january_2026.csv",
    "FileType": "Census",
    "UploadUrl": "https://storage.googleapis.com/upload-token-path...",
    "Expiration": "2026-01-22T11:25:00Z"
  }
]

Error Handling:

The API returns standard HTTP status codes to indicate the success or failure of the request. In case of an error, the response body will contain useful details.

Parameter Type Description
Title string A short, human-readable summary of the error type.
StatusCode int The HTTP status code associated with the error (e.g., 400, 401, 500).
Timestamp DateTime The exact ISO 8601 date and time when the error occurred.
Errors array[string] A detailed list of specific error messages explaining what went wrong.

Json Example:

{
  "title": null,
  "statusCode": 1,
  "timestamp": "",
  "errors": [
      ""
  ]
}

Step 3: Executing the Binary Upload (PUT)

Once you receive the uploadUrl, perform a raw binary upload.

⚠️ Do NOT include authentication headers such as Authorization.
Including them will invalidate the request signature.

Technical Requirements:

Requirement Value
HTTP Method PUT
URL uploadUrl returned in Step 2
Headers Content-Type: text/csv
Body Raw binary content of the file

Upload Examples:

Node.js

const axios = require('axios');
const fs = require('fs');

const uploadFile = async (uploadUrl, filePath) => {
  try {
    const fileData = fs.readFileSync(filePath);

    await axios.put(uploadUrl, fileData, {
      headers: {
        'Content-Type': 'text/csv'
      }
    });

    console.log("Upload successful!");
  } catch (error) {
    console.error("Upload failed:", error.message);
  }
};

C#

var fileBytes = File.ReadAllBytes("payroll.csv");

using var content = new ByteArrayContent(fileBytes);
content.Headers.ContentType =
    new System.Net.Http.Headers.MediaTypeHeaderValue("text/csv");

using var client = new HttpClient();
var response = await client.PutAsync(uploadUrl, content);

if (response.IsSuccessStatusCode)
{
    Console.WriteLine("Upload successful");
}

Python

import requests

def upload_file(upload_url, file_path):
    try:
        with open(file_path, 'rb') as f:
            response = requests.put(
                upload_url, 
                data=f, 
                headers={'Content-Type': 'text/csv'}
            )
            
        response.raise_for_status()
        print("Upload successful!")
        
    except requests.exceptions.RequestException as e:
        print(f"Upload failed: {e}")

upload_file("PASTE_UPLOAD_URL_HERE", "payroll_january.csv")

Troubleshooting:

  • 403 Forbidden
    Usually caused by expired URLs or extra headers (e.g., Authorization).
  • 400 Bad Request
    Typically indicates a Content-Type mismatch.

Step 4: Verification & Tracking

A successful PUT request (200 OK) indicates that the file has been stored successfully.

Processing and validation occur asynchronously.
To track the submission status, you can check the processing status and review any validation errors by querying the Submission History.

Endpoint: GET /api/v1/submissions

Filtering your results You can use query parameters to locate specific submissions:

  • Status:
    • 1 → Pending
    • 2 → Processed
    • 3 → Failed
  • StartDate / EndDate: Narrow down history to a specific timeframe.

Authentication:

This API is secured by Bearer Token authentication. To access protected resources, the client must provide a valid access token in the HTTP Authorization header of every request (Bearer ).

Header Value Description
Authorization Bearer {your token} Authentication bearer token

⚠️ Ensure your token is valid and hasn't expired. Requests without a valid Bearer token will return a 401 Unauthorized error.

Implementation Examples (Check Status):

Node.js

const axios = require('axios');

const getSubmissionHistory = async () => {
  const token = 'YOUR_TOKEN';
  // You can filter by Status, StartDate, or EndDate via query parameters
  const url = 'https://sandbox.bmgmoney.com/api/v1/submissions?Status=1';

  try {
    const response = await axios.get(url, {
      headers: { 
        'Authorization': `Bearer ${token}`,
        'Accept': 'application/json'
      }
    });
    
    console.log('History Records:', response.data);
  } catch (error) {
    console.error('Error fetching history:', error.response?.status || error.message);
  }
};

getSubmissionHistory();

C#

using System.Net.Http.Headers;

using var client = new HttpClient();
var token = "YOUR_TOKEN";

// Adding Bearer Token authentication
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);

// Fetching history with optional filters
var url = "https://sandbox.bmgmoney.com/api/v1/submissions?StartDate=2024-01-01";
var response = await client.GetAsync(url);

if (response.IsSuccessStatusCode)
{
    var json = await response.Content.ReadAsStringAsync();
    // Parse JSON to check record.Status and record.ErrorDescription
    Console.WriteLine(json);
}

Pyhton

import requests

def get_submission_history():
    token = "YOUR_TOKEN"
    # You can filter by Status, StartDate, or EndDate via query parameters
    url = "https://sandbox.bmgmoney.com/api/v1/submissions"
    params = {
        "Status": 1,
        "StartDate": "2024-01-01"
    }
    
    headers = {
        "Authorization": f"Bearer {token}",
        "Accept": "application/json"
    }

    try:
        response = requests.get(url, headers=headers, params=params)
        
        # Raises an exception for 4XX/5XX errors
        response.raise_for_status()
        
        history = response.json()
        for record in history:
            print(f"File: {record.get('FileName')} - Status: {record.get('Status')}")
            
    except requests.exceptions.RequestException as e:
        print(f"Error fetching history: {e}")

if __name__ == "__main__":
    get_submission_history()

Common Responses:

  • 201 Created: The batch was accepted and all submission URLs were generated.
  • 400 Bad Request: Validation error in the submitted data (e.g., invalid date format).
  • 401 Unauthorized:: Bearer token is missing or invalid.

Response Body (Array of Object):

Parameter Type Description
Status string Current processing state: Processing, Completed, or Failed.
ErrorDescription string? Detailed explanation of why the file failed validation (null if successful).
Url string A unique, time-sensitive URL to download the original file.
Expiration datetime The ISO 8601 timestamp when the Url will expire.
Type string The document type (e.g., Census, Receipt).
FileName string The original name of the submitted file.
Checksum string The MD5 checksum used to verify file integrity.
ReferenceDate datetime The payroll reference period associated with this file.
CreatedAt datetime Timestamp of when the submission record was first created.
UpdatedAt datetime? Timestamp of the last status update.

Json Example:

[
  {
    "Status": "Completed",
    "ErrorDescription": null,
    "Url": "https://storage.googleapis.com/download-token-path",
    "Expiration": "2026-01-22T12:00:00Z",
    "Type": "Census",
    "FileName": "census_january_2026.csv",
    "Checksum": "79054025255fb1a26e4bc422aef54eb4",
    "ReferenceDate": "2026-01-01T00:00:00Z",
    "CreatedAt": "2026-01-22T10:05:00Z",
    "UpdatedAt": "2026-01-22T10:08:00Z"
  }
]

Error Handling:

The API returns standard HTTP status codes to indicate the success or failure of the request. In case of an error, the response body will contain useful details.

Parameter Type Description
Title string A short, human-readable summary of the error type.
StatusCode int The HTTP status code associated with the error (e.g., 400, 401, 500).
Timestamp DateTime The exact ISO 8601 date and time when the error occurred.
Errors array[string] A detailed list of specific error messages explaining what went wrong.

Json Example:

{
  "title": null,
  "statusCode": 1,
  "timestamp": "",
  "errors": [
      ""
  ]
}