API Client

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)

FieldTypeDescription
statusCodenumberHTTP status code of the response
codestringMachine-readable error code
requestIdstring | undefinedUnique request ID for support escalation
messagestringHuman-readable description

Error Class Reference

ClassHTTP StatusCodeExtra Fields
AuthenticationError401authentication_error
AuthorizationError403authorization_errorrequiredScope: string
NotFoundError404not_foundresourceType: string, resourceId: string
ValidationError422validation_errorfieldErrors: { field, message }[]
RateLimitError429rate_limit_exceededretryAfter: number (seconds)
InternalError500internal_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 }
FieldSource HeaderDescription
limitX-RateLimit-LimitMaximum requests per window
remainingX-RateLimit-RemainingRequests left in current window
resetX-RateLimit-ResetUnix 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
});
AttemptDelay
1st retry1 second
2nd retry2 seconds
3rd retry4 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;
}

On this page