ACH Validation API
Overview
The FISTWorks ACH Validation API lets you validate ACH files programmatically from your own applications, scripts, or automated workflows. Submit an ACH file and receive detailed NACHA compliance results — the same validation engine that powers the web-based ACH Validator.
The API is available on Plus and Max plans.
Getting Started
Request API Credentials
- Sign in to FISTWorks and navigate to your Organization page.
- Contact us through the Contact page to request API access for your organization.
- We will provision OAuth2 client credentials (Client ID and Client Secret) for your application.
Authentication
The API uses OAuth2 client credentials for authentication. Your application requests an access token using your Client ID and Client Secret, then includes that token in API requests.
Request an access token:
POST https://login.fistworks.com/{tenant-id}/oauth2/v2.0/token
Content-Type: application/x-www-form-urlencoded
client_id={YOUR_CLIENT_ID}
&client_secret={YOUR_CLIENT_SECRET}
&scope={API_SCOPE}/.default
&grant_type=client_credentials
The token endpoint and scope values are provided when your API credentials are provisioned.
Use the token in API requests:
Include the access token in the Authorization header:
Authorization: Bearer {ACCESS_TOKEN}
Access tokens expire after one hour. Request a new token when the current one expires.
Endpoints
Validate ACH File (JSON)
Submit ACH file content as a JSON string for validation.
POST /app/api/v1/ach/validate
Content-Type: application/json
Authorization: Bearer {ACCESS_TOKEN}
Request body:
{
"fileContent": "101 091000019 1234567891...",
"options": {
"strictMode": false,
"validateRoutingNumbers": true
}
}
Validate ACH File (Upload)
Upload an ACH file directly as multipart form data.
POST /app/api/v1/ach/validate/upload
Content-Type: multipart/form-data
Authorization: Bearer {ACCESS_TOKEN}
Form fields:
| Field | Type | Required | Description |
|---|---|---|---|
file |
File | Yes | The ACH file to validate (.txt or .ach, max 1 MB) |
strictMode |
Boolean | No | Convert warnings to errors (default: false) |
Validation Options
All options are optional. Defaults provide standard NACHA validation.
| Option | Type | Default | Description |
|---|---|---|---|
strictMode |
bool | false | Convert warnings to errors for stricter compliance |
validateRoutingNumbers |
bool | true | Verify ABA routing number check digits |
validateDateRanges |
bool | true | Check that file dates are reasonable |
validateSECCodeRules |
bool | true | Validate SEC code specific requirements |
validateControlTotals |
bool | true | Verify counts, hashes, and amounts |
validateRequiredFields |
bool | true | Ensure required fields are not empty |
validateHeaderControlConsistency |
bool | true | Header and control record consistency |
validateSameDayACHLimits |
bool | true | Check same-day ACH transaction limits |
sameDayACHLimit |
decimal | 1000000 | Same-day ACH limit threshold |
largeAmountThreshold |
decimal | 100000 | Threshold for large amount warnings |
validateAddendaRecords |
bool | false | Validate addenda record content |
validateBalancedFile |
bool | false | Verify total debits equal total credits |
detectDuplicates |
bool | true | Detect duplicate transactions by trace number, amount, and routing |
Response Format
A successful validation request returns a JSON response with the validation results:
{
"isValid": true,
"isPerfect": false,
"errorCount": 0,
"warningCount": 2,
"infoCount": 1,
"summary": "File is valid with 2 warnings",
"statistics": {
"batchCount": 1,
"entryCount": 5,
"totalDebits": 1500.00,
"totalCredits": 1500.00,
"immediateDestination": "091000019",
"immediateOrigin": "123456789",
"fileCreationDate": "250126"
},
"messages": [
{
"severity": "Warning",
"category": "BusinessLogic",
"code": "LARGE-AMOUNT",
"message": "Transaction amount exceeds threshold",
"location": "Batch 0, Entry 2",
"fieldName": "Amount",
"actualValue": "150000",
"expectedValue": "< 100000"
}
]
}
Response Fields
| Field | Type | Description |
|---|---|---|
isValid |
bool | true if the file has no errors (warnings are allowed) |
isPerfect |
bool | true if the file has no errors, warnings, or info messages |
errorCount |
int | Number of error-level messages |
warningCount |
int | Number of warning-level messages |
infoCount |
int | Number of informational messages |
summary |
string | Human-readable summary of the validation result |
statistics |
object | File-level statistics (batch count, entry count, totals) |
messages |
array | Detailed validation messages with severity, location, and context |
Message Severity Levels
| Severity | Meaning |
|---|---|
| Error | NACHA rule violation — the file will be rejected by the ACH network |
| Warning | Potential issue that may cause problems but is not a strict rule violation |
| Info | Informational observation about the file structure or contents |
HTTP Status Codes
| Code | Description |
|---|---|
| 200 | Validation completed — check isValid for the result |
| 400 | Invalid request — missing file content or unparseable file |
| 401 | Missing or invalid access token |
| 403 | Access token does not include the required API role |
| 413 | File exceeds the 1 MB size limit |
| 429 | Rate limit exceeded — wait and retry |
| 500 | Internal server error |
Rate Limits
| Plan | Daily API Requests |
|---|---|
| Plus | 300 requests per day |
| Max | Unlimited |
When you exceed your daily limit, the API returns HTTP 429. Limits reset at midnight UTC.
Code Examples
cURL
# Get access token
TOKEN=$(curl -s -X POST "{TOKEN_ENDPOINT}" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "client_id={CLIENT_ID}" \
-d "client_secret={CLIENT_SECRET}" \
-d "scope={API_SCOPE}/.default" \
-d "grant_type=client_credentials" \
| jq -r '.access_token')
# Validate a file
curl -X POST "https://app.fistworks.com/app/api/v1/ach/validate/upload" \
-H "Authorization: Bearer $TOKEN" \
-F "file=@/path/to/ach-file.txt" \
-F "strictMode=true"
Python
import requests
# Get access token
token_response = requests.post(
"{TOKEN_ENDPOINT}",
data={
"client_id": "{CLIENT_ID}",
"client_secret": "{CLIENT_SECRET}",
"scope": "{API_SCOPE}/.default",
"grant_type": "client_credentials"
}
)
access_token = token_response.json()["access_token"]
# Validate a file
with open("ach-file.txt", "r") as f:
file_content = f.read()
response = requests.post(
"https://app.fistworks.com/app/api/v1/ach/validate",
headers={"Authorization": f"Bearer {access_token}"},
json={
"fileContent": file_content,
"options": {"strictMode": True}
}
)
result = response.json()
print(f"Valid: {result['isValid']}, Errors: {result['errorCount']}")
C# (.NET)
using System.Net.Http.Headers;
// Get access token
var tokenClient = new HttpClient();
var tokenRequest = new FormUrlEncodedContent(new Dictionary<string, string>
{
["client_id"] = "{CLIENT_ID}",
["client_secret"] = "{CLIENT_SECRET}",
["scope"] = "{API_SCOPE}/.default",
["grant_type"] = "client_credentials"
});
var tokenResponse = await tokenClient.PostAsync("{TOKEN_ENDPOINT}", tokenRequest);
var tokenJson = await tokenResponse.Content.ReadFromJsonAsync<JsonElement>();
var accessToken = tokenJson.GetProperty("access_token").GetString();
// Validate a file
var apiClient = new HttpClient();
apiClient.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", accessToken);
var response = await apiClient.PostAsJsonAsync(
"https://app.fistworks.com/app/api/v1/ach/validate",
new
{
fileContent = File.ReadAllText("ach-file.txt"),
options = new { strictMode = true }
});
var result = await response.Content.ReadFromJsonAsync<JsonElement>();
Console.WriteLine($"Valid: {result.GetProperty("isValid")}");
JavaScript / Node.js
// Get access token
const tokenResponse = await fetch('{TOKEN_ENDPOINT}', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
client_id: '{CLIENT_ID}',
client_secret: '{CLIENT_SECRET}',
scope: '{API_SCOPE}/.default',
grant_type: 'client_credentials'
})
});
const { access_token } = await tokenResponse.json();
// Validate a file
const response = await fetch(
'https://app.fistworks.com/app/api/v1/ach/validate',
{
method: 'POST',
headers: {
'Authorization': `Bearer ${access_token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
fileContent: fileContent,
options: { strictMode: true }
})
}
);
const result = await response.json();
console.log('Valid:', result.isValid);
Tips
- Use the upload endpoint for simplicity when validating files from disk. Use the JSON endpoint when your application already has the file content in memory.
- Enable
strictModefor production pre-submission checks to catch issues that may not be errors but could cause bank rejections. - Enable
detectDuplicatesto catch accidentally repeated entries before they reach the bank. - Enable
validateBalancedFilewhen your ACH files are expected to have matching debits and credits (e.g., payroll with offsetting entries). - Cache your access token for its full lifetime (one hour) to avoid unnecessary token requests.
- Contact us through the Contact page for API access provisioning or technical support.