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

# Errors

> The typed error hierarchy you can `catch` for recovery.

Every error thrown by `@vibefollow/sdk` extends `VibeFollowError`. Narrow to a subclass for typed recovery.

| Class                                             | HTTP status | Retryable  | Meaning                                            |
| ------------------------------------------------- | ----------- | ---------- | -------------------------------------------------- |
| [`AuthError`](#autherror)                         | 401 / 403   | No         | API key missing, invalid, or revoked               |
| [`ValidationError`](#validationerror)             | 422         | No         | Request body failed validation; `.field` indicates |
| [`RateLimitError`](#ratelimiterror)               | 429         | Yes (auto) | Rate limited; `.retryAfterMs` from `Retry-After`   |
| [`ServerError`](#servererror)                     | 5xx         | Yes (auto) | Server problem; retried with exponential backoff   |
| [`NetworkError`](#networkerror)                   | 0           | Yes (auto) | DNS, refused, abort, timeout                       |
| [`WebhookSignatureError`](#webhooksignatureerror) | 0           | No         | HMAC mismatch or timestamp outside tolerance       |
| [`VibeFollowError`](#vibefollowerror)             | other 4xx   | No         | Generic — base class for everything above          |

## Worked example

```ts theme={null}
import { AuthError, RateLimitError } from '@vibefollow/sdk';

try {
  await vf.users.signedUp('usr_42');
} catch (err) {
  if (err instanceof AuthError) {
    console.error('check your API key');
  } else if (err instanceof RateLimitError) {
    console.error('still rate limited after retries; backoff', err.retryAfterMs);
  } else {
    throw err;
  }
}
```

<Info>
  Auto-retries (with exponential backoff plus jitter) are applied on `NetworkError`, `ServerError`, and `RateLimitError`. Non-retryable errors throw on the first failure. The retry budget is `maxRetries + 1` total attempts (default 3 total).
</Info>

## Error reference

<AccordionGroup>
  <Accordion title="VibeFollowError — base class" icon="triangle-exclamation">
    Base class. Catch this to handle every SDK error uniformly.

    ```ts theme={null}
    class VibeFollowError extends Error {
      status: number;     // HTTP status or 0 for network/webhook
      code?: string;      // server-emitted error code, when available
    }
    ```

    ```ts theme={null}
    import { VibeFollowError } from '@vibefollow/sdk';

    try {
      await vf.users.signedUp('usr_42');
    } catch (err) {
      if (err instanceof VibeFollowError) {
        logger.error({ status: err.status, code: err.code, message: err.message }, 'vibefollow error');
      }
      throw err;
    }
    ```
  </Accordion>

  <Accordion title="AuthError — 401 / 403" icon="key">
    The API key is missing, malformed, or revoked. Not retryable — this is a configuration problem on your side.

    ```ts theme={null}
    class AuthError extends VibeFollowError {
      status: 401 | 403;
      code?: string;
    }
    ```

    **Recovery example**

    ```ts theme={null}
    import { AuthError } from '@vibefollow/sdk';

    try {
      await vf.users.signedUp('usr_42');
    } catch (err) {
      if (err instanceof AuthError) {
        alerts.fire('vibefollow_auth', err.message);
      } else {
        throw err;
      }
    }
    ```

    Check the env var. If it's set and looks right, the key has probably been revoked — issue a new one in the dashboard.
  </Accordion>

  <Accordion title="ValidationError — 422" icon="circle-exclamation">
    The request body failed validation. `.field` names the offending field when the server is able to pinpoint it.

    ```ts theme={null}
    class ValidationError extends VibeFollowError {
      status: 422;
      field?: string;   // e.g. "external_user_id", "properties.trialDays"
      code?: string;
    }
    ```

    **Recovery example**

    ```ts theme={null}
    import { ValidationError } from '@vibefollow/sdk';

    try {
      await vf.events.track('feature_used', 'usr_42', {});
    } catch (err) {
      if (err instanceof ValidationError) {
        logger.error({ field: err.field, message: err.message }, 'vibefollow validation');
      } else {
        throw err;
      }
    }
    ```

    Fix the request shape. Common causes: required field missing, wrong type, value out of bounds.
  </Accordion>

  <Accordion title="RateLimitError — 429" icon="gauge-high">
    You've hit a rate limit. `.retryAfterMs` carries the server's `Retry-After` (parsed to milliseconds). The SDK retries automatically up to `maxRetries`; the error is only thrown if retries are exhausted.

    ```ts theme={null}
    class RateLimitError extends VibeFollowError {
      status: 429;
      retryAfterMs: number;
      code: 'rate_limited';
    }
    ```

    **Recovery example**

    ```ts theme={null}
    import { RateLimitError } from '@vibefollow/sdk';

    try {
      await vf.users.signedUp('usr_42');
    } catch (err) {
      if (err instanceof RateLimitError) {
        await sleep(err.retryAfterMs);
      } else {
        throw err;
      }
    }
    ```

    Wait `retryAfterMs` and try again, or batch your writes (`vf.events.batch()`).
  </Accordion>

  <Accordion title="ServerError — 5xx" icon="server">
    Vibefollow had a problem. Retryable by definition; surfaced when retries are exhausted.

    ```ts theme={null}
    class ServerError extends VibeFollowError {
      status: 500 | 502 | 503 | 504 | …;
    }
    ```

    **Recovery example**

    ```ts theme={null}
    import { ServerError } from '@vibefollow/sdk';

    try {
      await vf.users.signedUp('usr_42');
    } catch (err) {
      if (err instanceof ServerError) {
        logger.warn({ status: err.status }, 'vibefollow transient error');
      } else {
        throw err;
      }
    }
    ```

    Check [status.vibefollow.com](https://status.vibefollow.com) if you're seeing sustained `ServerError`s. For single failures, the request can be safely retried later — the SDK's `Idempotency-Key` prevents duplicates within a 24-hour window.
  </Accordion>

  <Accordion title="NetworkError — status = 0" icon="signal">
    A network-layer failure: DNS, connection refused, timeout, aborted. Retryable.

    ```ts theme={null}
    class NetworkError extends VibeFollowError {
      status: 0;
      cause?: unknown;  // the underlying error
    }
    ```

    **Recovery example**

    ```ts theme={null}
    import { NetworkError } from '@vibefollow/sdk';

    try {
      await vf.users.signedUp('usr_42');
    } catch (err) {
      if (err instanceof NetworkError) {
        logger.warn({ cause: err.cause }, 'vibefollow network error');
      } else {
        throw err;
      }
    }
    ```

    Retry. The SDK does this automatically up to `maxRetries`; widen the budget if your environment is flaky.
  </Accordion>

  <Accordion title="WebhookSignatureError — status = 0" icon="shield-halved">
    HMAC mismatch, malformed `X-Vibefollow-Signature` header, or timestamp outside the ±5 minute window. Not retryable.

    ```ts theme={null}
    class WebhookSignatureError extends VibeFollowError {
      status: 0;
      code: 'webhook_signature_invalid';
    }
    ```

    **Recovery example**

    ```ts theme={null}
    import { WebhookSignatureError } from '@vibefollow/sdk';

    try {
      const event = vf.webhooks.constructEvent(rawBody, sigHeader, secret);
      // ...
    } catch (err) {
      if (err instanceof WebhookSignatureError) {
        return res.sendStatus(401);
      }
      throw err;
    }
    ```

    Return `401` to Vibefollow. Do **not** retry on your side; if the failure was transient, Vibefollow's retry will pick it up. See [Signature verification](/webhooks/signature-verification).
  </Accordion>
</AccordionGroup>
