Skip to content

Error Handling

The client provides specific exception types for different error conditions. All exceptions inherit from APIError, so you can catch broad or narrow depending on your needs.

Exception Hierarchy

Every API error maps to a specific exception class based on the HTTP status code:

Exception HTTP Status Description
APIError - Base class for all API errors
AuthenticationError 401 Invalid or missing API key
AuthorizationError 403 Insufficient permissions
NotFoundError 404 Resource not found
ValidationError 400 Server-side validation failed
RequestValidationError - Client-side validation failed
ConflictError 409 Resource conflict (e.g., duplicate ID)
UnprocessableEntityError 422 Well-formed request that cannot be processed (has .code)
RateLimitError 429 Rate limit exceeded
ServerError 5xx Server-side error

The code Property

APIError exposes a code property that carries a machine-readable string identifying the specific failure. Not every error includes a code; check for None before branching on it.

The identity endpoints set code on every error they return. Known codes:

code Exception Meaning
TOKEN_VERIFICATION_FAILED UnprocessableEntityError The provider rejected the credential; ask the user to sign in again
EMAIL_REQUIRED UnprocessableEntityError A new user cannot be created because the provider sent no email
IDENTITY_ALREADY_LINKED ConflictError The identity belongs to another user or the user already has one
IDENTITY_NOT_LINKED NotFoundError The user has no identity from the provider being unlinked
LAST_IDENTITY ConflictError Cannot unlink the user's only remaining identity
PROVIDER_UNAVAILABLE ServerError The provider could not be reached
from vclient.exceptions import ConflictError, UnprocessableEntityError

try:
    result = await svc.identify(provider="apple", token=token)
except UnprocessableEntityError as e:
    if e.code == "TOKEN_VERIFICATION_FAILED":
        ...  # ask the user to sign in again
    elif e.code == "EMAIL_REQUIRED":
        result = await svc.identify(provider="apple", token=token, email=collected_email)
except ConflictError as e:
    if e.code == "IDENTITY_ALREADY_LINKED":
        ...  # inform the user that this provider account is already in use

Example

Catch specific exceptions to handle different failure modes:

from vclient import companies_service
from vclient.exceptions import (
    APIError,
    AuthenticationError,
    AuthorizationError,
    ConflictError,
    NotFoundError,
    RateLimitError,
    RequestValidationError,
    ServerError,
    UnprocessableEntityError,
    ValidationError,
)

companies = companies_service()

try:
    company = await companies.get("invalid-id")
except NotFoundError:
    print("Company not found")
except AuthorizationError:
    print("You don't have access to this company")
except AuthenticationError:
    print("Invalid API key")
except ValidationError as e:
    print(f"Server validation failed: {e}")
except RequestValidationError as e:
    print(f"Client validation failed: {e}")
except ConflictError:
    print("Resource conflict (check idempotency key)")
except UnprocessableEntityError as e:
    print(f"Cannot process request: {e.code}")
except RateLimitError as e:
    print(f"Rate limited, retry after {e.retry_after}s")
except ServerError:
    print("Server error, try again later")
except APIError as e:
    print(f"API error: {e}")

Tip

Catch APIError as a fallback to handle any unexpected API error. Place it last in your except chain so more specific exceptions are matched first.