Bytefusedocs

Knowledge Base

Idempotency & retries

The metering layer is built so you can retry safely — you’re charged exactly once per successful attempt, and never for a failure. Here’s how that works and how to lean on it.

The billing guarantees

Every metered call inherits four guarantees (covered in Credits & billing):

  1. Rate-limit first — a throttled request is rejected before any charge.
  2. Debit before work — credits are reserved up front; an insufficient balance fails before processing.
  3. Exactly once per attempt — the debit and usage record are idempotent on a server-generated request ID.
  4. Refund on failure — if the work raises, the reservation is refunded and the usage row records 0 net credits.

Together these mean a failed call — a 4xx, 5xx, or a dropped connection mid-work — costs you nothing.

Retrying a tool call

Because you’re only charged on success, retrying a failed call is safe: a call that errored wasn’t billed, so a retry is a fresh, independently-billed attempt. Back off and retry on transient conditions — 429(respect Retry-After), 503, network timeouts — and don’t retry on deterministic errors like 400, 401, 403, 415 or 422, which will just fail again.

Retry with backoff (JavaScript)
async function callWithRetry(url, options, tries = 3) {
  for (let attempt = 0; attempt < tries; attempt++) {
    const res = await fetch(url, options);
    if (res.ok) return res;
    if (res.status === 429) {
      const wait = Number(res.headers.get("Retry-After") ?? 2 ** attempt);
      await new Promise((r) => setTimeout(r, wait * 1000));
      continue;               // transient — retry
    }
    if (res.status >= 500) { await new Promise((r) => setTimeout(r, 2 ** attempt * 1000)); continue; }
    return res;               // 4xx (except 429) — don't retry
  }
  throw new Error("exhausted retries");
}

One caveat on double-billing

Idempotency is per server-generated request ID, so a network retry of a call that actually succeeded on the server (you just never saw the response) is a new attempt and will be billed again. For expensive calls, prefer catching the timeout and checking your own records before blindly re-sending.

The request ID

Each request gets a server-generated UUID, returned in the request_id error field and the X-Request-ID response header. The API deliberately ignores any inbound X-Request-ID you send — the ID is always minted server-side so a client can’t replay one to get free work. Log it: it’s the fastest way for support to trace a specific call. See Errors.

Webhook idempotency

On the receiving side, webhook deliveries are at-least-once, so you can get the same event twice (a retry, or a manual redelivery). Dedupe on event_id (also sent as the Webhook-Id header) — it’s stable across retries and redeliveries of the same event. Process each event_id once and acknowledge the rest.