The connector is configured once, through a readonly SteamConfig — everything the SDK
needs at runtime lives on that object.
The connector
Section titled “The connector”SteamConnector targets https://api.steampowered.com and carries your credentials:
-
Keep your Steam Web API key out of the source tree:
.env STEAM_API_KEY=your-steam-web-api-key -
Hand it to a
SteamConfig, and the config to a connector:steam.php use Fkrzski\SteamApiSdk\SteamConfig;use Fkrzski\SteamApiSdk\SteamConnector;$connector = new SteamConnector(new SteamConfig(apiKey: $_ENV['STEAM_API_KEY'],)); -
Reuse that one connector — it carries the key and the daily budget, so building a fresh one per call buys you nothing.
The key is appended to every request as the key query parameter, so you never pass it
per call. The endpoints Steam serves anonymously — GetNumberOfCurrentPlayers,
GetGlobalAchievementPercentagesForApp, GetSDRConfig, GetServersAtAddress,
UpToDateCheck, GetNewsForApp and GetServerInfo — go out with it stripped back off.
GetSupportedAPIList keeps it, to list the methods the key can call. A request of your own
picks the same way, through marker interfaces.
SteamConfig options
Section titled “SteamConfig options”| Option | Type | Default | Description |
|---|---|---|---|
apiKey |
?string |
null |
Your Steam Web API key. Leave it out to reach only the anonymous endpoints. |
rateLimitStore |
?RateLimitStore |
MemoryStore |
Where the request budget, and the lock after a 429, are kept. |
sender |
?Sender |
GuzzleSender |
The HTTP client requests go out through. |
language |
?Language |
null |
Language for endpoints that localise their payload. |
connectTimeout |
?float |
10.0 |
Seconds to wait for a connection to Steam. |
requestTimeout |
?float |
30.0 |
Seconds the whole request may take. |
tries |
int |
1 |
Attempts per request, the first one included. |
retryInterval |
int |
0 |
Milliseconds to pause before a retry. |
exponentialBackoff |
bool |
false |
Double the pause after every retry. |
rateLimits |
?array<Limit> |
100 000 a day with a key | Limits that replace the daily budget; [] meters nothing locally. |
tooManyRequestsCooldown |
int |
60 |
Seconds a 429 without Retry-After refuses requests locally. |
Without an API key
Section titled “Without an API key”A config built without one reaches the anonymous endpoints and GetSupportedAPIList, which
then lists only the methods Steam serves without a key. Every other request raises ApiKeyNotConfiguredException before it leaves your process, naming
the request class that needed the key:
$connector = new SteamConnector(new SteamConfig);
$connector->stats()->currentPlayers(381210); // sent$connector->users()->summaries([$steamId]); // ApiKeyNotConfiguredExceptionDefault language
Section titled “Default language”Steam localises the achievement and stat names it sends back through the l query
parameter. Set it once on the config rather than on every call:
use Fkrzski\SteamApiSdk\Enums\Language;
$connector = new SteamConnector(new SteamConfig( apiKey: $_ENV['STEAM_API_KEY'], language: Language::Polish,));A language passed to a call wins over the config, and endpoints that do not localise anything never receive one:
$connector->stats()->achievements($steamId, 381210); // polish$connector->stats()->achievements($steamId, 381210, Language::English); // englishSteam’s codes are Valve’s own rather than ISO, so the
Language enum transcribes their table.
Timeouts
Section titled “Timeouts”Both are in seconds, and both fall back to Saloon’s own defaults when left out:
$connector = new SteamConnector(new SteamConfig( apiKey: $_ENV['STEAM_API_KEY'], connectTimeout: 3, requestTimeout: 120,));0 waits indefinitely, and a negative value throws InvalidTimeoutException as the config
is built. A request that runs out of either raises SteamConnectionException, like any
other request Steam never answered.
Retries
Section titled “Retries”Every request is sent once until tries says otherwise:
$connector = new SteamConnector(new SteamConfig( apiKey: $_ENV['STEAM_API_KEY'], tries: 3, retryInterval: 500, exponentialBackoff: true,));retryInterval is in milliseconds and exponentialBackoff doubles it after each retry, so
the config above pauses 500 ms, then 1 s. Fewer than one try or a negative interval throws
InvalidRetryException as the config is built.
Only what can change by the next attempt is retried: Steam unreachable, a timeout, a 5xx.
A 4xx is thrown at once, and so is SteamRateLimitException, whether the budget ran out
locally or Steam answered 429. So are the 500 GetSDRConfig answers for an app ID Steam
does not know, as AppNotFoundException, and a failure Steam reports in a 200 payload.
Once the tries are spent, the last attempt’s exception is thrown. sendAsync() and pool()
send every request once, and so do summaries() and bans() with
more than 100 IDs.
Your own sender
Section titled “Your own sender”sender takes any Saloon\Contracts\Sender: one your framework already configures, one
built on another HTTP client, or one GuzzleSender with your middleware, shared by every
connector:
use GuzzleHttp\Middleware;use Psr\Http\Message\RequestInterface;use Saloon\Http\Senders\GuzzleSender;
$sender = new GuzzleSender;$sender->addMiddleware(Middleware::mapRequest( static fn (RequestInterface $request): RequestInterface => $request->withHeader('User-Agent', 'my-app/1.0'),));
$store = new SteamConnector(new SteamConfig(apiKey: $_ENV['STEAM_STORE_KEY'], sender: $sender));$stats = new SteamConnector(new SteamConfig(apiKey: $_ENV['STEAM_STATS_KEY'], sender: $sender));The connector wraps whatever you pass in SteamSender, so a request Steam never answered
still raises SteamConnectionException and is retried,
and a 4xx or 5xx maps the same under send(), sendAsync() and pool().
$connector->sender() returns that wrapper rather than a GuzzleSender, so keep your own
handle to reach Guzzle.
What a sender has to honour
Section titled “What a sender has to honour”A sender built on another client has to:
- read
connect_timeoutandtimeoutfrom$pendingRequest->config(): seconds, set fromconnectTimeoutandrequestTimeout,0for no limit; - send
$pendingRequest->createPsrRequest()as built, the API key in its query; - return a
Responsefrom$pendingRequest->getResponseClass()::fromPsrResponse()for every status, and undersendAsync()resolve it or reject with$response->toException(); - report a request that got no answer as
FatalRequestExceptionor a PSR-18NetworkExceptionInterface. Anything else reaches the caller as it is, without a retry.