Every SDK failure extends SteamApiException, so one catch handles them all:

SteamApiException (root, extends RuntimeException)
├── ApiKeyNotConfiguredException No key on the config, and the request needs one.
├── AppNewsUnavailableException No news published for the app, or unknown app.
├── AppNotFoundException No Steam app carries the given app ID.
├── AppVersionUnavailableException No server version to check against, or unknown app.
├── InvalidApiKeyException Steam refused the key: wrong, revoked, or absent.
├── InvalidRateLimitException Two rate limits share a name, or a 429 cool-down under 1 s.
├── InvalidRetryException Fewer than one try, or a negative retry interval.
├── InvalidServerAddressException Address is neither IPv4 nor IPv4:queryport.
├── InvalidSteamIdException Malformed SteamID64.
├── InvalidTimeoutException Negative timeout handed to SteamConfig.
├── ProfileNotPublicException Profile, games list, groups, or stats are private.
├── StatsUnavailableException Stats withheld: none exist, unknown app, or private profile.
├── SteamConnectionException Steam never answered: timeout, DNS, TLS, refused connection.
├── SteamRateLimitException A local limit ran out, or Steam answered 429; exposes the Limit.
├── SteamUserNotFoundException Vanity URL unresolved or profile missing.
└── TooManySteamIdsException More than 100 IDs in one request; summaries() and bans() split them.

Catch the leaf you care about, or the root to handle every SDK failure uniformly:

use Fkrzski\SteamApiSdk\Exceptions\SteamApiException;
try {
$summaries = $connector->users()->summaries([$id]);
} catch (SteamApiException $e) {
// Every SDK failure lands here — auth, privacy, quota, transport.
$logger->error($e->getMessage(), ['status' => $e->getCode()]);
$summaries = [];
}

The API reference notes which request throws which exception.

Exceptions raised from Steam’s answer carry the response that produced them, and their code is the HTTP status, 200 included when only the payload reports the failure:

use Fkrzski\SteamApiSdk\Exceptions\SteamApiException;
try {
$summaries = $connector->users()->summaries([$id]);
} catch (SteamApiException $e) {
$e->getCode(); // 403
$e->response?->body(); // raw Steam payload, or null when no response came back
}

Whenever response is set, and on SteamConnectionException, the message opens with the Steam method called: GetPlayerSummaries: Steam API request failed with HTTP 500. Only that prefix is kept between releases, so match on the class rather than the wording.

A status the SDK gives no meaning of its own surfaces as the root SteamApiException.

No SDK exception chains Saloon’s or Guzzle’s, so getPrevious() is null: Guzzle 7 quotes the request URI, API key included, in its messages, and error trackers record the whole chain. A connection failure keeps the HTTP client’s wording in its message with the key masked as key=***, and SteamConfig marks the key #[\SensitiveParameter], which keeps it out of stack-trace arguments. response still reaches it through the request it answered.

Steam signals failure in two different ways, and the SDK reads both, along with the request Steam never answers at all:

Steam’s answer Exception
400 + HTML naming the key parameter InvalidApiKeyException (missing)
401 or 403 + HTML naming the key parameter InvalidApiKeyException (rejected)
403 + JSON, on GetSupportedAPIList InvalidApiKeyException (rejected)
400 or 403 + JSON, on ISteamUserStats StatsUnavailableException / ProfileNotPublicException / AppNotFoundException
403 + {}, on GetNewsForApp AppNewsUnavailableException
401 or 403 not naming the key parameter ProfileNotPublicException
404 + JSON, on ISteamUserStats AppNotFoundException
500 + JSON Failed to get appinfo, on GetSDRConfig AppNotFoundException, never retried
429 SteamRateLimitException, then a cool-down
any other 4xx / 5xx SteamApiException, a 5xx once the retries are spent
200 with an empty, success: 42 or success: false payload ProfileNotPublicException / SteamUserNotFoundException / AppVersionUnavailableException / InvalidServerAddressException
200 with success: false and a message the SDK does not recognise SteamApiException, quoting Steam’s message
no answer: a timeout, a DNS or TLS failure, a refused connection SteamConnectionException with code 0, once the retries are spent

ApiKeyNotConfiguredException has no row here: it is raised locally, so a request that needs a key the config does not carry never reaches Steam at all.

Steam overloads 400 across unrelated causes. Key errors are the one case it answers with HTML rather than JSON — that is what keeps a misconfigured key from surfacing as a data problem. Only GetSupportedAPIList refuses a key with JSON, so its request reads its own 403. Which status a key error lands on is the endpoint’s choice, not the SDK’s: ISteamUser reports an absent key as 400, GetBadges and GetSteamLevel answer 403, and GetCommunityBadgeProgress answers 401. Only ISteamUser separates an absent key from a rejected one, so everywhere else it surfaces as rejected. A JSON 400 on ISteamUserStats is endpoint-specific too: GetSchemaForGame is the one endpoint whose 400 means nothing but an app ID Steam does not know, so it is the one that raises AppNotFoundException from that status.