Skip to content

Rate Limits

Mehrdad Pajuhaan edited this page Oct 23, 2026 · 1 revision

Rate Limits

The Backoffice API is shared by every Selldone dashboard, integration and tool, so requests are rate limited. This page lists the current limits and explains how to design an integration that stays within them, whether you call the REST API directly or use the JavaScript SDK.

Dashboard API limit

Dashboard API calls through api.{domain} (for Selldone: https://api.selldone.com) are rate limited to protect the shared backoffice API.

API surface Current limit
api.{domain} dashboard API routes 80 requests per 1 minute

Treat this as a budget shared by everything that uses the same credentials: your sync job, your dashboard tabs and any background workers.

Route-specific limits

Some routes apply an additional, stricter throttle. These limits come from the route metadata documented in apis/API.ts:

Endpoint Limit
GET /users/suggestion/{search} 50 requests per 2 minutes
GET /users/mention/{search} 50 requests per 2 minutes
GET /product-repository/search 100 requests per 30 minutes
POST /shops/{shop_id}/product-repository/add 100 requests per 30 minutes
POST /shops/{shop_id}/pages/{page_id}/preview 20 requests per minute
POST /shops/{shop_id}/pages/{page_id}/acknowledge 20 requests per minute
POST /shops/{shop_id}/email/service/test 20 requests per 10 minutes
POST /accounts/{account_number}/charge/{gateway} 20 requests per 5 minutes
POST /accounts/{account_number}/subscribe/{gateway} 20 requests per 5 minutes
DELETE /accounts/{account_number}/subscribe 20 requests per 5 minutes
POST /accounts/{account_number}/check-subscribe 20 requests per 5 minutes
POST /partners/deals/{ref_code}/plans/{plan_code}/purchase/{gateway} 20 requests per 5 minutes

Limits can change. The values above reflect the current source of this repository.

When you hit the limit

Requests over the limit are rejected with HTTP 429 Too Many Requests. The backend is built on Laravel, whose throttle middleware normally adds Retry-After and X-RateLimit-* headers to the response; read them when they are present.

Recommended handling:

  1. Stop sending new requests from the same worker.
  2. Wait for Retry-After seconds, or back off exponentially (for example 2 s, 4 s, 8 s) if the header is missing.
  3. Retry the failed request once the wait is over. Never retry in a tight loop.
async function withRetry(call, attempts = 4) {
  for (let i = 0; i < attempts; i++) {
    try {
      return await call();
    } catch (error) {
      const status = error?.response?.status;
      if (status !== 429 || i === attempts - 1) throw error;
      const retryAfter = Number(error.response.headers?.["retry-after"]);
      const waitMs = Number.isFinite(retryAfter) ? retryAfter * 1000 : 2000 * 2 ** i;
      await new Promise((resolve) => setTimeout(resolve, waitMs));
    }
  }
}

const result = await withRetry(() =>
  window.$backoffice.product.setQuantity(SHOP_ID, PRODUCT_ID, 120),
);

Staying within the budget

  • Page with large limits. Product lists accept up to 250 items per request (100 is a safe default). Fewer, larger pages use less budget than many small ones. See Fetch Products List.
  • Sync incrementally. Use updated_at to fetch only products or categories changed since your last sync instead of re-reading the whole catalog.
  • Send only what changed. Product and variant edit endpoints update only the fields you send, so skip unchanged records entirely.
  • Use bulk import for large catalogs. One POST /shops/{shop_id}/import/products call (window.$backoffice.product.importer.send()) can create or update many products.
  • Cache reads in the browser. In the SDK, chain .cache() and .optimize(seconds) to reuse recent responses, and .cancellation() to drop superseded list requests. See JavaScript SDK.
  • Debounce search inputs so typing does not trigger a request per keystroke.
  • Spread scheduled jobs. Pace batch jobs to stay under 80 requests per minute rather than sending bursts.

Next steps

Clone this wiki locally