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.
| Class | When | Key fields |
|---|---|---|
CheckCourtApiError | unwrap() on a non-2xx response from /api/v1 | status, code, message, response |
OAuthError | Errors from /api/oauth/*, e.g. invalid_client or slow_down | status, error, description |
TokenRevokedError | invalid_grant: code or refresh token is dead | as OAuthError |
AppTemporarilyUnavailableError | temporarily_unavailable on refresh | as OAuthError |
WebhookSignatureError | Webhook verification failed | reason |
ExtensionVerificationError | Context token or extension request invalid | reason |
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;
}
}code | HTTP | Meaning |
|---|---|---|
UNAUTHORIZED | 401 | Token or key invalid, expired, or the feature is off |
VALIDATION | 400 | Invalid input, or X-Tenant-Id missing with several clubs |
FORBIDDEN | 403 | Missing scope, or a foreign club |
NOT_FOUND | 404 | Does not exist, or you may not see it |
BUSINESS_RULE | 422 | A business rule, such as a booking policy |
RATE_LIMITED | 429 | Too 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
maxRetriesretries (default 2) it returns the429response;unwrap()then throwsCheckCourtApiErrorwithcode: "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 sameuserAuththrows 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.