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 aContent-Typemismatch.
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→ Pending2→ Processed3→ 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": [
""
]
}