Public API

Errors & rate limits

What a failed request looks like, which status and error codes to expect, and how many requests you can make.

On this page

Error format

A failed request returns a non-2xx status and the error envelope:

json
{
  "success": false,
  "error": {
    "code": "LIMIT_EXCEEDED",
    "message": "Limit exceeded for keys. Maximum allowed: 10000",
    "statusCode": 403,
    "details": { "resource": "keys", "limit": 10000 }
  }
}
FieldMeaning
codeMachine-readable reason. Stable — safe to branch on.
messageHuman-readable explanation. May change wording; show it, don't parse it.
statusCodeSame as the HTTP status.
detailsOptional extra data: invalid fields for validation errors, resource and limit for plan limits.

Status codes

StatusTypical codeWhat to do
400VALIDATION_ERROR, BAD_REQUESTA field is missing or invalid. Fix the request — retrying won't help.
401UNAUTHORIZEDThe API key is missing, unknown, expired or revoked.
403FORBIDDENThe key lacks the scope, the endpoint is closed to API keys, or your role in a shared service doesn't allow it.
403LIMIT_EXCEEDEDYour plan's limit for details.resource is reached. Upgrade or delete something first.
404NOT_FOUND, KEY_NOT_FOUND, SERVICE_NOT_FOUNDThe resource doesn't exist, or you can't see it.
409CONFLICT, SLUG_ALREADY_EXISTSClashes with existing data, e.g. a taken key-page slug.
422KEY_VALIDATION_ERROR, BATCH_COUNT_EXCEEDEDThe request is well-formed but breaks a rule, e.g. hwid together with no_hwid.
429TOO_MANY_REQUESTSRate limit hit. Wait for Retry-After seconds, then retry.
5xxINTERNAL_SERVER_ERROROur side. Retry with exponential backoff.

Rate limits

Two limits apply to every request made with an API key, measured per minute:

LimitApplies toRequests / minute
Per keyEach API key on its own120
Per accountAll your API keys togetherFree 60 · Trial 120 · Premium 300 · Enterprise 900

Whichever runs out first returns 429. Each response carries X-RateLimit-Plan with the plan the account budget was computed for; a 429 from the account budget also carries Retry-After (seconds).

To stay under the limits:

  • Batch. Create up to 100 keys in one request instead of 100.
  • Cache. Services, providers and revenues rarely change — fetch them once, not per order.
  • Spread load. On Premium and Enterprise the account budget is higher than one key's 120/minute, so give each integration its own key.

Retrying safely

Retry?When
Yes, with backoff429, 500, 502, 503, 504, network timeouts.
NoOther 4xx — the same request will fail the same way.

Creating keys is not idempotent: if a POST /api/keys times out, the key may still have been created. Put your order ID in key_name and check List keys with search=<order id> before you retry, so a buyer never gets two keys.

async function santiauth(path, init = {}, attempt = 0) {
  const res = await fetch(`https://api.santiauth.com${path}`, {
    ...init,
    headers: {
      "x-api-key": process.env.SANTIAUTH_API_KEY,
      "Content-Type": "application/json",
      ...init.headers,
    },
  });
  const retryable = res.status === 429 || res.status >= 500;
  if (retryable && attempt < 4) {
    const wait = Number(res.headers.get("Retry-After")) * 1000 || 500 * 2 ** attempt;
    await new Promise((r) => setTimeout(r, wait));
    return santiauth(path, init, attempt + 1);
  }
  const body = await res.json();
  if (!body.success) throw Object.assign(new Error(body.error?.message), { status: res.status, code: body.error?.code });
  return body.data;
}