Error Handling
Handle API errors gracefully
Error Types
The Betalink API client provides typed error classes for different error scenarios:
| Error Class | HTTP Status | Description |
|---|---|---|
UnauthorizedError | 401 | Invalid or missing API key |
CompanyNotFoundError | 404 | Company not found by CVR |
BadRequestError | 400 | Invalid request parameters |
ValidationError | 422 | Response validation failed |
RateLimitError | 429 | Rate limit exceeded |
NetworkError | - | Network or connection issues |
BetalinkApiError | Various | Base error class |
Basic Error Handling
The API client provides type guard functions for checking and narrowing error types:
| Type Guard | Error Class |
|---|---|
isUnauthorizedError(error) | UnauthorizedError |
isCompanyNotFoundError(error) | CompanyNotFoundError |
isBadRequestError(error) | BadRequestError |
isValidationError(error) | ValidationError |
isRateLimitError(error) | RateLimitError |
isNetworkError(error) | NetworkError |
isBetalinkApiError(error) | BetalinkApiError |
import {
createBetalinkClient,
isUnauthorizedError,
isCompanyNotFoundError,
isRateLimitError,
isNetworkError,
} from "@betalink/api-client";
const client = createBetalinkClient({
baseUrl: "https://betalink.dev/api",
apiKey: "your-api-key",
});
try {
const company = await client.getCompany("12345678");
console.log(company.name);
} catch (error) {
if (isUnauthorizedError(error)) {
console.error("Invalid API key. Please check your credentials.");
} else if (isCompanyNotFoundError(error)) {
// TypeScript knows error.cvr is available here
console.error(`Company not found: ${error.cvr}`);
} else if (isRateLimitError(error)) {
// TypeScript knows error.retryAfter is available here
console.error(`Rate limited. Retry after ${error.retryAfter} seconds.`);
} else if (isNetworkError(error)) {
console.error("Network error. Please check your connection.");
} else {
console.error("An unexpected error occurred:", error);
}
}Error Properties
All errors extend BetalinkApiError and include:
class BetalinkApiError extends Error {
readonly code: string; // Error code (e.g., "UNAUTHORIZED")
readonly statusCode?: number; // HTTP status code
readonly cause?: unknown; // Original error
}UnauthorizedError
if (error instanceof UnauthorizedError) {
console.log(error.code); // "UNAUTHORIZED"
console.log(error.statusCode); // 401
console.log(error.message); // "Invalid or missing API key"
}CompanyNotFoundError
if (error instanceof CompanyNotFoundError) {
console.log(error.code); // "COMPANY_NOT_FOUND"
console.log(error.statusCode); // 404
console.log(error.cvr); // The CVR that was not found
}RateLimitError
if (error instanceof RateLimitError) {
console.log(error.code); // "RATE_LIMIT_EXCEEDED"
console.log(error.statusCode); // 429
console.log(error.retryAfter); // Seconds to wait before retrying
}API Error Responses
The API returns errors in a consistent JSON format:
{
"error": {
"code": "COMPANY_NOT_FOUND",
"message": "No company found with CVR 12345678"
}
}Error Codes
| Code | Description |
|---|---|
UNAUTHORIZED | Invalid or missing API key |
COMPANY_NOT_FOUND | Company not found by CVR |
BAD_REQUEST | Invalid request parameters |
VALIDATION_ERROR | Request/response validation failed |
RATE_LIMIT_EXCEEDED | Too many requests |
INTERNAL_ERROR | Server-side error |
Retry Strategies
Simple Retry
import { isNetworkError } from "@betalink/api-client";
async function fetchWithRetry<T>(
fn: () => Promise<T>,
retries = 3
): Promise<T> {
for (let i = 0; i < retries; i++) {
try {
return await fn();
} catch (error) {
if (i === retries - 1) throw error;
if (isNetworkError(error)) {
await sleep(1000 * (i + 1));
continue;
}
throw error;
}
}
throw new Error("Unreachable");
}
const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));Exponential Backoff
import { isNetworkError, isRateLimitError } from "@betalink/api-client";
async function fetchWithBackoff<T>(
fn: () => Promise<T>,
maxRetries = 5
): Promise<T> {
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
return await fn();
} catch (error) {
const isRetryable = isNetworkError(error) || isRateLimitError(error);
if (!isRetryable || attempt === maxRetries - 1) {
throw error;
}
const delay = isRateLimitError(error)
? (error.retryAfter ?? 60) * 1000
: Math.min(1000 * Math.pow(2, attempt), 30000);
await sleep(delay);
}
}
throw new Error("Unreachable");
}Effect Error Handling
For applications using Effect, errors are represented as tagged errors with full type safety. See the Effect Guide for details.
import { BetalinkApiClient, UnauthorizedApiError } from "@betalink/api-client/effect";
import * as Effect from "effect/Effect";
const program = Effect.gen(function* () {
const client = yield* BetalinkApiClient;
return yield* client.getCompany("12345678");
}).pipe(
Effect.catchTag("UnauthorizedApiError", (error) =>
Effect.succeed({ fallback: true, message: error.message })
)
);