What error codes can I expect from Merge and how do I handle them?
Last updated: July 15, 2025
Error handling
Handling error messages based on different response codes is critical to API development. Here’s a general approach to handling various HTTP response codes coming back from Merge effectively.
Categorize response codes
HTTP error codes can be categorized into different classes. Only some are used by Merge.
3xx: Redirection messages (not returned by Merge)4xx: Client errors5xx: Server errors
Define error handling logic
Here are the types of errors returned by Merge and guidance on each:
Note, the "Retry" column applies to POSTs. GET requests are always safe to retry with a maximum retry threshold.
4xx: Client errors
Code | Issue | Description | Retry | Troubleshooting Tips |
400 | Bad Request | Malformed request- data is not present in the 3rd Party System and/or the 3rd party system does not support the request. | No | Check if the 3rd party system has the data you want to pull or push to. |
401 | Unauthorized | Unauthorized: authorization is either missing or misconfigured. | No | If this is a test account, make sure you are using the Account Token stored in the bottom right of their Linked Account page. |
404 | URL Not Found | The resource was not found with the given identifier- the request was entered incorrectly. | No | Check our docs on how to format our requests to the Merge API. |
404 | URL Not Found | The resource was not found with the given identifier- the data does not exist. | No | Check if the data is present within the 3rd party system. |
404 | URL Not Found | The resource was not found with the given identifier- the integration does not support that endpoint. | No | Check our docs to see what common models our integrations support. |
404 | URL Not Found | The resource was not found with the given identifier- the subdomain was entered incorrectly. | No | Check for a valid subdomain. If it is accurate, contact support. |
408 | Passthrough request timeout | The passthrough request to the third party exceeded the 60 second timeout limit | No | Use async passthrough. |
429 | Too Many Requests | The Merge Organization has reached a rate limit for the requested linked account, preventing any more requests from going through. | Yes | Retry using the logic explained in the Automatic Retry section below. |
5xx: Server errors
Code | Issue | Description | Retry | Troubleshooting Tips |
500 | Internal Server Error | Merge is experiencing a service interruption. | Yes* | Retry using the logic explained in the Automatic Retry section below. |
502, 503, 504 | Service Interruption | The connection was interrupted or timed out. | Yes* | Retry using the logic explained in the Automatic Retry section below. |
Automatic retry
It's generally best practice to have pre-defined retry logic when encountering specific error codes. Below is guidance on what logic we recommend setting for each response code you get back from Merge.
Automatic retry logic
Use the retry logic outlined below to ensure that transient issues do not cause immediate failures, improving the resilience of your integration.
Code | Issue | Description | Retry Logic |
429 | Too Many Requests | The Merge Organization has reached a rate limit for the requested linked account, preventing any more requests from going through. | Retry the request after an initial 1 minute delay, increasing the delay exponentially with each retry (aka "exponential backoff") |
500 | Internal Server Error | Merge is experiencing a service interruption. | Retry the request after an initial 5-30s delay, increasing the delay exponentially with each retry (aka "exponential backoff") POST requests must use the Idempotency-Key header to avoid creating duplicate records |
502, 503, 504 | Service Interruption | The connection was interrupted or timed out. | Retry the request after an initial 5-30s delay, increasing the delay exponentially with each retry (aka "exponential backoff") POST requests must use the Idempotency-Key header to avoid creating duplicate records |
What is "exponential backoff"?
An exponential backoff algorithm reduces the rate of API requests in response to an error code. For example, if an API request hits a 5xx error, it might try again 1 second later, then if it fails again, 2 seconds later, then 4 seconds, etc. Each time, the pause and subsequent attempt are multiplied by a fixed amount (in this case, 2).
Example of retry logic
GET request
An example of retry logic with exponential backoff for a GET request using our Node SDK is included below.
async function listFiles(modifiedAfter: Date) {
const { accountToken, apiKey } = retrieveTokens()
const merge = new MergeClient({ apiKey, accountToken });
let pageSize = 100; // default limit
let originalPageSize = pageSize; // store the original page size
let cursor: string | undefined;
const files: any[] = [];
const maxRetries = 3;
let retryCount = 0;
let retryDelay = 5000; // initial delay of 5 seconds
while (true) {
try {
const response = await merge.filestorage.files.list({
modifiedAfter,
pageSize,
cursor,
});
if (response.results) {
files.push(...response.results);
}
cursor = response.next;
// Reset retry count, delay, and pageSize after a successful request
retryCount = 0;
retryDelay = 1000;
pageSize = originalPageSize;
if (!cursor) {
break;
}
} catch (error) {
if (error.response.status === 429) {
// Too Many Requests, sleep for 60 seconds and retry
await new Promise(resolve => setTimeout(resolve, 60000));
} else {
// Server Error or other errors, retry with backoff
retryCount++;
if (retryCount <= maxRetries) {
await new Promise(resolve => setTimeout(resolve, retryDelay));
retryDelay *= 2; // exponential backoff
if (error.response.status >= 500 && error.response.status < 600) {
pageSize = Math.floor(pageSize / 2);
}
} else {
throw error;
}
}
}
}
return files;
}POST request
An example of retry logic with exponential backoff for a POST request using our Node SDK is included below.
async function createInvoice(invoice: InvoiceRequest): Promise<InvoiceResponse> {
const { accountToken, apiKey } = retrieveTokens();
const merge = new MergeClient({ apiKey, accountToken });
const maxRetries = 3;
let retryCount = 0;
let retryDelay = 5000; // initial delay of 5 seconds
while (true) {
try {
const response = await merge.accounting.invoices.create({
model: invoice
});
return response;
} catch (error) {
console.error(`Error creating invoice: ${error.message}`);
if (error.response.status === 429) {
// Too Many Requests, sleep for 60 seconds and retry
await new Promise(resolve => setTimeout(resolve, 60000));
} else if (error.response.status >= 500 && error.response.status < 600) {
// Server Error, retry with backoff
retryCount++;
if (retryCount <= maxRetries) {
await new Promise(resolve => setTimeout(resolve, retryDelay));
retryDelay *= 2; // exponential backoff
} else {
throw error;
}
} else {
throw error;
}
}
}
}