Handle errors and retries
Which errors to retry, how long to wait, and what to log so support can help.
Version v1.1, updated
On this page
You will make your integration recover on its own from the errors that pass, and stop on the ones that need a fix. The status and the code tell you which is which.
Before you start
Read Errors for the members of a problem, and Idempotency for safe retries of writes.
Retry or fix
Steps
Create the idempotency key once per write, before the first attempt.
On a retryable answer, wait the
Retry-Afterseconds when the header is there. Otherwise double the wait each time, from one second up to a minute, with some randomness.Stop after a few attempts and report the failure with its request id.
On any other error, log it and move the record aside for a person to look at.
async function callBirp(path, init = {}, attempts = 5) {
// One idempotency key for every attempt of the same write.
const headers = {
Authorization: `Bearer ${process.env.BIRP_API_KEY}`,
"Idempotency-Key": crypto.randomUUID(),
...init.headers,
};
for (let attempt = 1; ; attempt++) {
const response = await fetch(`https://api.birp.io${path}`, { ...init, headers });
const retryable = response.status === 429 || response.status >= 500;
if (!retryable || attempt === attempts) return response;
const after = Number(response.headers.get("Retry-After"));
const wait = after > 0 ? after * 1000 : Math.min(60000, 1000 * 2 ** attempt) * (0.5 + Math.random());
console.warn("BIRP retry", response.status, response.headers.get("X-Request-Id"));
await new Promise((resolve) => setTimeout(resolve, wait));
}
}import os
import random
import time
import uuid
import requests
def call_birp(method, path, attempts=5, **kwargs):
# One idempotency key for every attempt of the same write.
headers = {
"Authorization": f"Bearer {os.environ['BIRP_API_KEY']}",
"Idempotency-Key": str(uuid.uuid4()),
}
for attempt in range(1, attempts + 1):
response = requests.request(method, f"https://api.birp.io{path}", headers=headers, timeout=10, **kwargs)
retryable = response.status_code == 429 or response.status_code >= 500
if not retryable or attempt == attempts:
return response
after = int(response.headers.get("Retry-After", "0") or 0)
wait = after if after > 0 else min(60, 2 ** attempt) * (0.5 + random.random())
print("BIRP retry", response.status_code, response.headers.get("X-Request-Id"))
time.sleep(wait)<?php
function call_birp(string $method, string $path, ?string $json = null, int $attempts = 5): array
{
// One idempotency key for every attempt of the same write.
$key = bin2hex(random_bytes(16));
for ($attempt = 1; ; $attempt++) {
$ch = curl_init("https://api.birp.io" . $path);
$headers = [];
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => $method,
CURLOPT_HTTPHEADER => array_filter([
"Authorization: Bearer " . getenv("BIRP_API_KEY"),
"Idempotency-Key: " . $key,
$json !== null ? "Content-Type: application/json" : null,
]),
CURLOPT_POSTFIELDS => $json,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HEADERFUNCTION => function ($ch, $line) use (&$headers) {
$parts = explode(":", $line, 2);
if (count($parts) === 2) $headers[strtolower(trim($parts[0]))] = trim($parts[1]);
return strlen($line);
},
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);
$retryable = $status === 429 || $status >= 500;
if (!$retryable || $attempt === $attempts) return [$status, $body];
$after = (int) ($headers["retry-after"] ?? 0);
$wait = $after > 0 ? $after : min(60, 2 ** $attempt) * (0.5 + mt_rand() / mt_getrandmax());
error_log("BIRP retry " . $status . " " . ($headers["x-request-id"] ?? ""));
usleep((int) ($wait * 1000000));
}
}Log what support needs
The
request_idof the answer, orX-Request-Id, and the time of the request.The method, the path, the status and the
code.Never the API key, and never a whole request body with customer data.
Check the result
Force a 422 on purpose, for example with an unknown field. Check that your integration logs the code and the request id, and does not retry.
What can go wrong
Retrying a 4xx unchanged repeats the error and uses up your rate limit. Retrying a write without the same idempotency key can create it twice.
Related
- errors-overview
- idempotency
- rate-limits