Error Reference

The three errors the Ava SDK can throw — APIError, AuthRequiredError, NetworkError — when each is raised, and how to handle them.

Error Reference#

The v4 SDK throws three error classes. All three are exported from the package root:

import { APIError, NetworkError, AuthRequiredError } from "@avaprotocol/sdk-js";

For the auto-generated, per-field reference, see the SDK Reference pages linked under each section.

APIError#

Thrown when the gateway returns a non-2xx response. The aggregator speaks RFC 7807 problem+json (application/problem+json); APIError surfaces every field of the Problem body plus the HTTP status so you can switch on the failure without re-parsing the body.

FieldTypeMeaning
statusnumberHTTP status code (e.g. 400, 404, 409, 500).
code?stringStable machine-readable code (e.g. WORKFLOW_NOT_FOUND, AUTH_INVALID_TOKEN).
title?stringShort human-readable summary (Problem.title) — safe to surface to power users.
requestId?stringPer-request identifier — also returned in the X-Request-ID response header.
problem?v4.ProblemThe full parsed Problem body, when the server returned one.
messagestring (inherited)The Error.message you see in stack traces — defaults to detail, then title, then HTTP {status} (e.g. HTTP 404).

When you'll see it: invalid input (4xx), missing entity (404), conflicting state (409), or upstream failures (5xx). Switch on code for stable handling; code is stable across releases, title and the message text aren't.

Recovery: for 4xx, fix the request. For 5xx, retry with backoff — the SDK does not retry server errors automatically; that's your decision.

import { APIError } from "@avaprotocol/sdk-js";

try {
  await client.workflows.get(id);
} catch (e) {
  if (e instanceof APIError) {
    if (e.code === "WORKFLOW_NOT_FOUND") return null; // expected
    if (e.status >= 500) throw e;                     // retry upstream
    console.error(`API ${e.status} ${e.code}: ${e.title} (req=${e.requestId})`);
  }
  throw e;
}

Full class: APIError →

AuthRequiredError#

Thrown when a call requires an authenticated client but no valid bearer token is on the transport (or the token has expired).

Default message: "Authentication required — call client.auth.exchange() first".

When you'll see it:

  • You called a write or owner-scoped method before exchanging an auth message.
  • The bearer token the gateway minted has expired between calls (default JWT lifetime is 24h).
  • You called client.auth.clear() and then called something other than health.check().

Recovery: re-authenticate, then retry once. The two canonical patterns:

// Production / browser: sign with the user's wallet, then exchange.
import { Client, buildAuthMessage, AuthRequiredError } from "@avaprotocol/sdk-js";

async function ensureAuthed(client: Client, wallet: { signMessage: (m: string) => Promise<string>; address: string }, gatewayVersion: string) {
  const { message } = buildAuthMessage({
    ownerAddress: wallet.address,
    uri: window.location.origin,
    chainId: await provider.getNetwork().then(n => Number(n.chainId)),
    version: gatewayVersion, // from client.health.check()
  });
  const signature = await wallet.signMessage(message);
  await client.auth.exchange({ ownerAddress: wallet.address, signature, message });
}
// Node tooling: convenience wrapper that signs locally with a key.
await client.auth.exchangeWithKey(process.env.PRIVATE_KEY!, {
  uri: "https://app.avaprotocol.org",
  chainId: 8453,
  version: (await client.health.check()).version,
});

Don't hardcode uri, chainId, or version. They're required (no silent defaults) because each one has a real failure mode if it lies — phishing-shaped origin in the wallet popup, JWT routed to the wrong chain, support triage blind to which gateway minted the token. Source them from the runtime: window origin, wallet-reported chain, client.health.check().version.

Full class: AuthRequiredError →

NetworkError#

Thrown when the transport itself fails — fetch rejected, the request was aborted (timeout or caller signal), or a 2xx response body wasn't valid JSON.

FieldTypeMeaning
messagestringfetch <path>: <inner> or Failed to parse JSON response ….
cause?unknownThe original error (e.g. the TypeError fetch threw).

When you'll see it:

  • The aggregator is unreachable (DNS, TCP, TLS — anything that makes fetch throw).
  • The request timed out (the SDK schedules an AbortController from defaultTimeoutMs, default 30s) or the caller-supplied signal aborted.
  • A 2xx response's JSON body failed to parse — usually a transient gateway bug or an intermediate proxy returning a non-JSON page.

Recovery: these are typically transient. Retry with exponential backoff, capped attempts, jitter — and surface a "service unavailable" state once retries are exhausted. Don't loop indefinitely.

import { NetworkError } from "@avaprotocol/sdk-js";

try {
  await client.workflows.list();
} catch (e) {
  if (e instanceof NetworkError) {
    // e.cause is the underlying fetch / parse error if you need it
    console.warn("transport failed:", e.message, e.cause);
  }
  throw e;
}

Full class: NetworkError →

Quick decision tree#

caught an error
  ├── instance of AuthRequiredError?      → re-auth, then retry once
  ├── instance of NetworkError?           → retry with backoff (3–5x cap)
  ├── instance of APIError?
  │     ├── status >= 500?                 → retry with backoff (3–5x cap)
  │     ├── status === 409?                → reconcile state, optionally retry
  │     ├── status === 404?                → surface "not found" to caller
  │     ├── code === "AUTH_INVALID_TOKEN"? → re-auth, then retry once
  │     └── status >= 400 && < 500?        → fix request; do NOT auto-retry
  └── anything else                        → bug — log and rethrow

Reporting#

When filing bugs, include the requestId from APIError and a timestamp — those are the two pieces that let us correlate against aggregator logs. For NetworkError, include message and cause if you have them.