Errors & retries
Every SDK-raised error inherits from dream.DreamError, so a single
except dream.DreamError: catches everything — but the typed
subclasses let you handle specific conditions cleanly.
Taxonomy
| Class | Trigger | status_code | Retryable? |
|---|---|---|---|
dream.AuthError | 401, 403 | 401/403 | no |
dream.InsufficientCreditsError | 402 | 402 | no |
dream.RateLimitError | 429 | 429 | yes (honors Retry-After) |
dream.ModelNotFoundError | 404 on /v1/models/{slug} | 404 | no |
dream.ValidationError | 4xx other (server-side) | 4xx | no |
dream.ServerError | 5xx, transport error | 5xx or None | 502/503/504/transport: yes |
dream.EngineError | 200 OK but engine signaled failure | 200 | no (rare) |
dream.ModelNotActiveError | client-side: wrong handle.predict | n/a | no |
dream.InputValidationError | client-side: bad shape/dtype/path | n/a | no |
InputValidationError and ModelNotActiveError are also
ValueError / RuntimeError so older except ValueError: patterns
still catch them.
Common attributes
class DreamError(Exception): message: str status_code: int | None # HTTP status, None for client-side request_id: str | None # echo from X-DreamEngine-Request-Id body: str | None # raw response body if availableRateLimitError adds retry_after: float | None.
InsufficientCreditsError adds the customer's current balance and
the predicted cost the engine wanted to debit:
class InsufficientCreditsError(DreamError): balance_cents: int # current balance, integer cents requested_cents: int # cost of this predict, integer cents @property def balance_usd(self) -> float: ... # balance_cents / 100 @property def requested_usd(self) -> float: ... # requested_cents / 100The 402 fires before GPU work — you only see this error when the predict didn't actually run. To recover:
try: rollout = model.predict(start_frame=img, actions=acts)except dream.InsufficientCreditsError as e: session = client.billing.topup(amount_usd=25.00) print("Top up here:", session.url)See Billing for the credits + top-up surface.
Idiomatic handling
import time, dream try: rollout = client.models.get("dreamdojo-2b-gr1").predict(...)except dream.AuthError: # Re-authenticate / surface to user raiseexcept dream.RateLimitError as e: # SDK already retried 3x; backoff further or escalate time.sleep(e.retry_after or 5.0)except dream.ValidationError as e: # Server rejected the request shape; you sent something invalid log.error("server validation: %s", e)except dream.DreamError as e: # Catch-all (Auth, RateLimit, ModelNotFound, ServerError, etc.) log.exception("dream-engine error %s: %s", e.status_code, e)Retries
The SDK retries automatically on:
429 Too Many Requests(honorsRetry-After)502 Bad Gateway503 Service Unavailable504 Gateway Timeouthttpx.TransportError(DNS, TLS, conn-refused, etc.)
Defaults — overridable via RetryPolicy(...) on the Client:
dream.RetryPolicy( max_attempts=3, # 1 disables retries base_delay_s=0.25, # → 0.25, 0.5, 1.0, 2.0, 4.0 (capped) max_delay_s=4.0, jitter=True, # ±50%)Errors not in the retryable set (auth, validation, 500, model not-found) raise immediately — no retry, no backoff.
What's never raised
httpx.HTTPStatusError,httpx.RequestError, etc. — the SDK wraps every httpx exception in aDreamErrorsubclass at the transport boundary. If you ever see a raw httpx exception escape, that's a bug; please file it.requests.*— the SDK useshttpx, notrequests.
Server-side error bodies
When the server returns a 4xx/5xx with a JSON body shaped like
{"detail": "..."} (FastAPI default), the SDK extracts that string
into the error's message. If parsing fails, the raw body lands in
error.body.
Future versions may switch to RFC 7807 application/problem+json
bodies; the SDK already prefers problem+json when present and falls
back gracefully.
Testing your error handling
For unit tests, construct errors directly without making a network call:
import dreamerr = dream.RateLimitError("rate limited", retry_after=5.0, status_code=429)assert err.retry_after == 5.0assert isinstance(err, dream.DreamError)The errors are plain dataclasses; no httpx response object required.