Skip to main content

Rate limits

Neuroon enforces rate limiting per endpoint, not per subscription plan. Plan quotas (synced products, monthly searches) are a separate concept from request throttling and are documented elsewhere. See Plan quotas vs rate limit below.

There are two throttling layers: one on /api/plugin/shops/* (authenticated with Shop API Key) and another on /api/widget/* (authenticated with Widget Token). Limits are global per endpoint — they do not vary by plan.

Per-endpoint limits

EndpointLimitWindow
POST /api/plugin/shops/{shopId}/products/sync100 req1 min
GET /api/plugin/shops/me60 req1 min
GET /api/plugin/shops/{shopId}/verification-data20 req1 min
POST /api/plugin/shops/{shopId}/verify5 req5 min
DELETE /api/plugin/shops/{shopId}/verify5 req5 min
/api/widget/search*200 req1 min
/api/widget/suggestions*300 req1 min
/api/widget/compare*10 req1 min
/api/widget/analytics*500 req1 min
Other rate-limited endpoints100 req1 min

Response headers

HeaderWhen emittedMeaning
X-RateLimit-LimitAll rate-limited responses (200 and 429)Total quota for this endpoint
X-RateLimit-RemainingAll rate-limited responsesRemaining requests in the current window
Retry-AfterOnly on 429Seconds to wait before retrying (equals window size)

Successful response:

HTTP/1.1 200 OK
X-RateLimit-Limit: 200
X-RateLimit-Remaining: 187
Content-Type: application/json

429 error body

When the bucket is exhausted:

HTTP/1.1 429 Too Many Requests
X-RateLimit-Limit: 200
X-RateLimit-Remaining: 0
Retry-After: 60
Content-Type: application/json

{
"error": "rate_limit_exceeded",
"message": "Too many requests. Please try again later.",
"retryAfter": 60,
"limit": 200,
"remaining": 0,
"timestamp": "2026-05-06T10:15:00Z"
}

Exponential backoff

Retry only on 429 and 5xx. Honor Retry-After. Recommended pattern: 1s, 2s, 4s, 8s with jitter, max 3-5 attempts.

using Polly;
using Polly.Extensions.Http;

var retryPolicy = HttpPolicyExtensions
  .HandleTransientHttpError()
  .OrResult(msg => (int)msg.StatusCode == 429)
  .WaitAndRetryAsync(
      retryCount: 3,
      sleepDurationProvider: (attempt, response, ctx) =>
      {
          if (response.Result?.Headers.RetryAfter?.Delta is TimeSpan retryAfter)
              return retryAfter;
          return TimeSpan.FromMilliseconds(
              Math.Pow(2, attempt) * 1000 + Random.Shared.Next(0, 250));
      },
      onRetryAsync: (_, _, _, _) => Task.CompletedTask);

var http = new HttpClient(new PolicyHttpMessageHandler(retryPolicy)
            { InnerHandler = new HttpClientHandler() });

Plan quotas vs rate limit

These are two distinct layers. Don't confuse them:

ConceptWhat it controlsWhere to see itGranularity
Rate limit (this page)Per-minute / per-five-minute spikesX-RateLimit-* headers, 429 errorPer endpoint, identical for all callers
Plan quotaTotal monthly volume and catalog capGET /api/plugin/shops/memaxProducts, productsCount, maxSearchesPerMonth, searchesThisMonthPer shop, depends on plan

The plan tier (BASIC, GROWTH, PRO, ENTERPRISE) influences maxProducts and maxSearchesPerMonth returned by /shops/me, not the per-endpoint rate limits, which are global. If your integration needs higher monthly volume, contact support to upgrade plan; per-endpoint rate limits are not raised by plan tier.

Best practices

  • Cache /api/plugin/shops/me for ~5 min on the client like the official WordPress plugin does; the endpoint allows only 60 req/min and productsCount/searchesThisMonth evolve slowly.
  • Don't abuse widget-compare (10/min): cache results or batch comparisons.
  • Sync products in batches of up to 500 (sync endpoint cap) and space out calls; the sync bucket allows 100 req/min, enough for large catalogs if they paginate well.
  • Don't retry 400, 401, 403, 404 or 422 — these are deterministic and won't fix themselves.
  • Honor Retry-After strictly. Ignoring it can lead to abuse and manual block.

Further reading

  • Errors — full envelope of remaining 4xx/5xx errors.
  • Shop API Key — generation, rotation and Origin validation.
  • Widget Token — 24 h TTL and rotation.