Skip to main content
Every non-2xx API response is raised as a typed exception. Import from the package root:
from spekoai import SpekoApiError, SpekoAuthError, SpekoRateLimitError
ExceptionWhenAttributes
SpekoAuthErrorHTTP 401message, status=401, code="AUTH_ERROR"
SpekoRateLimitErrorHTTP 429message, status=429, code="RATE_LIMITED", retry_after: int | None
SpekoApiErrorany other non-2xxmessage, status, code
message and code are parsed from the JSON error body when present ({"error": "...", "code": "..."}); otherwise they fall back to response.text and "UNKNOWN".

Example — targeted handling

import time

from spekoai import Speko, SpekoApiError, SpekoAuthError, SpekoRateLimitError

speko = Speko(api_key="sk_live_...")

try:
    speko.complete(
        messages=[{"role": "user", "content": "Hi"}],
        intent={"language": "en", "vertical": "general"},
    )
except SpekoAuthError:
    raise
except SpekoRateLimitError as err:
    time.sleep(err.retry_after or 1)
except SpekoApiError as err:
    log.exception("speko call failed: %s (%s)", err.code, err.status)
SpekoAuthError and SpekoRateLimitError both inherit from SpekoApiError, so a bare except SpekoApiError catches all three. Always branch from most specific to most general.

Realtime errors

connect_realtime raises the same three exceptions from the initial POST /v1/sessions call. Once the WebSocket is open, transport failures surface as frames with type == "error":
async for frame in session:
    if frame["type"] == "error":
        log.error("realtime: %s%s", frame["code"], frame["message"])
        break