Every error the Invincible Pay REST API can return follows a structured JSON format with standard HTTP status codes, making troubleshooting predictable once you understand the patterns. This guide covers every error code you may encounter, explains what causes each one, and provides production-ready code examples so you can handle failures gracefully and keep your Canadian payment integrations running smoothly.
Whether you are sending Interac e-Transfers, initiating EFT payouts, managing beneficiaries, or querying wallet balances through the Invincible Pay API, understanding error responses is essential to building a resilient integration. Below, you will find a complete reference organized by HTTP status code, endpoint-specific guidance, and reusable error-handling patterns in JavaScript and Python.
How Does the Invincible Pay API Return Errors?
The Invincible Pay API is built on an OpenAPI 3.1 specification, which means every endpoint follows a consistent, predictable response structure. When a request fails, the API responds with an appropriate HTTP status code and a JSON body that conforms to the GenericError schema.
A typical error response looks like this:
json
{
"error": "Unauthorized",
"message": "Invalid or expired API key.",
"statusCode": 401
}Three fields appear in every error response. The statusCode is the numeric HTTP status code mirrored in the response body for convenience. The error field is a short, human-readable label for the error class (such as "Bad Request" or "Conflict"). The message field provides a more detailed explanation of what went wrong, which is especially useful for debugging during development.
This consistency is one of the advantages of working with an OpenAPI-first platform. You can write a single error-handling function that parses this structure, and it will work across every endpoint in the API.
What Are the HTTP Status Codes You Will Encounter?
The API uses standard HTTP status codes grouped into familiar categories. Understanding these categories helps you decide how to respond programmatically, whether that means retrying, alerting a user, or escalating to support.
2xx: Success Responses
200 OK is the standard success response for most endpoints. When you fetch your account information from /v1/accounts/me, list beneficiaries from /v1/beneficiaries, send an e-Transfer via /v1/funding-sources/{id}/etransfer, or check your wallet balance through /v1/funding-sources/balance/me, a 200 response means the request completed as expected. The response body will contain the relevant data object (an AccountDto, a BeneficiaryDto, a TransactionDto, and so on).
4xx: Client Errors
Client errors indicate something about your request that needs to be corrected before retrying.
400 Bad Request means the server understood your request but could not process it because of invalid input. This commonly occurs on the /v1/funding-sources/{id}/expand endpoint when you provide a malformed funding_source_id, or when a transaction request violates the input rules returned by /v1/funding-sources/input-rules. For example, sending an amount below the minimum transaction threshold or above the maximum (which the AmountRuleDto defines as validateTransactionMinAmount and validateTransactionMaxAmount) will trigger a 400.
To avoid 400 errors proactively, call the /v1/funding-sources/input-rules endpoint before submitting transactions. This returns the exact validation rules the backend enforces, including allowed character patterns for transaction descriptions and the minimum/maximum CAD amounts. Build your client-side validation to match these rules, and you will eliminate most 400 errors before they happen.
401 Unauthorized means your API key is missing, malformed, or expired. The Invincible Pay API uses API key and secret authentication. You generate keys through the /v1/users/me/api-keys endpoint, and if a key has been deleted (via the DELETE /v1/users/me/api-keys/{api_key} endpoint), any subsequent request using that key will return a 401.
Common causes include forgetting to include the authentication header, accidentally using a key from a different environment, or revoking a key without updating the systems that depend on it. If you receive a 401, verify your key is active by logging into your Invincible Pay dashboard.
404 Not Found indicates that the resource you requested does not exist. This appears on the /v1/funding-sources/{id}/expand endpoint when you reference a funding_source_id that is not associated with your account, and on /v1/users/me if the authenticated user record cannot be located. Double-check that your UUIDs are correct and that the resource belongs to your account.
409 Conflict is specific to the POST /v1/beneficiaries endpoint. It means you are trying to create a beneficiary that duplicates an existing record. Before creating a new beneficiary, query your existing list with GET /v1/beneficiaries and check whether a matching record already exists. If you need to change details for an existing beneficiary, use the PUT /v1/beneficiaries/{uuid} endpoint instead.
5xx: Server Errors
500 Internal Server Error is the catch-all for unexpected server-side issues. Every endpoint in the Invincible Pay API documents 500 as a possible response. While these are rare on a well-maintained platform, they can occur during infrastructure maintenance windows or under extreme load.
The key rule for 500 errors: always retry with exponential backoff. A single 500 does not mean the system is down. It may mean a transient issue affected your specific request. Implement a retry strategy (discussed in detail below) and only escalate to Invincible Pay support if 500 errors persist across multiple retries.
503 Service Unavailable is returned by the /health endpoint when the platform is undergoing maintenance or is otherwise unavailable. If you are building an integration that depends on platform availability, poll the /health endpoint periodically and pause outgoing transactions when it returns 503.
Endpoint-by-Endpoint Error Reference
This section breaks down the specific errors each endpoint can return, so you can build targeted error handling for each part of your integration.
Ready to stop chasing payments?
Get your Invincible Wallet in minutes. Near-instant delivery across Canada.
Account Endpoints
GET /v1/accounts/me retrieves your account information, including account type (personal, corporate, or moneyServicesBusiness) and status (active, inactive, inReview, or awaitingOnboardingApproval).
Possible errors: 401 (invalid credentials), 500 (server error).
If your account status is returned as inReview or awaitingOnboardingApproval, certain transaction endpoints may reject requests until your account is fully activated. This is not an error code per se, but it is a common source of confusion for developers who have just completed onboarding.
Beneficiary Endpoints
GET /v1/beneficiaries lists all your saved beneficiaries. Possible errors: 500.
POST /v1/beneficiaries creates a new beneficiary. You must create a beneficiary before sending any transaction (e-Transfer, EFT, or internal transfer). Possible errors: 409 (duplicate beneficiary), 500.
PUT /v1/beneficiaries/{uuid} updates an existing beneficiary. All future transactions to that beneficiary will use the updated details. Possible errors: 500.
DELETE /v1/beneficiaries/{uuid} removes a beneficiary. Possible errors: 500.
A practical tip: if you receive a 409 on beneficiary creation, do not treat it as a failure in your user flow. Instead, fetch the existing beneficiary list, find the matching record, and proceed with the transaction using that beneficiary's UUID.
Funding Source and Transaction Endpoints
These are the core payment endpoints, and they are where most integration complexity lives.
GET /v1/funding-sources/me lists your wallets (typically one per account). Possible errors: 500.
GET /v1/funding-sources/balance/me returns your wallets with current CAD balances. Possible errors: 500.
GET /v1/funding-sources/{id}/expand returns detailed wallet information including balance. Possible errors: 400 (invalid funding source ID format), 404 (funding source not found), 500.
POST /v1/funding-sources/calculate-fee calculates the fee for a proposed transaction before you execute it. Possible errors: 500.
GET /v1/funding-sources/input-rules returns the validation rules the backend uses for transaction descriptions and amounts. Possible errors: 500.
POST /v1/funding-sources/{id}/etransfer sends an Interac e-Transfer to a beneficiary. With Invincible Pay, e-Transfers support up to $25,000 per transaction with no daily limits. Possible errors: 500.
POST /v1/funding-sources/{id}/eft sends an EFT (Electronic Funds Transfer) to a beneficiary. Possible errors: 500.
POST /v1/funding-sources/{id}/internal sends a wallet-to-wallet transfer within the Invincible Pay network. Possible errors: 500.
For all three transaction endpoints, a 500 error does not necessarily mean the transaction failed. It may have been accepted by the backend but the response was lost due to a network issue. Always query /v1/transactions afterward to confirm whether the transaction was created before retrying.
Transaction Search Endpoint
GET /v1/transactions allows you to search and filter your transaction history by date range (using Unix timestamps in milliseconds), amount range (in CAD), beneficiary ID, transaction category (EFT, eTransfer, or internal), and pagination parameters. Possible errors: 500.
Pagination is zero-indexed, with a default page size of 20 and a maximum of 100 records per page.
User and API Key Endpoints
GET /v1/users/me returns details about the authenticated user. Possible errors: 400, 404, 500.
GET /v1/users/me/api-keys lists all your API keys. Possible errors: 500.
POST /v1/users/me/api-keys generates a new API key and secret. Store the secret immediately, as it will not be retrievable later. Possible errors: 500.
DELETE /v1/users/me/api-keys/{api_key} permanently revokes an API key. This action cannot be undone. Possible errors: 500.
Health Endpoint
GET /health checks whether the Invincible Pay API is operational. Returns 200 when healthy and 503 when unavailable.
How Should You Handle Errors in Production Code?
Knowing what errors exist is only half the equation. The other half is writing resilient code that handles them gracefully. Below are production-ready patterns in JavaScript (Node.js) and Python.
JavaScript/Node.js Error Handler
class InvinciblePayError extends Error {
constructor(statusCode, errorType, message) {
super(message);
this.statusCode = statusCode;
this.errorType = errorType;
this.name = "InvinciblePayError";
}
}
async function invinciblePayRequest(method, path, body = null) {
const baseUrl = "https://api.invinciblepay.com";
const maxRetries = 3;
let attempt = 0;
while (attempt < maxRetries) {
try {
const options = {
method,
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${process.env.INVINCIBLE_PAY_API_KEY}`
}
};
if (body) {
options.body = JSON.stringify(body);
}
const response = await fetch(`${baseUrl}${path}`, options);
if (response.ok) {
return await response.json();
}
const errorBody = await response.json();
// Do not retry client errors (4xx); they require a fix
if (response.status >= 400 && response.status < 500) {
throw new InvinciblePayError(
errorBody.statusCode,
errorBody.error,
errorBody.message
);
}
// Retry server errors (5xx) with exponential backoff
if (response.status >= 500) {
attempt++;
if (attempt >= maxRetries) {
throw new InvinciblePayError(
errorBody.statusCode,
errorBody.error,
`Server error after ${maxRetries} retries: ${errorBody.message}`
);
}
const delay = Math.pow(2, attempt) * 1000; // 2s, 4s, 8s
await new Promise(resolve => setTimeout(resolve, delay));
continue;
}
} catch (err) {
if (err instanceof InvinciblePayError) throw err;
// Network-level errors should also be retried
attempt++;
if (attempt >= maxRetries) {
throw new Error(`Network error after ${maxRetries} retries: ${err.message}`);
}
const delay = Math.pow(2, attempt) * 1000;
await new Promise(resolve => setTimeout(resolve, delay));
}
}
}Python Error Handler
import requests
import time
import os
class InvinciblePayError(Exception):
def __init__(self, status_code, error_type, message):
self.status_code = status_code
self.error_type = error_type
self.message = message
super().__init__(f"{status_code} {error_type}: {message}")
def invincible_pay_request(method, path, body=None):
base_url = "https://api.invinciblepay.com"
max_retries = 3
headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {os.environ['INVINCIBLE_PAY_API_KEY']}"
}
for attempt in range(max_retries):
try:
response = requests.request(
method,
f"{base_url}{path}",
json=body,
headers=headers,
timeout=30
)
if response.ok:
return response.json()
error_body = response.json()
# Do not retry client errors
if 400 <= response.status_code < 500:
raise InvinciblePayError(
error_body.get("statusCode"),
error_body.get("error"),
error_body.get("message")
)
# Retry server errors with exponential backoff
if response.status_code >= 500:
if attempt == max_retries - 1:
raise InvinciblePayError(
error_body.get("statusCode"),
error_body.get("error"),
f"Server error after {max_retries} retries"
)
time.sleep(2 ** (attempt + 1))
continue
except requests.exceptions.RequestException as e:
if attempt == max_retries - 1:
raise Exception(f"Network error after {max_retries} retries: {e}")
time.sleep(2 ** (attempt + 1))Using the Error Handler
Here is how you would use either wrapper to send an e-Transfer, handle a 409 conflict on beneficiary creation, and check your wallet balance:
javascript
// Example: Create a beneficiary, handle duplicate conflict
try {
const beneficiary = await invinciblePayRequest("POST", "/v1/beneficiaries", {
name: "Jane Smith",
email: "jane@example.com"
});
console.log("Beneficiary created:", beneficiary.id);
} catch (err) {
if (err instanceof InvinciblePayError && err.statusCode === 409) {
console.log("Beneficiary already exists. Fetching existing records...");
const existing = await invinciblePayRequest("GET", "/v1/beneficiaries");
// Find and use the matching beneficiary
} else {
throw err;
}
}
// Example: Send an e-Transfer with pre-flight fee calculation
const fee = await invinciblePayRequest("POST", "/v1/funding-sources/calculate-fee", {
amount: 5000.00,
type: "etransfer"
});
console.log(`Fee for $5,000 CAD e-Transfer: $${fee.fee} CAD`);
const transaction = await invinciblePayRequest(
"POST",
`/v1/funding-sources/${walletId}/etransfer`,
{
beneficiaryId: beneficiary.id,
amount: 5000.00,
description: "Invoice payment Q2"
}
);What Are the Most Common Mistakes Developers Make?
Based on typical integration patterns, here are the errors that trip up developers most frequently, along with how to avoid them.
Sending transactions without creating a beneficiary first. The Invincible Pay API requires a beneficiary record before any transaction can be initiated. If you try to send an e-Transfer or EFT with a beneficiary ID that does not exist, the request will fail. Always confirm the beneficiary exists (or create one) as the first step in your payment flow.
Ignoring input validation rules. The /v1/funding-sources/input-rules endpoint exists for a reason. It tells you exactly what the backend will accept for transaction descriptions and amounts. Fetch these rules when your application starts, cache them, and validate inputs client-side before sending requests. This prevents unnecessary 400 errors and gives your users immediate feedback.
Not handling idempotency for transactions. If a 500 error occurs on a transaction endpoint, you cannot assume the transaction was not created. The request may have succeeded on the server but the response was lost in transit. Before retrying, query /v1/transactions filtered by your expected beneficiary and amount to check whether the payment already went through. Double-sending a payment is a far worse outcome than a slightly delayed retry.
Hardcoding funding source IDs. Your funding source (wallet) ID can be retrieved dynamically with GET /v1/funding-sources/me. Hardcoding it introduces fragility. If Invincible Pay ever migrates your wallet or adds multi-wallet support, hardcoded IDs will break.
Not monitoring the health endpoint. Polling GET /health is the simplest way to build graceful degradation into your integration. If the endpoint returns 503, queue outgoing transactions and process them once the service is healthy again.
How Does Invincible Pay's API Compare to Other Payment APIs?
Developers who have worked with Stripe, Square, or traditional bank APIs will find the Invincible Pay API refreshingly straightforward. The OpenAPI-first design means you can auto-generate client libraries in any language. The consistent GenericError schema across all endpoints means one error handler covers everything. And the focused scope of the API (accounts, beneficiaries, funding sources, and transactions) avoids the overwhelming surface area of some larger payment platforms.
What sets the Invincible Pay API apart for Canadian developers is access to Interac e-Transfer rails with $25,000 per-transaction limits and no daily caps. Most Canadian bank APIs restrict e-Transfers to $3,000 or less. If your application handles vendor payouts, freelancer payments, or high-value B2B transactions, this limit alone can simplify your integration by eliminating the need to split large payments across multiple transactions.
The API also provides direct access to the Invincible Wallet, a centralized CAD balance from which all payment types (e-Transfer, EFT, wire, and internal transfers) originate. This unified funding model means you manage one balance instead of reconciling across multiple accounts and providers.
What Should You Do When You Cannot Resolve an Error?
If you have checked your request format, confirmed your API key is active, verified the beneficiary exists, and the error persists, here is a recommended escalation path.
First, check the /health endpoint. If it returns 503, wait and retry. Second, review the Invincible Pay API documentation at docs.invinciblepay.com for any recent changes to endpoint specifications. Third, contact Invincible Pay support through the Help Centre with the following details: the full request (with sensitive data redacted), the complete error response, the timestamp of the request, and your account ID. Invincible Pay provides enterprise-grade support for production integrations, so do not hesitate to reach out.
FAQ
What format do Invincible Pay API error responses use?
All error responses use a consistent JSON structure containing three fields: statusCode (the numeric HTTP code), error (a short label like "Bad Request" or "Unauthorized"), and message (a detailed explanation). This GenericError schema is the same across every endpoint, so you only need to write one error parser for your entire integration.
What should I do if I get a 500 error when sending a transaction?
Do not immediately retry the transaction. A 500 error on a payment endpoint does not confirm that the payment failed. Query the GET /v1/transactions endpoint first to check whether the transaction was created. If it was not, retry using exponential backoff (waiting 2 seconds, then 4 seconds, then 8 seconds between attempts). If 500 errors persist after three retries, check the /health endpoint and contact Invincible Pay support.
How do I avoid 409 Conflict errors when creating beneficiaries?
The 409 status code means a beneficiary with the same details already exists. Before calling POST /v1/beneficiaries, fetch your existing beneficiary list with GET /v1/beneficiaries and check for duplicates. If a match exists, use that beneficiary's UUID for your transaction. If the existing record needs updating, use PUT /v1/beneficiaries/{uuid}.
Where can I find the validation rules for transaction inputs?
Call GET /v1/funding-sources/input-rules to retrieve the exact validation rules the backend enforces. This includes minimum and maximum transaction amounts in CAD, as well as allowed patterns for transaction descriptions. Caching these rules on application startup and validating inputs client-side will prevent most 400 Bad Request errors.
Can I auto-generate an API client from the Invincible Pay API specification?
Yes. The Invincible Pay API publishes a complete OpenAPI 3.1 specification at https://docs.invinciblepay.com/api-v1.json. You can use tools like OpenAPI Generator, Swagger Codegen, or any OpenAPI-compatible SDK generator to create a client library in your preferred language. This saves development time and ensures your client stays in sync with the API specification.
Ready to build on Canadian payment rails? Open your Invincible Wallet in minutes and start integrating with the Invincible Pay API. Explore the full API documentation or talk to our team for integration support.
