Skip to main content

Error Classes

import { ApiError, AuthenticationError, RetryExhaustedError } from "@keystoneos/sdk";

try {
  await client.settlements.get("non-existent-id");
} catch (error) {
  if (error instanceof ApiError) {
    console.log(error.status);   // 404
    console.log(error.message);  // "Settlement not found"
    console.log(error.body);     // Raw response body
    console.log(error.headers);  // Response headers
  } else if (error instanceof AuthenticationError) {
    // M2M credentials invalid or Auth0 unavailable
    console.log(error.message);
  } else if (error instanceof RetryExhaustedError) {
    // All retries failed (429 or 5xx)
    console.log(error.lastStatus); // 503
    console.log(error.attempts);   // 3
  }
}
ErrorWhenProperties
ApiErrorNon-2xx responsestatus, message, body, headers
AuthenticationErrorToken fetch/refresh failedmessage
RetryExhaustedErrorAll retries exhausted on 429/5xxlastStatus, attempts

Common status codes

StatusMeaningTypical cause
400Bad requestMalformed JSON or invalid field format
401UnauthorizedInvalid or expired M2M token
403ForbiddenMissing required scope or suspended platform
404Not foundResource doesn’t exist or belongs to another environment
409ConflictState conflict (e.g., settlement already finalized)
422Validation errorBusiness rule violation (e.g., timeout in the past)
429Rate limitedToo many requests (auto-retried)
5xxServer errorKeyStone API issue (auto-retried)

Retry Behavior

The SDK automatically retries on 429 (rate limit) and 5xx (server error):
SettingDefaultDescription
maxRetries3Max retry attempts
retryBaseDelay500Base delay in ms
StrategyExponentialbaseDelay * 2^attempt * jitter(0.75, 1.25)
429 handlingRetry-AfterRespects the Retry-After header if present
Non-retryable errors (4xx except 429) fail immediately. There is no point retrying a 404 or 422.

Example timing

With default settings (500ms base, 3 retries):
  • Attempt 1: immediate
  • Attempt 2: ~500ms delay
  • Attempt 3: ~1000ms delay
  • Attempt 4: ~2000ms delay
  • After attempt 4: throws RetryExhaustedError

Recovery Patterns

Idempotent retries

For creation endpoints, always use an idempotency key so retries are safe:
const key = client.generateIdempotencyKey();

// If this fails mid-request (network error), retrying with the same key
// returns the existing resource instead of creating a duplicate
const settlement = await client.settlements.create({
  idempotencyKey: key,
  // ...
});

Handling rate limits

The SDK handles 429 automatically. If you need custom logic:
try {
  await client.settlements.list();
} catch (error) {
  if (error instanceof RetryExhaustedError && error.lastStatus === 429) {
    // All retries exhausted on rate limit
    // Wait longer and retry, or queue for later
  }
}

Handling state conflicts

Settlement operations may return 409 when the settlement has moved to a different state:
try {
  await client.settlements.submitComplianceDecision(id, { decision: "approve" });
} catch (error) {
  if (error instanceof ApiError && error.status === 409) {
    // Settlement is no longer in COMPLIANCE_CHECKING state
    // Fetch current state and act accordingly
    const settlement = await client.settlements.get(id);
    console.log(`Current state: ${settlement.state}`);
  }
}