> ## Documentation Index
> Fetch the complete documentation index at: https://docs.gavai.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Error handling

> Catch, classify, and retry SDK errors without swallowing the wrong ones.

export const ErrorTable = ({errors}) => <div className="gv-error-table" role="table" aria-label="Errors and fixes">
    <div className="gv-error-table__head" role="row">
      <span className="gv-error-table__col-head" role="columnheader">Error</span>
      <span className="gv-error-table__col-head" role="columnheader">Cause</span>
      <span className="gv-error-table__col-head" role="columnheader">Fix</span>
    </div>
    {errors.map((err, i) => <div className="gv-error-table__row" role="row" key={i}>
        <code className="gv-error-table__code" role="cell">{err.code}</code>
        <div className="gv-error-table__cause" role="cell">{err.cause}</div>
        <div className="gv-error-table__fix" role="cell">{err.fix}</div>
      </div>)}
  </div>;

export const Def = ({term, type, defaultValue, required, children}) => <>
    <dt className="gv-def-list__term">
      <span className="gv-def-list__name">{term}</span>
      {type && <span className="gv-def-list__type">{type}</span>}
      {required && <span className="gv-def-list__required">required</span>}
      {defaultValue !== undefined && <span className="gv-def-list__default">
          default <code>{defaultValue}</code>
        </span>}
    </dt>
    <dd className="gv-def-list__description">{children}</dd>
  </>;

export const DefList = ({children}) => <dl className="gv-def-list">{children}</dl>;

export const BrandCard = ({eyebrow, title, href, icon, badge, cta, featured = false, horizontal = false, children}) => {
  const classes = ["gv-card", featured && "gv-card--featured", horizontal && "gv-card--horizontal"].filter(Boolean).join(" ");
  const inner = <>
      {icon && <span className="gv-card__icon" aria-hidden="true">
          <Icon icon={icon} />
        </span>}
      <div className="gv-card__main">
        {(eyebrow || badge) && <div className="gv-card__meta">
            {eyebrow && <Eyebrow label={eyebrow} tone="muted" />}
            {badge && <span className="gv-card__badge">{badge}</span>}
          </div>}
        <h3 className="gv-card__title">{title}</h3>
        {children && <div className="gv-card__body">{children}</div>}
        {cta && <span className="gv-card__cta">
            {cta}
            <svg className="gv-card__cta-arrow" viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
              <line x1="7" y1="17" x2="17" y2="7" />
              <polyline points="7 7 17 7 17 17" />
            </svg>
          </span>}
      </div>
    </>;
  return href ? <a className={classes} href={href}>
      {inner}
    </a> : <div className={classes}>{inner}</div>;
};

export const NumberedStep = ({n, title, children}) => <li className="gv-step">
    <span className="gv-step__index">{n}</span>
    <div className="gv-step__body">
      <h3 className="gv-step__title">{title}</h3>
      <div className="gv-step__content">{children}</div>
    </div>
  </li>;

export const NumberedSteps = ({children}) => <ol className="gv-steps">{children}</ol>;

export const ArrowItem = ({children}) => <li className="gv-arrow-item">
    <span className="gv-arrow-item__arrow" aria-hidden="true">{"↳︎"}</span>
    <span className="gv-arrow-item__content">{children}</span>
  </li>;

export const ArrowList = ({children}) => <ul className="gv-arrow-list">{children}</ul>;

export const Eyebrow = ({label, tone = "default"}) => <div className={`gv-eyebrow gv-eyebrow--${tone}`}>
    <span className="gv-eyebrow__bar" aria-hidden="true" />
    <span className="gv-eyebrow__label">{label}</span>
  </div>;

<Eyebrow label="SDK" />

Both SDKs throw typed errors — never plain `Error` instances. This page is a recipe collection: pattern-match with `instanceof` to handle a specific failure mode, retry the ones that are safe to retry, and surface the rest. By the end you will have a catch block for every error class both SDKs can produce.

## Recipe: catching errors from `@gavai/sdk`

The high-level client throws `GavaiError` for API-level failures and `GavaiNetworkError` for transport failures (DNS, connection refused, timeout). `GavaiNetworkError` does **not** extend `GavaiError` — check for it separately.

`GavaiError` carries:

<DefList>
  <Def term="status" type="number">HTTP status code.</Def>
  <Def term="code" type="string">Machine-readable string (see common codes below).</Def>
  <Def term="message" type="string">Human-readable description.</Def>
  <Def term="details" type="object">Optional structured payload (e.g. validation field errors).</Def>
  <Def term="headers" type="Headers">The full response headers object.</Def>
</DefList>

Common `code` values you'll see in production:

<ErrorTable
  errors={[
{ code: "token_expired", cause: "Bearer token past its `exp` claim.", fix: "Refresh the token via your OAuth flow and retry." },
{ code: "insufficient_scope", cause: "Token does not carry the scope this endpoint requires.", fix: "Mint a fresh key with the right scope in Settings → API keys." },
{ code: "rate_limited", cause: "Exceeded the per-token rate limit.", fix: "Back off using the `Retry-After` header. Implement exponential backoff." },
{ code: "workspace_forbidden", cause: "Token is for a different workspace than the path requested.", fix: "Confirm the workspace slug. Re-mint the token under the right workspace." },
{ code: "not_found", cause: "Resource ID doesn't exist or isn't visible to this token.", fix: "Verify the ID. Confirm the active workspace." },
]}
/>

```ts catch-public.ts theme={null}
import { Gavai, GavaiError, GavaiNetworkError } from "@gavai/sdk";

const gv = new Gavai({ apiKey: process.env.GAVAI_API_KEY });

try {
  await gv.pages.publish({ workspace: "acme" });
} catch (e) {
  if (e instanceof GavaiError && e.code === "token_expired") {
    // Refresh the token and retry the call
    return;
  }
  if (e instanceof GavaiNetworkError) {
    // Transient connectivity failure — safe to retry with backoff
    return;
  }
  throw e;
}
```

## Recipe: catching errors from `@gavai/sdk-typescript`

The low-level client uses a five-class hierarchy. Every class extends `GavaiError`.

<DefList>
  <Def term="GavaiError" type="class">Base class. Thrown for any unmapped envelope error code.</Def>
  <Def term="GavaiSignatureError" type="class">Response signature did not verify, or the server omitted the `X-Gavai-Response-Signature` header.</Def>
  <Def term="GavaiRateLimitError" type="class">Envelope `error.code === "RATE_LIMITED"`. Carries `retryAfterSec`.</Def>
  <Def term="GavaiValidationError" type="class">Envelope `error.code === "VALIDATION_FAILED"`. Carries `fields`.</Def>
  <Def term="GavaiProtocolError" type="class">Envelope shape did not match. Usually indicates a version skew between client and server.</Def>
</DefList>

```ts catch-admin.ts theme={null}
import {
  GavaiRateLimitError,
  GavaiSignatureError,
} from "@gavai/sdk-typescript/errors";

try {
  await client.workspaces.me();
} catch (err) {
  if (err instanceof GavaiRateLimitError) {
    await sleep(err.retryAfterSec * 1000);
    // retry...
  }
  if (err instanceof GavaiSignatureError) {
    // Never retry — the response could not be authenticated
    throw err;
  }
  throw err;
}
```

## Recipe: retry safely

<NumberedSteps>
  <NumberedStep n="01" title="Decide what is retryable">
    Retry `rate_limited` codes and HTTP 5xx responses. Do **not** retry `token_expired` (refresh first), `insufficient_scope` (fix your token's scopes), `GavaiSignatureError` (the response is untrusted), or any 4xx other than 429.
  </NumberedStep>

  <NumberedStep n="02" title="Back off with jitter">
    Exponential backoff — start at 1 s, double on each attempt, cap at 30 s. Add random jitter of ±25% so concurrent clients do not retry in lockstep. When a `GavaiRateLimitError` carries `retryAfterSec`, treat that value as your floor.
  </NumberedStep>

  <NumberedStep n="03" title="Use an idempotency key on writes">
    Pass an `Idempotency-Key` on any mutating request before you start the retry loop. That way a retry after a network drop does not duplicate the operation. See [Idempotency](/api-reference/idempotency) for key format and caching window.
  </NumberedStep>

  <NumberedStep n="04" title="Cap attempts">
    Three to five attempts is a sensible default. After the cap, surface the error to your caller rather than looping indefinitely.
  </NumberedStep>
</NumberedSteps>

## Recipe: log without leaking secrets

When you log an error, include at minimum:

<ArrowList>
  <ArrowItem>`e.code` — lets you filter by error class in your aggregator</ArrowItem>
  <ArrowItem>`e.status` — the HTTP status code</ArrowItem>
  <ArrowItem>`e.headers["x-request-id"]` — the platform-assigned request ID, required when filing a support ticket</ArrowItem>
</ArrowList>

Do **not** log the full request headers or the `Authorization` header — they contain your API key. If you log `e.details`, scan it first for fields that might echo caller-supplied secrets.

## Next steps

<Columns cols={2}>
  <BrandCard title="Examples" href="/sdks/examples">
    Complete retry and error-handling patterns in context.
  </BrandCard>

  <BrandCard title="Idempotency" href="/api-reference/idempotency">
    Key format, caching window, and which methods accept an `Idempotency-Key`.
  </BrandCard>
</Columns>
