SwishXKnowledge Base
Guides

Error handling

Which errors to retry, and which to surface.

Not every error means the same thing, and treating them all as "retry with backoff" will either loop forever on something that will never succeed, or give up on something that would have worked a second later.

Decision table

typeRetry?What to do instead
invalid_request_errorNoFix the request. param tells you which field.
authentication_errorNoCheck the key is current and not revoked.
insufficient_balanceNoAdd funds before retrying — retrying immediately fails the same way.
compliance_errorNo, not as-isThe prompt (or dossier) needs to change — see Compliance & verification.
rate_limit_errorYes, after the reset windowRead X-RateLimit-Reset, wait, then retry.
api_errorYes, with exponential backoffTransient. 2–3 attempts is usually enough.

A minimal retry wrapper

async function createVideo(body, { maxAttempts = 3 } = {}) {
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
    const res = await fetch("https://api.swishx.com/v1/videos", {
      method: "POST",
      headers: {
        Authorization: `Bearer ${process.env.SWISHX_API_KEY}`,
        "Content-Type": "application/json",
        "Idempotency-Key": body.idempotencyKey,
      },
      body: JSON.stringify(body),
    });

    if (res.ok) return res.json();

    const { error } = await res.json();
    const retryable = error.type === "rate_limit_error" || error.type === "api_error";

    if (!retryable || attempt === maxAttempts) {
      throw new Error(`${error.type}: ${error.message}`);
    }

    await new Promise((r) => setTimeout(r, 2 ** attempt * 500));
  }
}

Note the Idempotency-Key — without it, a retry after a request that actually succeeded server-side (but whose response you never received) creates a second, billed generation. With it, the retry returns the original.

Log the code, not just the message

message is for humans and may be reworded over time. Branch on error.code, which is stable — see the full list on the Errors page.

On this page