Skip to content
MantraWorx
Developers › Errors and rate limits

Developers

Errors and rate limits

What each status code means, the default rate limits and how to retry safely.

ReferenceContent reviewed

On this page

Check the HTTP status before interpreting the body. Most failures provide an error and sometimes a message; individual routes can add fields or use a different shape.

Diagnose the response#

StatusMeaningNext action
200, 201Request succeeded.Verify the result; login may still require MFA.
400Invalid request.Check fields and the validation message.
401Missing or invalid authentication.Complete login and MFA.
403Action not allowed in this scope.Check component and record access.
404Route or resource unavailable.Check host, identifier and feature availability.
409Conflict with current state.Resolve the identified conflict before retrying.
429Request limit or authentication protection.Honor the server's retry delay.
503Service or verification unavailable.Retry reads later; retain a valid session.
500Server failure.Check the result before repeating a write; report persistent errors.

For DOCUMENT_CONFLICT, follow the merge procedure. Reloading alone does not preserve unsaved work.

Default limits#

Deployments can configure different values.

LimitScope
2,000 requests per 15 minutesGeneral API, per signed-in user or unauthenticated address.
20 failed sign-ins per 15 minutesLogin, per address and email; successes do not consume this bucket.
120 requests per minuteAI content routes under /api/ai, per user.

Account lockouts, MFA and specialized routes can add limits. Rate-limit responses may include Retry-After; account protections may provide retryAfterSeconds.

Retry reads with a bounded delay#

This same-origin example retries GET requests only, on 429 or 503. It honors a valid Retry-After. For delays above one minute, it returns the response so the caller can schedule a later attempt.

js
async function readWithRetry(url, { signal } = {}) {
  for (let attempt = 0; attempt < 4; attempt++) {
    const response = await fetch(url, {
      method: "GET", credentials: "same-origin", signal,
    });
    if (![429, 503].includes(response.status) || attempt === 3) {
      return response;
    }
    const header = response.headers.get("Retry-After");
    let delay = NaN;
    if (header) {
      delay = /^\d+$/.test(header.trim())
        ? Number(header) * 1000
        : Date.parse(header) - Date.now();
    }
    if (!Number.isFinite(delay)) {
      delay = 500 * 2 ** attempt + Math.random() * 250;
    }
    delay = Math.max(0, delay);
    if (delay > 60_000) return response;
    await response.body?.cancel();
    await new Promise((resolve) => setTimeout(resolve, delay));
    signal?.throwIfAborted();
  }
}

The caller must check the final status. Cancellation prevents the next attempt; an existing wait finishes before the abort check.

Do not blindly retry writes#

For create, approval, payment and other state-changing calls, first determine whether the original action completed. A timeout does not prove failure. Use idempotency only where the endpoint explicitly supports it; an arbitrary header does not create that guarantee.

Report a problem#

Include the endpoint, status, time, expected result and request identifier if supplied. Exclude passwords, session cookies, MFA codes and private record contents from shared logs.

Need more help?

Can’t find what you need? Talk to the team.

To report a problem with this guide, include its title and the step that blocked you.

Contact support