Betalink Docs

Error Handling

Handle API errors gracefully

Error Types

The Betalink API client provides typed error classes for different error scenarios:

Error ClassHTTP StatusDescription
UnauthorizedError401Invalid or missing API key
CompanyNotFoundError404Company not found by CVR
BadRequestError400Invalid request parameters
ValidationError422Response validation failed
RateLimitError429Rate limit exceeded
NetworkError-Network or connection issues
BetalinkApiErrorVariousBase error class

Basic Error Handling

The API client provides type guard functions for checking and narrowing error types:

Type GuardError 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

CodeDescription
UNAUTHORIZEDInvalid or missing API key
COMPANY_NOT_FOUNDCompany not found by CVR
BAD_REQUESTInvalid request parameters
VALIDATION_ERRORRequest/response validation failed
RATE_LIMIT_EXCEEDEDToo many requests
INTERNAL_ERRORServer-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 })
  )
);

On this page