CheckCourt
TypeScript SDK

Errors and retries

The SDK's error classes, rate limits and what to do in each case

Every error the SDK throws on purpose extends CheckCourtError. Network errors from fetch are passed through unchanged.

ClassWhenKey fields
CheckCourtApiErrorunwrap() on a non-2xx response from /api/v1status, code, message, response
OAuthErrorErrors from /api/oauth/*, e.g. invalid_client or slow_downstatus, error, description
TokenRevokedErrorinvalid_grant: code or refresh token is deadas OAuthError
AppTemporarilyUnavailableErrortemporarily_unavailable on refreshas OAuthError
WebhookSignatureErrorWebhook verification failedreason
ExtensionVerificationErrorContext token or extension request invalidreason

instanceof works even when the error comes from another copy of the SDK, for example a client cached on globalThis and shared between route bundles in Next.js: the classes check a brand (Symbol.for("checkcourt.sdk.error") plus one per class) instead of the prototype. name is fixed ("TokenRevokedError" and so on) and survives minification. Without importing the classes, use isCheckCourtError:

import { isCheckCourtError } from "@checkcourt/sdk";

if (isCheckCourtError(err, "TokenRevokedError")) {
  // err is typed as TokenRevokedError; a subclass matches its parents too ("OAuthError")
}

API errors

Branch on code, never on message. Messages are German and may change.

import { CheckCourtApiError, unwrap } from "@checkcourt/sdk";

try {
  await unwrap(client.POST("/me/bookings", { body }));
} catch (err) {
  if (err instanceof CheckCourtApiError && err.code === "BUSINESS_RULE") {
    // a booking rule was violated; show err.message to the member
  } else {
    throw err;
  }
}
codeHTTPMeaning
UNAUTHORIZED401Token or key invalid, expired, or the feature is off
VALIDATION400Invalid input, or X-Tenant-Id missing with several clubs
FORBIDDEN403Missing scope, or a foreign club
NOT_FOUND404Does not exist, or you may not see it
BUSINESS_RULE422A business rule, such as a booking policy
RATE_LIMITED429Too many requests, see below

Rate limits and retries

When the API answers 429, the client waits and tries again:

  • If CheckCourt sends Retry-After, it waits that long.
  • Otherwise it waits baseDelayMs (default 1 second), doubling on every further attempt.
  • After maxRetries retries (default 2) it returns the 429 response; unwrap() then throws CheckCourtApiError with code: "RATE_LIMITED".
  • Waits longer than maxDelayMs (default 60 seconds) are not attempted; the response is returned right away.
const client = createCheckCourtClient({
  auth,
  retry: { maxRetries: 5, baseDelayMs: 2000, maxDelayMs: 30_000 },
  // retry: false,  // never retry automatically
});

Only 429 is retried. Other errors, including 5xx, come back immediately, because write requests cannot be repeated safely. The limits themselves are listed under Rate limits.

On 401, a client using installationAuth or userAuth fetches a fresh token once and repeats the request. With apiKeyAuth, the 401 comes back directly.

Member tokens

userAuth and refreshTokens distinguish two cases you must handle differently:

import { AppTemporarilyUnavailableError, TokenRevokedError, unwrap } from "@checkcourt/sdk";

try {
  return await unwrap(client.GET("/me/bookings"));
} catch (err) {
  if (err instanceof AppTemporarilyUnavailableError) {
    // consent still valid, but nothing may run right now (connection paused, app
    // suspended, account banned). Keep the tokens, try again later.
    return showNotice("Die Verbindung zu CheckCourt ist gerade pausiert.");
  }
  if (err instanceof TokenRevokedError) {
    // chain is dead: expired, revoked, reused, older than 180 days or disconnected everywhere
    await db.tokens.delete(memberId);
    return redirectToConnect();
  }
  throw err;
}
  • On AppTemporarilyUnavailableError, CheckCourt does not consume the refresh token. The SDK keeps it and tries to refresh again on the next call.
  • After TokenRevokedError, the same userAuth throws that error on every further call without asking CheckCourt. Create a new one after a new authorization.
  • The 180-day limit since consent also surfaces as TokenRevokedError. If the member is still connected everywhere, the new authorization usually skips the dialog.

Installation tokens

If an installation keeps returning OAuthError with error: "invalid_client" while others work, the club has probably paused or removed it. The SDK does not remember failures; the next call asks CheckCourt again.

On this page