from getstream.exceptions import StreamApiException, StreamRateLimitException
try:
client.send_message(...)
except StreamRateLimitException as e:
# 429; e.retry_after is a timedelta or None
if e.retry_after:
time.sleep(e.retry_after.total_seconds())
except StreamApiException as e:
if e.status_code == 422:
for field, msg in e.exception_fields.items():
print(f"{field}: {msg}")
if e.unrecoverable:
raiseError handling
The backend SDKs group errors into a small typed hierarchy so you can write narrow catch blocks and access structured fields directly, without parsing response bodies yourself.
Exception classes
| Class | Raised when |
|---|---|
StreamException | Base. Every SDK-emitted exception inherits from it. |
StreamApiException | The server returned a 4xx or 5xx with the canonical error envelope, or returned a non-success body that could not be parsed. |
StreamRateLimitException | The server returned HTTP 429. Subclass of StreamApiException. Adds retryAfter. |
StreamTransportException | A network-layer failure (connection reset, timeout, DNS, TLS) prevented the request from reaching the server. |
StreamTaskException | An async task observed via the task-waiting helper ended with status: "failed". |
StreamRateLimitException extends StreamApiException, so a catch (StreamApiException ...) block matches 429s too. Catch the rate-limit subclass first if you want different handling.
Class names per SDK
The hierarchy is the same across SDKs. Class names follow each language's idiom.
| Concept | Go (sentinel error) | Python / Java / PHP | Ruby | .NET |
|---|---|---|---|---|
| Base | StreamError | StreamException | GetStreamRuby::StreamError | GetStreamException |
| HTTP API error | ErrApiResponse | StreamApiException | GetStreamRuby::ApiError | GetStreamApiException |
| HTTP 429 | ErrRateLimited | StreamRateLimitException | GetStreamRuby::RateLimitError | GetStreamRateLimitException |
| Transport failure | ErrTransport | StreamTransportException | GetStreamRuby::TransportError | GetStreamTransportException |
| Task failure | ErrTaskFailed | StreamTaskException | GetStreamRuby::TaskError | GetStreamTaskException |
In Go, branch on the sentinel with errors.Is(err, getstream.ErrApiResponse) and extract fields by unwrapping to *StreamError via errors.As.
Fields on the API exception
When the server returns a 4xx or 5xx with the standard error envelope, the API exception carries every documented field. Use these directly instead of re-parsing the response body.
| Field | Description |
|---|---|
statusCode | HTTP status code (e.g. 400, 404, 500). |
code | Stream's numeric error code from the envelope. See the API error codes reference. |
message | Human-readable error message. |
exceptionFields | Map of field name to validation message. Populated for 4xx validation errors. Empty otherwise. |
unrecoverable | Whether the server marked this error as unrecoverable. Honor this when composing retry logic. |
rawResponseBody | The exact bytes of the response body, as a string. Useful for logging and diagnostics. |
moreInfo | Optional URL pointing to more documentation about the error. |
details | Optional structured payload with error-specific context. |
Catching API errors
Transport errors
Transport errors are failures that happen before the server can respond: connection refused or reset, request timeout, DNS lookup failure, TLS handshake failure. The SDK catches these at the HTTP-client boundary and re-emits them as a typed transport exception with a categorized errorType. The original error is preserved on the cause chain.
This means callers do not have to catch httpx.RequestError, HttpRequestException, Faraday::Error, GuzzleException, IOException, or net.Error separately. One catch block covers all transport-layer failures.
The errorType enum
| Value | When the SDK uses it |
|---|---|
connection_reset | Connection refused, reset, or closed prematurely. |
timeout | Read, write, or wall-clock deadline exceeded. |
dns_failure | Could not resolve the host. |
tls_handshake_failed | TLS / SSL handshake failed (certificate verify, protocol mismatch, etc.). |
unknown | Could not be classified into one of the above. |
The transport exception always exposes the underlying error via the language-native cause-chain accessor.
Catching transport errors
from getstream.exceptions import StreamTransportException
try:
client.do_something()
except StreamTransportException as e:
# e.error_type is one of connection_reset / timeout / dns_failure / tls_handshake_failed / unknown
# e.__cause__ is the underlying httpx exception
if e.error_type == "timeout":
# back off and try again later
...Distinguishing transport errors from API errors
Transport errors happen when no HTTP response was received. API errors happen when the server responded with a 4xx or 5xx. Catch them separately if you want to react differently:
from getstream.exceptions import StreamApiException, StreamTransportException
try:
client.do_something()
except StreamApiException as e:
# server responded with a 4xx/5xx
handle_api_error(e)
except StreamTransportException as e:
# network or transport-layer failure
handle_transport_error(e)Both transport and API exceptions inherit from the SDK's base exception. Catch that base type if you want to handle all SDK-emitted errors uniformly.
Cause-chain preservation
Every wrapping point preserves the underlying cause via the language-native mechanism. You can always recover the original exception when you need it for diagnostics or instrumentation.
| Language | Accessor |
|---|---|
| Python | exc.__cause__ |
| Go | errors.Unwrap(err) |
| Java | exc.getCause() |
| PHP | $e->getPrevious() |
| Ruby | exc.cause |
| .NET | ex.InnerException |
Migration notes
Python: deprecated StreamAPIException alias
getstream.base.StreamAPIException (capital API) is preserved as a deprecated alias for StreamApiException. Existing except StreamAPIException blocks and isinstance(exc, StreamAPIException) checks keep working, with a one-time DeprecationWarning on import. Switch to the new spelling at your convenience. The alias is slated for removal in the following minor release.
Ruby: deprecated Stream::APIError alias
GetStreamRuby::APIError (capital API) is preserved as a deprecated alias for GetStreamRuby::ApiError. First access emits a one-time Kernel.warn. Slated for removal in v9.0.
PHP: getCode() semantics
StreamApiException::getCode() continues to return the HTTP status code, the pre-existing behavior inherited from \Exception::getCode(). Stream's canonical numeric error code from the response envelope is now exposed via the new getApiErrorCode(): int. Existing callers branching on $e->getCode() === 429 continue to work. Switch to getApiErrorCode() if you need the envelope code instead of the HTTP status.
.NET: per-status subclasses removed
The previously-published GetStreamAuthenticationException, GetStreamValidationException, and GetStreamFeedException are removed. They were never thrown by the SDK, so no existing catch block was reached. If you have defensive catch blocks for those types, replace them with status-code filters on GetStreamApiException.
// before (never matched)
catch (GetStreamAuthenticationException ex) { ... }
// after
catch (GetStreamApiException ex) when (ex.StatusCode is 401 or 403) { ... }