Errors & status codes

The SDK throws a typed VeveeError for every non-2xx HTTP response. Each error has a machine-readable code, the HTTP status, and a human-readable message.

The error class

import { VeveeError } from '@vevee/sdk';

class VeveeError extends Error {
  readonly code: ErrorCode;
  readonly status: number;
}

Catch pattern

import { VeveeError } from '@vevee/sdk';

try {
  await vevee.track(userId, 'image.render');
} catch (err) {
  if (err instanceof VeveeError) {
    switch (err.code) {
      case 'limit_reached':
        return res.status(429).json({ error: 'Out of credits' });
      case 'invalid_key':
        console.error('Bad API key - check env vars');
        return res.status(500).json({ error: 'Misconfigured' });
      default:
        console.error(err.code, err.message);
        return res.status(err.status).json({ error: err.code });
    }
  }
  throw err;
}

All error codes

CodeHTTPCause
not_found404Referenced resource (plan, reservation, user) does not exist or is not accessible to this app.
invalid_key401Missing, malformed, or revoked API key.
requires_secret_key403You called a write endpoint with a pk_live_ public key. Use sk_live_.
limit_reached429The end-user has hit a plan limit. Show upgrade UI; do not retry.
workspace_limit_reached429Your Vevee workspace is over its event quota. Upgrade your plan in the dashboard.
invalid_request400Missing required field, wrong type, or value out of range.
reservation_expired400Tried to commit or release a reservation past its 60-second TTL.
reservation_not_pending400Reservation was already committed or released. Idempotent - safe to ignore.
already_canceled409You called cancelSubscription on a user whose ends_at has already elapsed. A scheduled cancellation with a future endsAt is still updatable; this only fires after it's in effect.
internal_error500Server-side issue. Retry with backoff. If persistent, contact support.
not_implemented501Endpoint exists but the feature is not yet enabled for your account.

Response reasons (not thrown)

Two reason codes appear in the reasons array of canUse and reserve responses but are never thrown as errors. They signal that the SDK fell back to its fail-closed default:

ReasonHTTPCause
unmatched_event200The event string didn't match any limit group on the user's plan. Likely a typo, or you haven't added a matching limit group yet. The SDK console.warns in development.
no_subscription200The user has no active subscription on this app. Call vevee.upsertSubscription({ userId, planId }) first.

Retry strategy

  • Retry with backoff: internal_error, network timeouts.
  • Do not retry: limit_reached, workspace_limit_reached, invalid_key, requires_secret_key, invalid_request, not_found.
  • Safe to ignore: reservation_not_pending on release() calls - it just means someone got there first.

Wire format

If you call the HTTP API directly (without the SDK), error responses look like:

{
  "ok": false,
  "error": {
    "code": "limit_reached",
    "message": "User has hit the monthly image quota."
  }
}