Errors

The error envelope, HTTP status codes, the full error code catalog, and safe retry patterns

The Invitebase API uses conventional HTTP status codes and returns a consistent JSON error envelope on every failure. Errors are designed to be actionable: the type tells you the class of problem, the code tells you the exact problem, and param points at the offending field.

The error envelope

Every non-2xx response has this shape:

{
  "error": {
    "type": "invalid_request_error",
    "code": "parameter_missing",
    "message": "Missing required parameter: referred_user_id.",
    "param": "referred_user_id",
    "doc_url": "https://docs.invitebase.com/implementation/platform/errors#parameter_missing"
  }
}
FieldDescription
typeOne of invalid_request_error, authentication_error, rate_limit_error, api_error. Always present.
messageHuman-readable explanation. Log it; do not parse it — branch on type and code.
codeMachine-readable identifier from the catalog below.
paramThe request parameter the error relates to, when applicable.
doc_urlLink to the relevant docs section.

HTTP status codes

StatusMeaningError type
200Request succeeded.
201Resource created.
202Accepted for async processing (event ingestion).
400Malformed or invalid request — bad JSON, missing or invalid parameters, invalid qualification gate.invalid_request_error
401Missing, invalid, or revoked API key.authentication_error
402The action needs funds your prefunded balance does not have.invalid_request_error
403The key is valid but not allowed to do this — publishable key on a secret-only endpoint, or wrong mode.authentication_error
404The resource does not exist — or exists in the other mode or another org.invalid_request_error
409Conflict — usually a concurrent retry with the same Idempotency-Key still in flight.invalid_request_error
422The request is well-formed but semantically unusable — e.g. an idempotency key reused with a different payload.invalid_request_error
429Rate limit exceeded. See Rate limits.rate_limit_error
5xxSomething failed on Invitebase's side. Rare; safe to retry with backoff.api_error

404 is deliberately indistinguishable between "never existed", "wrong mode", and "different org" — the API never confirms the existence of another tenant's objects.

Error code catalog

CodeStatusMeaningRetry?
parameter_missing400A required parameter was not provided (param names it).No — fix the request.
parameter_invalid400A parameter has the wrong type, format, or an out-of-range value.No — fix the request.
api_key_missing401No Authorization or X-Publishable-Key header.No.
api_key_invalid401The key does not exist or is malformed.No.
api_key_revoked401The key was revoked or its roll grace period ended.No — use the replacement key.
key_type_not_allowed403A publishable key called a secret-only endpoint.No — call from your server with a secret key.
mode_mismatch403The request references an object from the other mode (e.g. a test campaign with a live key).No.
resource_missing404No such object in this org and mode.No.
balance_insufficient402The prefunded balance cannot cover the configured payout. Payouts queue rather than fail; top up to resume.Yes — after topping up.
idempotency_key_in_use409A request with this Idempotency-Key is still being processed.Yes — after a short delay, same key.
idempotency_payload_mismatch422This Idempotency-Key was already used with a different request body.No — use a new key for a new request.
rate_limit_exceeded429Too many requests for this key.Yes — honor Retry-After, back off.
internal_error500Unexpected failure on Invitebase's side.Yes — with backoff and the same Idempotency-Key.
service_unavailable503Temporary unavailability (deploys, upstream issues).Yes — with backoff and the same Idempotency-Key.

Retrying safely

Two rules make retries safe:

  1. Only retry retryable errors: 429, 5xx, network failures/timeouts, and 409 idempotency_key_in_use. Retrying a 400 will fail forever.
  2. Reuse the same Idempotency-Key on every attempt of the same logical request, so a retry of a request that actually succeeded returns the original result instead of creating a duplicate.

Every POST endpoint accepts the Idempotency-Key header (any unique string up to 255 characters): the first request with a given key is processed normally and its response recorded, and any later request with the same key replays that response without re-executing anything. Generate the key once, before the first attempt, and reuse it on every retry — a fresh key per attempt defeats the mechanism. Keys are scoped per endpoint, per organization, per mode, and retained for 24 hours, so keep your retry horizon inside that window; reusing a key with a different body returns 422 idempotency_payload_mismatch (see the table above). The SDKs manage keys automatically for the events they send — you only handle them on direct API calls.

retry.ts
import { randomUUID } from "node:crypto";

async function postWithRetry(path: string, body: unknown, maxAttempts = 5) {
  const idempotencyKey = randomUUID(); // one key for all attempts

  for (let attempt = 1; ; attempt++) {
    const res = await fetch(`https://api.invitebase.com${path}`, {
      method: "POST",
      headers: {
        Authorization: `Bearer ${process.env.INVITEBASE_SECRET_KEY}`,
        "Content-Type": "application/json",
        "Idempotency-Key": idempotencyKey,
      },
      body: JSON.stringify(body),
    }).catch(() => null); // network error → retryable

    if (res && res.status < 400) return res.json();

    const retryable =
      !res || res.status === 429 || res.status === 409 || res.status >= 500;
    if (!retryable || attempt >= maxAttempts) {
      throw new Error(`Invitebase request failed: ${res?.status ?? "network"}`);
    }

    const retryAfter = Number(res?.headers.get("Retry-After")) || 0;
    const backoff = Math.min(2 ** attempt * 500, 30_000); // 1s, 2s, 4s… cap 30s
    const jitter = Math.random() * 250;
    await new Promise((r) => setTimeout(r, Math.max(retryAfter * 1000, backoff) + jitter));
  }
}

await postWithRetry("/v1/events", {
  name: "subscription_started",
  referred_user_id: "usr_123",
  properties: { plan: "paid" },
});

Errors in test mode

Test and live mode share the same error surface — a request that returns qualification_gate_invalid in test mode returns it in live mode too. Build your error handling against test keys and it carries over unchanged. Every API error is also visible in the dashboard's API log under Developer tools, with the full request and response.

What's next

On this page