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
| Code | HTTP | Cause |
|---|---|---|
not_found | 404 | Referenced resource (plan, reservation, user) does not exist or is not accessible to this app. |
invalid_key | 401 | Missing, malformed, or revoked API key. |
requires_secret_key | 403 | You called a write endpoint with a pk_live_ public key. Use sk_live_. |
limit_reached | 429 | The end-user has hit a plan limit. Show upgrade UI; do not retry. |
workspace_limit_reached | 429 | Your Vevee workspace is over its event quota. Upgrade your plan in the dashboard. |
invalid_request | 400 | Missing required field, wrong type, or value out of range. |
reservation_expired | 400 | Tried to commit or release a reservation past its 60-second TTL. |
reservation_not_pending | 400 | Reservation was already committed or released. Idempotent - safe to ignore. |
already_canceled | 409 | You 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_error | 500 | Server-side issue. Retry with backoff. If persistent, contact support. |
not_implemented | 501 | Endpoint 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:
| Reason | HTTP | Cause |
|---|---|---|
unmatched_event | 200 | The 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_subscription | 200 | The 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_pendingonrelease()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."
}
}