lunterodevelopers
GuidesAPI Reference

Errors and rate limits

Know when to fix a request, wait for a rate window or retry a temporary failure, with a bounded retry helper you can adapt.

An integration needs a plan for the requests that do not succeed. Some failures require a different request or credential. Others mean the service is temporarily unavailable or your client needs to wait before trying again.

Luntero uses HTTP status codes, a structured error body and request identifiers to help you make that distinction. Start by keeping the status and response headers available to your client rather than throwing away everything except the JSON body.

Read an error response

API errors use this shape. The request ID below is illustrative.

{
  "error": {
    "code": "unauthorized",
    "message": "A valid Bearer API key is required.",
    "request_id": "example-request-id"
  }
}

Use the HTTP status and error.code to decide what your application should do. The message is useful when diagnosing a problem, but avoid treating its exact wording as a permanent machine-readable contract.

The X-Request-ID response header provides the same kind of correlation identifier. Keep it with the time, endpoint and status when reporting a failed request. Do not include the API key or the full authorization header in that report.

StatusTypical causeAppropriate response
400Invalid or repeated parameters, unsupported values, reversed ranges, an expired cursor or an ambiguous address.Correct the request. For an invalid cursor, restart the search without it.
401Missing, malformed, disabled, expired or revoked key.Check the credential and its runtime configuration.
404Unknown endpoint, missing record or a record no longer publicly available.Check the URL or show an unavailable state.
405Unsupported HTTP method.Use GET for data operations.
413Request URL exceeds the accepted length.Reduce the URL; do not truncate a cursor or credential.
429The key's request allowance, or the shared failed-authentication allowance, has been exceeded.Wait for Retry-After before retrying.
503A required dependency is unavailable or the service is busy.Retry with backoff and a finite attempt limit.

A collection returning 200 with data: [] is a successful search with no matches. It is not the same as a detail endpoint returning 404.

Understand the rate window

Each key has an operator-configured limit, normally 60 requests per 60-second window. The window begins with its first request and is shared across API instances. Rejected requests do not extend its expiry.

Authenticated requests that reach a data endpoint consume this request allowance, including requests that later fail validation or return a missing record. Debugging by repeatedly sending an invalid request can therefore use up the window. The public ping and OpenAPI specification do not consume a key's allowance.

There are no paid tiers, subscriptions or monthly quotas enforced in the current release. The response headers describe the short-term request window, not a monthly balance.

Read the response headers

RateLimit-Limit: 60
RateLimit-Remaining: 0
RateLimit-Reset: 1900000060
Retry-After: 12

These numbers are illustrative. Retry-After is included on a 429 response; the three rate-limit headers are set after valid key authentication when the request reaches the per-key limiter.

HeaderMeaning
RateLimit-LimitThe key's configured maximum for the current window.
RateLimit-RemainingRequests remaining after this attempt, bounded at zero.
RateLimit-ResetThe window reset time as Unix seconds, not milliseconds.
Retry-AfterThe number of seconds to wait before another attempt.

Do not use RateLimit-Remaining as an exact promise about your next request: another worker using the same key may consume capacity first. Coordinate request volume across workers where possible.

Decide what to retry

Retry 429 after the indicated delay and 503 after a backoff delay. A timeout or connection interruption can also be retried for these read-only GET operations, subject to the same finite budget.

Do not automatically retry an unchanged 400 or 401. An expired cursor needs a new first-page request; an invalid key needs a corrected credential. For a 404 after a user opens a search result, return them to a useful state rather than hiding the problem behind repeated retries.

Backoff needs a stopping point

A retry is another request. Limit the number of attempts, add a little random jitter so workers do not retry together, and stop if waiting would exceed your application's response-time budget. For a user-facing search, it can be better to show a retry action than keep the user waiting through a long rate window.

A bounded TypeScript retry helper

This server-side helper makes at most three attempts. It retries only rate limits, temporary service failures and transport errors. A 429 waits at least the supplied number of seconds; permanent HTTP failures are returned immediately for the caller to handle.

export async function getWithRetry(
  url: URL,
  key: string,
): Promise<Response> {
  const sleep = (ms: number) =>
    new Promise<void>((resolve) => setTimeout(resolve, ms));

  for (let attempt = 0; attempt < 3; attempt++) {
    const backoffMs = 500 * 2 ** attempt + Math.random() * 250;
    let response: Response;

    try {
      response = await fetch(url, {
        headers: { Authorization: `Bearer ${key}` },
        signal: AbortSignal.timeout(10_000),
      });
    } catch (error) {
      if (attempt === 2) throw error;
      await sleep(backoffMs);
      continue;
    }

    const retryable = response.status === 429 || response.status === 503;
    if (!retryable || attempt === 2) return response;

    const value = response.headers.get("Retry-After");
    const seconds = value === null ? 0 : Number(value);
    const serverDelayMs = Number.isFinite(seconds)
      ? Math.max(0, seconds) * 1000
      : 0;

    // Consume the small error body before reusing the connection.
    await response.arrayBuffer();
    await sleep(Math.max(backoffMs, serverDelayMs));
  }

  throw new Error("Retry budget exhausted.");
}

The helper returns a Response, including the last unsuccessful response. Its caller must still check response.ok, handle the error body and decide what to show the user. It does not turn an exhausted 429 into a successful result.

The 10-second timeout applies separately to each network attempt; it is not a total deadline for the function. A server-supplied delay can make the whole operation take longer. Add cancellation and a total time budget appropriate to your application if a person is waiting for the result.

Diagnose failures by layer

A public ping is a useful first check when you cannot connect. If it succeeds but a data request returns 401, investigate the header and key rather than the network. If a data request returns 503, the service may be reachable while a required dependency is unavailable.

A browser CORS failure, an HTML response from an intermediary or a connection error may not contain the API's JSON error envelope. Check Content-Type before assuming an unsuccessful response is JSON, and do not display a raw HTML error page inside your own interface.

For browser integrations, keep the Luntero key on your backend. The hosted docs playground has its own allowed origin; copying its request code into an arbitrary frontend does not grant that origin browser access.

Capture useful diagnostics

When a failure needs investigation, record the endpoint path, HTTP status, request ID, approximate time and whether a retry succeeded. If filters matter, share a sanitized version with no credentials or private application data.

Avoid logging the full request headers. A report such as “listing detail returned 503 at this time, request ID …, after three attempts” is more useful than an unstructured dump containing secrets.

On this page