Errors
Typed error classes mapped to the `Vendo-Error-Code` header.
All SDK errors inherit from a common base (VendoError in Python/TS, VendoError enum in Swift). Every error carries enough context to act on it without string parsing.
Error code reference
The Vendo-Error-Code response header is the canonical wire-format code for proxy-returned errors. Several codes can map to the same SDK class — binding_missing and connection_revoked both raise NotConnected, for example. Two codes (vendo_only_feature, identity_not_present) are SDK-only: the SDK raises them before any HTTP call and they never appear on the wire.
| Code | Python / TS class | Swift case | Origin | When it's raised |
|---|---|---|---|---|
app_unknown | AuthError | .auth | proxy | The presented vendo_sk_* bearer was not found |
app_revoked | AuthError | .auth | proxy | The app key was revoked by the tenant |
app_expired | AuthError | .auth | proxy | The app key passed its expiry |
binding_missing | NotConnected | .notConnected | proxy | No connection is bound for the requested provider |
connection_revoked | NotConnected | .notConnected | proxy | The bound connection was revoked |
connection_needs_reauth | NeedsReauth | .needsReauth | proxy | The bound OAuth token expired; user must re-authorize |
balance_exhausted | BalanceExhausted | .balanceExhausted | proxy | The tenant's wallet hit zero |
spend_cap_daily | SpendCapExceeded | .spendCapExceeded | proxy | The per-app daily spend cap fired |
spend_cap_monthly | SpendCapExceeded | .spendCapExceeded | proxy | The per-app monthly spend cap fired |
upstream_rate_limited | RateLimited | .rateLimited | proxy | The upstream provider returned 429 |
upstream_error | UpstreamError | .upstream | proxy | The upstream provider returned a non-2xx, non-429 response |
validation_failed | ValidationError | .validation | proxy | Request payload or webhook signature was invalid |
idempotency_conflict | IdempotencyConflict | .idempotencyConflict | proxy | Same idempotency key with a different payload hash |
vendo_only_feature | VendoOnlyFeature | .vendoOnlyFeature | SDK | OSS-mode call hit a surface that requires VENDO_API_KEY |
identity_not_present | IdentityNotPresent | .identityNotPresent | SDK | forRequest called without an X-Vendo-User-JWT header |
Codes the SDK does not recognise raise the base VendoError with the code preserved on err.code.
Catching errors
All error classes live in vendo.errors. They all inherit from vendo.errors.VendoError.
import vendo
from vendo.errors import (
NotConnected,
NeedsReauth,
AuthError,
RateLimited,
BalanceExhausted,
SpendCapExceeded,
VendoOnlyFeature,
VendoError,
)
try:
tok = vendo.token("slack")
except NotConnected as e:
# binding_missing or connection_revoked
url = vendo.connect_url(e.slug, return_to="https://yourapp.com/after-connect")
print(f"Connect Slack first: {url}")
except NeedsReauth as e:
# connection_needs_reauth — user must re-authorize
print(f"Re-authorize: {e.connect_url}")
except AuthError:
# app_unknown / app_revoked / app_expired
print("Invalid or revoked VENDO_API_KEY.")
except RateLimited as e:
# upstream_rate_limited
print(f"Rate limited — retry after {e.retry_after}s")
except BalanceExhausted:
print("Wallet empty — top up at vendo.run")
except SpendCapExceeded as e:
# spend_cap_daily or spend_cap_monthly — inspect e.code
print(f"Spend cap hit ({e.code}); retry after {e.retry_after}s")
except VendoOnlyFeature as e:
print(f"Not available in OSS mode: {e}")
except VendoError as e:
print(f"Unexpected SDK error [{e.code}]: {e}")Useful attributes on VendoError:
| Attribute | Type | Description |
|---|---|---|
code | str | Canonical Vendo-Error-Code value |
status | int | None | HTTP status from the response |
slug | str | None | Provider slug (NotConnected, NeedsReauth) |
connect_url | str | None | OAuth popup URL to fix the issue |
retry_after | int | None | Seconds to wait before retrying (RateLimited, SpendCapExceeded) |
suggested_fix | str | None | Optional human-readable remediation hint |
message | str | Human-readable description |
All error classes are named exports from @vendodev/sdk.
import {
Vendo,
NotConnected,
NeedsReauth,
AuthError,
RateLimited,
BalanceExhausted,
SpendCapExceeded,
VendoOnlyFeature,
VendoError,
} from "@vendodev/sdk";
const vendo = new Vendo();
try {
const tok = await vendo.token("slack");
} catch (e) {
if (e instanceof NotConnected) {
console.log("Connect first:", e.connectUrl);
} else if (e instanceof NeedsReauth) {
console.log("Re-authorize:", e.connectUrl);
} else if (e instanceof AuthError) {
console.log("Invalid VENDO_API_KEY");
} else if (e instanceof RateLimited) {
console.log(`Retry after ${e.retryAfter}s`);
} else if (e instanceof BalanceExhausted) {
console.log("Wallet empty");
} else if (e instanceof SpendCapExceeded) {
console.log(`Spend cap hit (${e.code})`);
} else if (e instanceof VendoOnlyFeature) {
console.log("Set VENDO_API_KEY to use this feature");
} else if (e instanceof VendoError) {
console.log(`SDK error [${e.code}]: ${e.message}`);
} else {
throw e;
}
}Useful properties on VendoError:
| Property | Type | Description |
|---|---|---|
code | string | Canonical Vendo-Error-Code value |
status | number | undefined | HTTP status |
slug | string | undefined | Provider slug |
connectUrl | string | undefined | OAuth popup URL |
retryAfter | number | undefined | Seconds to retry |
message | string | Human-readable description |
All errors are cases of the VendoError enum. Use catch pattern matching.
import Vendo
do {
let token = try await vendo.token("openai")
} catch VendoError.notConnected(let slug, let message) {
print("Connect \(slug) first: \(message)")
} catch VendoError.needsReauth(let slug, let message, let connectURL) {
print("Re-authorize \(slug): \(connectURL?.absoluteString ?? "")")
} catch VendoError.rateLimited(let retryAfter) {
print("Rate limited — retry in \(retryAfter ?? 60)s")
} catch VendoError.balanceExhausted(let message) {
print("Wallet empty: \(message)")
} catch VendoError.spendCapExceeded(let message) {
print("Spend cap hit: \(message)")
} catch VendoError.vendoOnlyFeature(let message) {
print("Vendo-only: \(message)")
} catch VendoError.auth(let message) {
print("Auth error: \(message)")
} catch let e as VendoError {
print("SDK error: \(e.localizedDescription)")
}Testing with mocks
All three SDKs provide a MockClient that can be configured to throw specific errors:
from vendo.testing import MockClient, fake_connection
from vendo.errors import NotConnected
import pytest
mock = MockClient.with_connections([
fake_connection(slug="slack", status="connected",
credential={"access_token": "xoxb-fake"}),
])
assert mock.token("slack") == "xoxb-fake"
with pytest.raises(NotConnected):
mock.token("missing")import { MockClient, fakeConnection } from "@vendodev/sdk";
const mock = MockClient.withConnections([
fakeConnection({ slug: "slack", credential: { access_token: "xoxb-fake" } }),
]);
expect(await mock.token("slack")).toBe("xoxb-fake");
await expect(mock.token("missing")).rejects.toBeInstanceOf(NotConnected);// Use environment variables pointing at vendo dev server
// or inject a custom URLSession mock
let mockToken = "fake-token"
setenv("VENDO_TOKEN_OPENAI", mockToken, 1)
let vendo = try Vendo(apiKey: "byok")
let token = try await vendo.token("openai")
assert(token == mockToken)