Errors & Rate Limits
Handle API errors and rate limits using WhataloClient's typed error classes and built-in retry logic.
WhataloClient throws typed error classes for every non-2xx response. Catching specific error types lets you handle failures precisely and surface meaningful messages to merchants.
Error Class Hierarchy
All errors extend WhataloAPIError, which provides the common fields shared across all failure types.
import {
WhataloAPIError,
AuthenticationError,
AuthorizationError,
NotFoundError,
ValidationError,
RateLimitError,
InternalError,
} from "@whatalo/plugin-sdk";Common Fields (WhataloAPIError)
| Field | Type | Description |
|---|---|---|
statusCode | number | HTTP status code of the response |
code | string | Machine-readable error code |
requestId | string | undefined | Unique request ID for support escalation |
message | string | Human-readable description |
Error Class Reference
| Class | HTTP Status | Code | Extra Fields |
|---|---|---|---|
AuthenticationError | 401 | authentication_error | — |
AuthorizationError | 403 | authorization_error | requiredScope: string |
NotFoundError | 404 | not_found | resourceType: string, resourceId: string |
ValidationError | 422 | validation_error | fieldErrors: { field, message }[] |
RateLimitError | 429 | rate_limit_exceeded | retryAfter: number (seconds) |
InternalError | 500 | internal_error | — |
Error Handling Examples
Basic pattern
import {
WhataloClient,
NotFoundError,
RateLimitError,
ValidationError,
AuthorizationError,
AuthenticationError,
} from "@whatalo/plugin-sdk";
const client = new WhataloClient({ apiKey: process.env.WHATALO_API_KEY! });
try {
const product = await client.products.get("999999999999");
} catch (error) {
if (error instanceof NotFoundError) {
// Product does not exist
console.log(`${error.resourceType} not found: ${error.resourceId}`);
} else if (error instanceof AuthorizationError) {
// Plugin is missing the required scope
console.log(`Insufficient permissions. Required: ${error.requiredScope}`);
} else if (error instanceof AuthenticationError) {
// API key is invalid or has been revoked
console.log("Invalid API key — check your WHATALO_API_KEY");
} else if (error instanceof RateLimitError) {
// Too many requests — wait before retrying
console.log(`Rate limited. Retry after ${error.retryAfter} seconds`);
} else if (error instanceof ValidationError) {
// Request body failed validation
error.fieldErrors.forEach((e) => {
console.log(` ${e.field}: ${e.message}`);
});
} else {
// Unknown error — re-throw so it surfaces properly
throw error;
}
}Handling validation errors on create/update
import { ValidationError } from "@whatalo/plugin-sdk";
try {
await client.products.create({
name: "", // Empty name is invalid
price: -5, // Negative price is invalid
});
} catch (error) {
if (error instanceof ValidationError) {
const messages = error.fieldErrors
.map((e) => `${e.field}: ${e.message}`)
.join("\n");
throw new Error(`Product creation failed:\n${messages}`);
}
throw error;
}Rate Limits
Reading the rate limit state
After every API call, the client updates its rateLimit property by reading the response headers:
await client.products.list({ page: 1, per_page: 10 });
console.log(client.rateLimit);
// { limit: 100, remaining: 89, reset: 1709280000 }| Field | Source Header | Description |
|---|---|---|
limit | X-RateLimit-Limit | Maximum requests per window |
remaining | X-RateLimit-Remaining | Requests left in current window |
reset | X-RateLimit-Reset | Unix timestamp when the window resets |
Proactive rate limit checks
For batch operations, check client.rateLimit.remaining before each call to avoid hitting the limit mid-batch:
const productIds = ["472819365001", "472819365002", "472819365003", /* ... */];
for (const id of productIds) {
// Pause if fewer than 10 requests remain to avoid hitting the limit
if (client.rateLimit.remaining < 10) {
const msUntilReset = client.rateLimit.reset * 1000 - Date.now();
await new Promise((resolve) => setTimeout(resolve, msUntilReset + 100));
}
await client.products.get(id);
}Automatic Retries
Configure retries on the client to automatically retry 5xx errors with exponential backoff:
const client = new WhataloClient({
apiKey: process.env.WHATALO_API_KEY!,
retries: 3, // Retry up to 3 times on server errors
});| Attempt | Delay |
|---|---|
| 1st retry | 1 second |
| 2nd retry | 2 seconds |
| 3rd retry | 4 seconds |
RateLimitError (429) is not retried automatically. Use error.retryAfter to schedule your own retry:
import { RateLimitError } from "@whatalo/plugin-sdk";
async function withRateLimitRetry<T>(fn: () => Promise<T>): Promise<T> {
try {
return await fn();
} catch (error) {
if (error instanceof RateLimitError) {
await new Promise((resolve) =>
setTimeout(resolve, error.retryAfter * 1000)
);
return fn(); // Single retry after the window resets
}
throw error;
}
}
const product = await withRateLimitRetry(() =>
client.products.get("472819365047")
);Logging Request IDs
Include requestId in error logs to enable fast support escalation:
import { WhataloAPIError } from "@whatalo/plugin-sdk";
try {
await client.orders.get("order_123");
} catch (error) {
if (error instanceof WhataloAPIError) {
console.error({
message: error.message,
code: error.code,
statusCode: error.statusCode,
requestId: error.requestId, // Include in support tickets
});
}
throw error;
}