← All documentationContents ↓

Rate Limiting

Per-tier rate limits applied to each API credential, response headers for monitoring, and recommended retry strategies.

Overview

The Simply360 API enforces rate limits to ensure fair usage and platform stability. Limits are applied per credential — per API key, per user, or per OAuth client — within each team, and are tracked in fixed time windows. Session tokens share the counters of the API key that created them, so minting many session tokens does not increase your quota.

Different kinds of operations have different limits:

TierApplies toLimit
Read GET requests 300 requests per minute
Write POST, PUT, PATCH, DELETE requests 60 requests per minute
Search Search endpoints (paths containing /search) 30 requests per minute
Bulk Batch and bulk endpoints (paths containing /batch or /bulk) 10 requests per minute
Export Export endpoints (paths containing /export) 5 requests per 5 minutes

In addition, an IP-based limit of 600 requests per minute applies before authentication, and authentication endpoints (sign-up, password reset) have stricter per-IP limits to protect against abuse.

Response Headers

Authenticated API responses include rate-limit headers so you can monitor your usage in real time:

HeaderDescriptionExample
X-RateLimit-Limit Maximum requests allowed in the current window for this tier. 300
X-RateLimit-Remaining Requests remaining in the current window. 287
X-RateLimit-Reset Unix timestamp (seconds) when the current window resets. 1709726520

Reading Headers

const response = await fetch(
  'https://api.simply360.app/v1/data-records?dataCollectionSimplyId=XXXX-XXXX-XXXX',
  { headers: { Authorization: `Bearer ${apiKey}` } },
);

const limit = response.headers.get('X-RateLimit-Limit');
const remaining = response.headers.get('X-RateLimit-Remaining');
const reset = response.headers.get('X-RateLimit-Reset');

console.log(`${remaining}/${limit} requests remaining, resets at ${new Date(Number(reset) * 1000).toISOString()}`);

Handling 429 Responses

When you exceed a rate limit, the API returns 429 Too Many Requests with the standard error envelope. Use the X-RateLimit-Reset header to determine when the window resets.

{
  "error": {
    "code": "RATE_LIMIT_EXCEEDED",
    "message": "Rate limit exceeded. Maximum 300 requests per 60 seconds."
  },
  "meta": { "requestId": "req_abc123" }
}

Exponential Backoff with Jitter

The recommended strategy for handling rate limits is exponential backoff with jitter. This prevents thundering-herd retry storms when many clients are limited at once. The Simply360 SDK does not retry automatically — implement the strategy in your own code:

async function fetchWithBackoff(
  url: string,
  options: RequestInit,
  maxRetries = 3,
): Promise<Response> {
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
    const response = await fetch(url, options);
    if (response.status !== 429) return response;
    if (attempt === maxRetries) throw new Error(`Rate limited after ${maxRetries} retries`);

    // Prefer the reset timestamp when provided; fall back to exponential delay.
    const resetHeader = response.headers.get('X-RateLimit-Reset');
    const resetDelayMs = resetHeader ? Number(resetHeader) * 1000 - Date.now() : NaN;
    const baseDelay = Number.isFinite(resetDelayMs) && resetDelayMs > 0
      ? resetDelayMs
      : Math.pow(2, attempt) * 1000;
    const jitter = Math.random() * baseDelay * 0.5;
    await new Promise((resolve) => setTimeout(resolve, baseDelay + jitter));
  }
  throw new Error('Unreachable');
}

If you are using the SDK, catch the ApiError with statusCode === 429 and apply the same logic before retrying the SDK call. See Error Handling for the full error model.

Best Practices

Monitor Your Usage

Watch X-RateLimit-Remaining on every response. If it drops faster than expected, look for unnecessary calls in your code.

Use the Largest Reasonable Page Size

List endpoints support limit up to 100. Fewer requests — lower bill against your quota. See Pagination, Sorting & Field Selection.

// Efficient: 100 records per page, advancing manually with offset.
let offset = 0;
const limit = 100;
while (true) {
  const response = await s360.dataRecords.list({ dataCollectionSimplyId, limit, offset });
  // process response.data...
  if (!response.meta.pagination.hasMore) break;
  offset += limit;
}

Use Batch Endpoints for Bulk Writes

When creating or updating many records, use the batch endpoints instead of individual calls. Batch endpoints are limited to 10 requests per minute, but each request can carry up to 100 records — up to 1,000 record writes per minute, versus 60 individual write calls:

await s360.dataRecords.batchCreate({
  dataCollectionSimplyId: 'XXXX-XXXX-XXXX',
  records: items.map((item) => ({
    fields: { 'FLDS-NAME': item.name, 'FLDS-EMAL': item.email },
  })),
});

Cache Responses

Cache read responses on your side when the data doesn't change frequently. Collection metadata, field definitions, and other configuration data are good candidates.

Spread Requests Over Time

For batch jobs, spread requests evenly rather than bursting:

async function processInBatches<T>(
  items: T[],
  processFn: (item: T) => Promise<void>,
  delayMs = 200,
): Promise<void> {
  for (const item of items) {
    await processFn(item);
    await new Promise((resolve) => setTimeout(resolve, delayMs));
  }
}

Use Field Selection

Request only the fields you need with the fields query parameter. Smaller responses are faster to process and reduce server work, helping you stay within limits.

Error Reference

HTTP StatusError CodeDescription
429 RATE_LIMIT_EXCEEDED You've exceeded the quota for this tier. Wait until the window resets (see X-RateLimit-Reset) before retrying.