steam_api_client 0.10.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +66 -8
  3. data/README.md +13 -47
  4. data/docs/USAGE.md +326 -0
  5. data/lib/steam_api_client/client.rb +58 -35
  6. data/lib/steam_api_client/configuration.rb +55 -0
  7. data/lib/steam_api_client/endpoint/param.rb +83 -0
  8. data/lib/steam_api_client/endpoint.rb +90 -0
  9. data/lib/steam_api_client/endpoints.rb +122 -0
  10. data/lib/steam_api_client/errors/api_error.rb +10 -8
  11. data/lib/steam_api_client/errors/connection_error.rb +7 -0
  12. data/lib/steam_api_client/errors/response_parse_error.rb +6 -6
  13. data/lib/steam_api_client/failure_handler.rb +1 -6
  14. data/lib/steam_api_client/model/field.rb +42 -0
  15. data/lib/steam_api_client/model/scalars.rb +59 -0
  16. data/lib/steam_api_client/model.rb +102 -0
  17. data/lib/steam_api_client/models/app_details.rb +118 -0
  18. data/lib/steam_api_client/models/friend.rb +11 -0
  19. data/lib/steam_api_client/models/game.rb +35 -0
  20. data/lib/steam_api_client/models/global_achievement.rb +10 -0
  21. data/lib/steam_api_client/models/news_item.rb +20 -0
  22. data/lib/steam_api_client/models/player_achievements.rb +19 -0
  23. data/lib/steam_api_client/models/player_game_stats.rb +25 -0
  24. data/lib/steam_api_client/models/player_summary.rb +37 -0
  25. data/lib/steam_api_client/request.rb +53 -59
  26. data/lib/steam_api_client/requestor.rb +42 -0
  27. data/lib/steam_api_client/response.rb +30 -22
  28. data/lib/steam_api_client/version.rb +1 -1
  29. data/lib/steam_api_client.rb +18 -57
  30. metadata +32 -86
  31. data/lib/steam_api_client/errors/url_error.rb +0 -5
  32. data/lib/steam_api_client/requestors/app_details.rb +0 -27
  33. data/lib/steam_api_client/requestors/app_global_achievements.rb +0 -19
  34. data/lib/steam_api_client/requestors/app_news.rb +0 -23
  35. data/lib/steam_api_client/requestors/base.rb +0 -32
  36. data/lib/steam_api_client/requestors/game_current_players.rb +0 -19
  37. data/lib/steam_api_client/requestors/player_achievements.rb +0 -21
  38. data/lib/steam_api_client/requestors/player_friends.rb +0 -21
  39. data/lib/steam_api_client/requestors/player_game_stats.rb +0 -21
  40. data/lib/steam_api_client/requestors/player_level.rb +0 -18
  41. data/lib/steam_api_client/requestors/player_owned_games.rb +0 -23
  42. data/lib/steam_api_client/requestors/player_recent_games.rb +0 -21
  43. data/lib/steam_api_client/requestors/player_steamid.rb +0 -19
  44. data/lib/steam_api_client/requestors/player_summaries.rb +0 -19
  45. data/lib/steam_api_client/structs/app_details/achievement.rb +0 -12
  46. data/lib/steam_api_client/structs/app_details/category.rb +0 -12
  47. data/lib/steam_api_client/structs/app_details/details.rb +0 -82
  48. data/lib/steam_api_client/structs/app_details/genre.rb +0 -12
  49. data/lib/steam_api_client/structs/app_details/movie.rb +0 -25
  50. data/lib/steam_api_client/structs/app_details/package_group.rb +0 -18
  51. data/lib/steam_api_client/structs/app_details/requirements.rb +0 -12
  52. data/lib/steam_api_client/structs/app_details/screenshot.rb +0 -13
  53. data/lib/steam_api_client/structs/app_details/sub.rb +0 -18
  54. data/lib/steam_api_client/structs/app_global_achievements.rb +0 -18
  55. data/lib/steam_api_client/structs/app_new.rb +0 -20
  56. data/lib/steam_api_client/structs/app_news.rb +0 -13
  57. data/lib/steam_api_client/structs/base.rb +0 -15
  58. data/lib/steam_api_client/structs/game_current_players.rb +0 -12
  59. data/lib/steam_api_client/structs/player_achievements.rb +0 -23
  60. data/lib/steam_api_client/structs/player_friends.rb +0 -17
  61. data/lib/steam_api_client/structs/player_game_stats.rb +0 -28
  62. data/lib/steam_api_client/structs/player_level.rb +0 -11
  63. data/lib/steam_api_client/structs/player_owned_games.rb +0 -31
  64. data/lib/steam_api_client/structs/player_recent_games.rb +0 -22
  65. data/lib/steam_api_client/structs/player_steamid.rb +0 -16
  66. data/lib/steam_api_client/structs/player_summaries.rb +0 -11
  67. data/lib/steam_api_client/structs/player_summary.rb +0 -46
  68. data/lib/steam_api_client/structs/requests/abstract.rb +0 -59
  69. data/lib/steam_api_client/structs/requests/get_app_details.rb +0 -48
  70. data/lib/steam_api_client/structs/requests/get_app_global_achievements.rb +0 -48
  71. data/lib/steam_api_client/structs/requests/get_app_news.rb +0 -56
  72. data/lib/steam_api_client/structs/requests/get_game_current_players.rb +0 -48
  73. data/lib/steam_api_client/structs/requests/get_player_achievements.rb +0 -48
  74. data/lib/steam_api_client/structs/requests/get_player_friends.rb +0 -51
  75. data/lib/steam_api_client/structs/requests/get_player_game_stats.rb +0 -48
  76. data/lib/steam_api_client/structs/requests/get_player_level.rb +0 -42
  77. data/lib/steam_api_client/structs/requests/get_player_owned_games.rb +0 -50
  78. data/lib/steam_api_client/structs/requests/get_player_recent_games.rb +0 -48
  79. data/lib/steam_api_client/structs/requests/get_player_steamid.rb +0 -42
  80. data/lib/steam_api_client/structs/requests/get_player_summaries.rb +0 -46
  81. data/lib/steam_api_client/structs/requests/storefront_abstract.rb +0 -58
  82. data/lib/steam_api_client/structs/types.rb +0 -23
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d0c5680dd7c5f04050858684ffdedba92a405af03105ff67d5ab5f83786a9be3
4
- data.tar.gz: 18dace5a415dbae06ab33d22af1d8d2f89a692c18b33d0fde7632e9608860476
3
+ metadata.gz: d69cc1b3c7d853d973708c6ec1c4eacebb60122b0f338dbbec3eb0766ae63464
4
+ data.tar.gz: 4df0c047832658d3ecbc4505a0308682ab93121c834e44c9515cb63e5e8f17be
5
5
  SHA512:
6
- metadata.gz: '008e3f030129225f200f25063f45bc1df84a0abc0aba381d1c2a919ef99f9cfebba87a908a14ebfc6652a0d9ea9dfdd76e8a7dc5299762df648b03423879dc07'
7
- data.tar.gz: 886f409484facc5b94264c94a488e07d85bcf0514c84b81b9541f395da769d53d9407b8af83b2dc8cfcf45cb8bae81a5044a4d8542074c3940a3cec9ccfa2a71
6
+ metadata.gz: 14c25c60ca63dbc6eee90a07060b0a34d87a61c5d2808a9ac5b12aab5aeb1b2ca769e1f58b2dd638cbb32f4b00740dede05f83534105b80d39599fa9b47eec35
7
+ data.tar.gz: c36ccb50189c59e39be4420bc9419594457fa4383a614506b68770621df445d487a223fa4f7e2450ffd9dc7911a398b29dfb6967f8a8c13e791ff3779d5db9e1
data/CHANGELOG.md CHANGED
@@ -6,6 +6,64 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [1.0.0] - 2026-10-24
10
+
11
+ ### Added
12
+
13
+ - Gem metadata: a description, and links to the source code, changelog, bug tracker and documentation on the RubyGems page (#84).
14
+ - Customize the Faraday connection (middleware, adapter options, test stubs) with a block: `Client.new { |faraday| ... }` per client, or `Client.configure { |c| c.connection { |faraday| ... } }` for every client (#85).
15
+ - [docs/USAGE.md](docs/USAGE.md), shipped with the gem: every endpoint and what it returns, configuration, errors and retries, and Steam's limits. The README links to it (#83).
16
+ - `retrieve_raw` on every endpoint returns Steam's JSON untouched (keys symbolized), for fields the results don't expose (#81).
17
+
18
+ ### Changed
19
+
20
+ - **Breaking:** results are plain Ruby `Data` objects with snake_case fields, unwrapped from Steam's envelopes, and the gem no longer depends on dry-struct: its only dependency is Faraday (8 runtime gems instead of 18) (#81). `SteamApiClient::Structs::*` is removed. What each endpoint returns:
21
+
22
+ | Endpoint | Before | Now |
23
+ |---|---|---|
24
+ | `player_summaries` | `.response.players` | `[PlayerSummary]` (`steam_id`, `persona_name`, `avatar_full`, `country_code`, …) |
25
+ | `player_friends` | `.friendslist.friends` | `[Friend]` (`steam_id`, `relationship`, `friend_since`) |
26
+ | `player_owned_games` | `.response` (`game_count`, `games`) | `OwnedGames` (`game_count`, `games: [Game]`), `nil` for a private library |
27
+ | `player_recent_games` | `.response` (`total_count`, `games`) | `RecentGames` (`total_count`, `games: [Game]`), `nil` when not public |
28
+ | `player_achievements` | `.playerstats` (`steamID`, `gameName`, `achievements`, `success`) | `PlayerAchievements` (`steam_id`, `game_name`, `achievements` with `api_name`, `achieved` as true/false, `unlock_time`) |
29
+ | `player_game_stats` | `.playerstats` | `PlayerGameStats` (`steam_id`, `game_name`, `stats`, `achievements` with `achieved` as true/false) |
30
+ | `player_level` | `.response.player_level` | Integer, `nil` when not visible |
31
+ | `player_steamid` | `.response` (`steamid`, `success`, `message`) | Integer, `nil` when nothing matches |
32
+ | `game_current_players` | `.response.player_count` | Integer |
33
+ | `app_news` | `.appnews.newsitems` | `[NewsItem]` (`feed_label`, `feed_name`, `app_id`, …) |
34
+ | `app_global_achievements` | `.achievementpercentages.achievements` | `[GlobalAchievement]` (`name`, `percent`) |
35
+ | `app_details` | `Structs::AppDetails::Details` (`.response.success`, `.response.data`) | `AppDetails` (`app_id`, `name`, …; parts such as `AppDetails::Price`, `AppDetails::Package`), `nil` when the app is unavailable |
36
+
37
+ Other changes:
38
+ - Field names are snake_case: `steamid` → `steam_id`, `appid` → `app_id`, `personaname` → `persona_name`, `loccountrycode` → `country_code`, `packageid` → `package_id`, and so on. Fields that were already snake_case keep their names.
39
+ - `app_details`: `recommendations` is the review count (Steam's `recommendations.total`), and `movies` drop the legacy `webm` / `mp4` files, which Steam no longer sends. A `[]` sent instead of requirements reads as `nil`.
40
+ - Lists Steam leaves out are `[]`, and data a private profile hides is `nil`.
41
+ - `#to_h` returns nested hashes. Results compare by value and support pattern matching.
42
+ - `ResponseParseError#cause` is a `SteamApiClient::Model::Error` (instead of a `Dry::Struct::Error`). Its message names the field but never the value.
43
+ - `Client.configure` / `Client.config` use a plain Ruby `SteamApiClient::Configuration` instead of dry-configurable, which the gem no longer depends on. The settings and their defaults are unchanged. New: `Client.reset_config!` restores the defaults (handy in specs), `config.to_h`, and `config.inspect` hides the token (#82).
44
+ - Endpoints are declared as data: each is one `SteamApiClient::Endpoint` in `SteamApiClient::Endpoints::ALL`, and `client.<name>` returns a `SteamApiClient::Requestor` for it (#87). Every `retrieve` keeps its keyword arguments, defaults and results. Differences:
45
+ - Arguments are checked with Ruby's own messages (`missing keyword: :steamid`, `unknown keyword: :steam_id`), and invalid values raise `ArgumentError` naming the argument (e.g. `steamid must be an Integer, got "abc"`), instead of a `Dry::Struct::Error`. `nil` is rejected too (before, an empty value was sent to Steam). Numeric strings are still accepted for IDs.
46
+ - **Breaking (internal classes):** `SteamApiClient::Requestors::*`, `SteamApiClient::Structs::Requests::*` and `SteamApiClient::Errors::EndpointKindNotSupported` are removed, and `Request.new` takes `endpoint:, params:, client:`. Code that only uses `client.<endpoint>.retrieve` is unaffected.
47
+
48
+ ### Security
49
+
50
+ - `Client`, `Request` and `Response` no longer show the API token in `inspect` (and `pp`), so consoles, logs and error trackers don't leak it. The token shows as `[REDACTED]`, the `key=` value in URLs too (#80).
51
+
52
+ ## [0.11.0] - 2026-10-23 [YANKED]
53
+
54
+ > Yanked: the published gem was built after the 1.0 changes had merged, so it contained everything listed under 1.0.0, not only the change below. Use 1.0.0 (#100).
55
+
56
+ ### Added
57
+
58
+ - `Client` `adapter` setting (`Client.configure { |c| c.adapter = ... }` or `Client.new(adapter:)`) to choose the Faraday adapter, `:net_http` by default (#35).
59
+
60
+ ### Changed
61
+
62
+ - **Breaking:** the HTTP client is now [Faraday](https://github.com/lostisland/faraday) with its `Net::HTTP` adapter, replacing Typhoeus (#35). The gem no longer installs `typhoeus`, `ethon` or `ffi` (a compiled gem that needs libcurl). The public API (`Client`, every `retrieve`, the `Errors::*` classes) is unchanged, except:
63
+ - Connection failures (DNS, refused connection, TLS) raise the new `SteamApiClient::Errors::ConnectionError`. It is **not** an `ApiError`: before, they surfaced as a generic `ApiError` with status code `0`.
64
+ - `HttpTimeout` for a timed-out request has no HTTP response (`#response` is `nil`), and its message gives the timeouts. A real `408` response also raises `HttpTimeout`.
65
+ - Redirects aren't followed anymore. No Steam endpoint redirects (all were checked).
66
+
9
67
  ## [0.10.0] - 2026-10-23
10
68
 
11
69
  ### Added
@@ -36,13 +94,6 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
36
94
  | `app_global_achievements.retrieve(appid)` | `app_global_achievements.retrieve(appid: appid)` |
37
95
  | `game_current_players.retrieve(appid)` | `game_current_players.retrieve(appid: appid)` |
38
96
 
39
- ### Fixed
40
-
41
- - `player_owned_games` failed on real public libraries: it required `playtime_windows_forever`, `playtime_mac_forever`, `playtime_linux_forever` and `rtime_last_played`, which Steam doesn't send. They're optional now, and `playtime_2weeks`, `has_community_visible_stats`, `has_leaderboards` and `content_descriptorids` are exposed (#70).
42
- - Responses with duplicate JSON keys parse the same on every `json` gem version, with the last value winning. json 3 rejects duplicate keys by default, which would have raised `ResponseParseError` (#19).
43
-
44
- ### Changed
45
-
46
97
  - Allows `dry-configurable` 1.2 and newer (`~> 1.1` instead of `~> 1.1.0`, which only allowed 1.1.x) (#19).
47
98
  - **Breaking:** requires Ruby 3.3 or newer (#17). Ruby 3.0, 3.1 and 3.2 have reached end of life. CI now tests 3.3, 3.4 and 4.0.
48
99
  - **Breaking:** response structs now validate their declared types (#15). Integer, Float and String fields convert Steam's values (Steam often sends numbers as strings), and a value that can't be converted raises `SteamApiClient::Errors::ResponseParseError` instead of passing through unchanged. `null` values still come through as `nil`.
@@ -51,6 +102,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
51
102
  - Global achievement `percent` is now a Float (it held Steam's raw value, e.g. `"49.6"`).
52
103
  - The gem homepage and README links point to `github.com/jpechaves/steam_api` instead of the old `sosolidkk` account (#22).
53
104
 
105
+ ### Fixed
106
+
107
+ - `player_owned_games` failed on real public libraries: it required `playtime_windows_forever`, `playtime_mac_forever`, `playtime_linux_forever` and `rtime_last_played`, which Steam doesn't send. They're optional now, and `playtime_2weeks`, `has_community_visible_stats`, `has_leaderboards` and `content_descriptorids` are exposed (#70).
108
+ - Responses with duplicate JSON keys parse the same on every `json` gem version, with the last value winning. json 3 rejects duplicate keys by default, which would have raised `ResponseParseError` (#19).
109
+
54
110
  ## [0.9.1] - 2026-10-23
55
111
 
56
112
  ### Fixed
@@ -142,7 +198,9 @@ The versions below were bumped in `version.rb` during development but **never pu
142
198
 
143
199
  - Initial gem skeleton.
144
200
 
145
- [Unreleased]: https://github.com/jpechaves/steam_api/compare/v0.10.0...HEAD
201
+ [Unreleased]: https://github.com/jpechaves/steam_api/compare/v1.0.0...HEAD
202
+ [1.0.0]: https://github.com/jpechaves/steam_api/compare/v0.10.0...v1.0.0
203
+ [0.11.0]: https://github.com/jpechaves/steam_api/compare/v0.10.0...v0.11.0
146
204
  [0.10.0]: https://github.com/jpechaves/steam_api/compare/v0.9.1...v0.10.0
147
205
  [0.9.1]: https://github.com/jpechaves/steam_api/compare/v0.9.0...v0.9.1
148
206
  [0.9.0]: https://github.com/jpechaves/steam_api/releases/tag/v0.9.0
data/README.md CHANGED
@@ -4,10 +4,6 @@
4
4
  - [Features](#features)
5
5
  - [Installation](#installation)
6
6
  - [Usage](#usage)
7
- - [Configuring the Client](#configuring-the-client)
8
- - [Example Requests](#example-requests)
9
- - [Get Player Summaries](#get-player-summaries)
10
- - [Get App Details](#get-app-details)
11
7
  - [Development](#development)
12
8
  - [Requirements](#requirements)
13
9
  - [Initial Setup](#initial-setup)
@@ -16,7 +12,6 @@
16
12
  - [Code Quality](#code-quality)
17
13
  - [Releasing](#releasing)
18
14
  - [Contributing](#contributing)
19
- - [How to Contribute](#how-to-contribute)
20
15
  - [License](#license)
21
16
  - [Code of Conduct](#code-of-conduct)
22
17
 
@@ -27,7 +22,7 @@
27
22
 
28
23
  > Previously developed as `steam_api`. It is not related to the [`steam_api`](https://rubygems.org/gems/steam_api) gem on RubyGems.
29
24
 
30
- A Ruby gem for interacting with the [Steam Web API](https://developer.valvesoftware.com/wiki/Steam_Web_API). This gem provides a structured and user-friendly way to fetch various types of data from Steam's API endpoints, such as game details, player information, and achievements.
25
+ A Ruby gem for interacting with the [Steam Web API](https://developer.valvesoftware.com/wiki/Steam_Web_API). It returns plain Ruby objects (`Data`) with snake_case fields, such as game details, player information, and achievements, and its only dependency is Faraday.
31
26
 
32
27
  ## Features
33
28
 
@@ -65,48 +60,25 @@ require "steam_api_client"
65
60
 
66
61
  ## Usage
67
62
 
68
- ### Configuring the Client
69
-
70
- ```ruby
71
- SteamApiClient::Client.configure do |config|
72
- config.api_token = "your-API-token-value"
73
- end
74
- ```
75
-
76
- Alternatively, you can initialize the client directly:
77
-
78
- ```ruby
79
- client = SteamApiClient::Client.new(api_token: "your-API-token-value")
80
- ```
81
-
82
- Requests time out after 10 seconds (5 seconds to connect) and raise `SteamApiClient::Errors::HttpTimeout`. You can change this globally or per client, in seconds:
83
-
84
63
  ```ruby
85
64
  SteamApiClient::Client.configure do |config|
86
- config.timeout = 30
87
- config.connect_timeout = 2
65
+ config.api_token = ENV.fetch("STEAM_API_KEY") # https://steamcommunity.com/dev/apikey
88
66
  end
89
67
 
90
- client = SteamApiClient::Client.new(api_token: "your-API-token-value", timeout: 5)
91
- ```
92
-
93
- ### Example Requests
68
+ client = SteamApiClient::Client.new
94
69
 
95
- #### Get Player Summaries
70
+ player = client.player_summaries.retrieve(steamids: [76561197960435530]).first
71
+ player.persona_name # => "Robin"
96
72
 
97
- ```ruby
98
- client = SteamApiClient::Client.new(api_token: "your-API-token-value")
99
- client.player_summaries.retrieve(steamids: [76561198160009756])
73
+ client.player_level.retrieve(steamid: player.steam_id) # => 32
74
+ client.player_owned_games.retrieve(steamid: player.steam_id) # => OwnedGames, or nil when private
75
+ client.app_details.retrieve(appid: 440, filters: ["basic"]).name # => "Team Fortress 2"
76
+ client.game_current_players.retrieve(appid: 440) # => 88231
100
77
  ```
101
78
 
102
- #### Get App Details
79
+ Results are immutable `Data` objects with snake_case fields (or Integers, or Arrays of them). HTTP errors raise `SteamApiClient::Errors::*` classes, e.g. `HttpTooManyRequests` with `#retry_after`.
103
80
 
104
- ```ruby
105
- client = SteamApiClient::Client.new
106
- client.app_details.retrieve(appid: 1091500, filters: ["basic"])
107
- ```
108
-
109
- For more examples, see the [Usage](#usage) section above.
81
+ **See [docs/USAGE.md](docs/USAGE.md)** for every endpoint and what it returns, configuration (timeouts, adapter, Faraday middleware, logging without leaking the key), errors and retries, and Steam's limits.
110
82
 
111
83
  ## Development
112
84
 
@@ -168,15 +140,9 @@ $ bin/release --dry-run # show what would happen without building, pushing or t
168
140
 
169
141
  ## Contributing
170
142
 
171
- Bug reports and pull requests are welcome on GitHub at [GitHub Repository](https://github.com/jpechaves/steam_api). This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Code of Conduct](https://github.com/jpechaves/steam_api/blob/main/CODE_OF_CONDUCT.md).
172
-
173
- ### How to Contribute
143
+ Bug reports, endpoint requests and pull requests are welcome on [GitHub](https://github.com/jpechaves/steam_api). Every change starts with an issue; see [CONTRIBUTING.md](CONTRIBUTING.md) for the setup, the checks and the CHANGELOG rules. Security problems go through [SECURITY.md](SECURITY.md), not public issues.
174
144
 
175
- 1. Fork the repository.
176
- 2. Create a new branch for your feature or bug fix.
177
- 3. Write tests for your changes.
178
- 4. Run the tests to ensure everything works.
179
- 5. Submit a pull request.
145
+ This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Code of Conduct](CODE_OF_CONDUCT.md).
180
146
 
181
147
  ## License
182
148
 
data/docs/USAGE.md ADDED
@@ -0,0 +1,326 @@
1
+ # Using steam_api_client
2
+
3
+ - [Configuration](#configuration)
4
+ - [Calling an endpoint](#calling-an-endpoint)
5
+ - [Endpoints](#endpoints)
6
+ - [Players](#players): [`player_summaries`](#player_summaries), [`player_steamid`](#player_steamid), [`player_friends`](#player_friends), [`player_level`](#player_level), [`player_owned_games`](#player_owned_games), [`player_recent_games`](#player_recent_games), [`player_achievements`](#player_achievements), [`player_game_stats`](#player_game_stats)
7
+ - [Apps](#apps): [`app_details`](#app_details), [`app_news`](#app_news), [`app_global_achievements`](#app_global_achievements), [`game_current_players`](#game_current_players)
8
+ - [Results](#results)
9
+ - [Errors](#errors)
10
+ - [Limits and caveats](#limits-and-caveats)
11
+
12
+ The values in the examples are real-shaped, but illustrative: Steam's data changes all the time.
13
+
14
+ ## Configuration
15
+
16
+ Most endpoints need a [Steam Web API key](https://steamcommunity.com/dev/apikey). Set it once for every client, or per client:
17
+
18
+ ```ruby
19
+ SteamApiClient::Client.configure do |config|
20
+ config.api_token = ENV.fetch("STEAM_API_KEY")
21
+ end
22
+
23
+ client = SteamApiClient::Client.new # uses the configured key
24
+ client = SteamApiClient::Client.new(api_token: "another-key") # overrides it
25
+ ```
26
+
27
+ Calling an endpoint that needs a key without one raises `SteamApiClient::Errors::MissingApiToken` before anything is sent.
28
+
29
+ | Setting | Default | Meaning |
30
+ |---|---|---|
31
+ | `api_token` | `nil` | Steam Web API key |
32
+ | `timeout` | `10` | Seconds for the whole request |
33
+ | `connect_timeout` | `5` | Seconds to open the connection |
34
+ | `adapter` | `:net_http` | [Faraday adapter](https://lostisland.github.io/faraday/#/adapters/index); add its gem to your Gemfile |
35
+ | `connection { \|faraday\| ... }` | none | Customizes the Faraday connection (middleware, adapter options) |
36
+
37
+ Every setting can be given globally (`Client.configure`) or per client (`Client.new(timeout: 3)`, or a block for the connection). Per-client values win. `Client.reset_config!` restores the defaults, which is handy in specs.
38
+
39
+ ```ruby
40
+ SteamApiClient::Client.configure do |config|
41
+ config.timeout = 30
42
+ config.connect_timeout = 2
43
+ config.adapter = :net_http_persistent # from the net-http-persistent adapter gem
44
+ config.connection { |faraday| faraday.request :instrumentation }
45
+ end
46
+
47
+ # The connection block runs after the adapter is set, so it can also replace it, with options
48
+ client = SteamApiClient::Client.new do |faraday|
49
+ faraday.adapter :net_http_persistent, pool_size: 5
50
+ end
51
+ ```
52
+
53
+ > **Logging:** Steam only accepts the key in the query string, so a logger that prints request URLs prints your key. The gem keeps it out of its own `inspect` output (`api_token=[REDACTED]`), but a logging middleware needs a filter:
54
+ >
55
+ > ```ruby
56
+ > client = SteamApiClient::Client.new do |faraday|
57
+ > faraday.response :logger, Rails.logger, headers: false do |logger|
58
+ > logger.filter(/(key=)[^&\s]+/, '\1[REDACTED]')
59
+ > end
60
+ > end
61
+ > ```
62
+
63
+ In your own specs, Faraday's test adapter replaces the network:
64
+
65
+ ```ruby
66
+ stubs = Faraday::Adapter::Test::Stubs.new do |stub|
67
+ stub.get("/IPlayerService/GetSteamLevel/v0001/") { [200, {}, '{"response":{"player_level":10}}'] }
68
+ end
69
+ client = SteamApiClient::Client.new(api_token: "test") { |faraday| faraday.adapter :test, stubs }
70
+ client.player_level.retrieve(steamid: 76561197960435530) # => 10
71
+ ```
72
+
73
+ ## Calling an endpoint
74
+
75
+ Every endpoint is a method on the client with a `retrieve` method that takes keyword arguments:
76
+
77
+ ```ruby
78
+ client.player_level.retrieve(steamid: 76561197960435530) # => 32
79
+ ```
80
+
81
+ - Missing or unknown arguments raise `ArgumentError` with Ruby's own messages (`missing keyword: :steamid`, `unknown keyword: :steam_id`).
82
+ - Values are checked: IDs must be Integers (numeric strings like `"76561197960435530"` are accepted), lists must be Arrays. Anything else raises `ArgumentError`, e.g. `steamid must be an Integer, got "robin"`.
83
+ - `retrieve_raw` takes the same arguments and returns Steam's JSON untouched (with symbol keys), for fields the gem doesn't expose:
84
+
85
+ ```ruby
86
+ client.player_level.retrieve_raw(steamid: 76561197960435530) # => {response: {player_level: 32}}
87
+ ```
88
+
89
+ ## Endpoints
90
+
91
+ A 🔑 means the endpoint needs an API key.
92
+
93
+ ### Players
94
+
95
+ #### `player_summaries`
96
+
97
+ 🔑 Profiles of up to 100 players. Steam leaves out IDs it doesn't know.
98
+
99
+ ```ruby
100
+ players = client.player_summaries.retrieve(steamids: [76561197960435530, 76561197960287930])
101
+ # => [#<data SteamApiClient::PlayerSummary steam_id=76561197960435530, persona_name="Robin", ...>, ...]
102
+
103
+ player = players.first
104
+ player.persona_name # => "Robin"
105
+ player.profile_url # => "https://steamcommunity.com/id/robinwalker/"
106
+ player.avatar_full # => "https://avatars.steamstatic.com/..._full.jpg"
107
+ player.persona_state # => 0 (0 offline, 1 online, 2 busy, 3 away, 4 snooze, 5 looking to trade, 6 looking to play)
108
+ player.community_visibility_state # => 3 (3 public, 1 not visible to you)
109
+ player.country_code # => "US", or nil when not public
110
+ ```
111
+
112
+ `SteamApiClient::PlayerSummary` fields: `steam_id`, `persona_name`, `profile_url`, `avatar`, `avatar_medium`, `avatar_full`, `avatar_hash`, `persona_state`, `persona_state_flags`, `community_visibility_state`, `profile_state`, `last_logoff`, `comment_permission`, `real_name`, `time_created`, `country_code`, `primary_clan_id`. Everything after `community_visibility_state` is `nil` when the profile (or that part of it) isn't public. Times are unix timestamps.
113
+
114
+ #### `player_steamid`
115
+
116
+ 🔑 The 64 bit SteamID a vanity URL (`steamcommunity.com/id/<vanityurl>`) resolves to, or `nil`.
117
+
118
+ ```ruby
119
+ client.player_steamid.retrieve(vanityurl: "robinwalker") # => 76561197960435530
120
+ client.player_steamid.retrieve(vanityurl: "no-such-profile") # => nil
121
+ ```
122
+
123
+ #### `player_friends`
124
+
125
+ 🔑 A player's friend list. `relationship:` is `"all"` (default) or `"friend"`.
126
+
127
+ ```ruby
128
+ client.player_friends.retrieve(steamid: 76561197960435530)
129
+ # => [#<data SteamApiClient::Friend steam_id=76561198000000002, relationship="friend", friend_since=1262304000>, ...]
130
+ ```
131
+
132
+ A private friend list raises `Errors::HttpUnauthorized`.
133
+
134
+ #### `player_level`
135
+
136
+ 🔑 A player's Steam level, or `nil` when the profile isn't visible.
137
+
138
+ ```ruby
139
+ client.player_level.retrieve(steamid: 76561197960435530) # => 32
140
+ ```
141
+
142
+ #### `player_owned_games`
143
+
144
+ 🔑 The games a player owns, or `nil` when the library isn't public. `include_appinfo: true` adds each game's name and icon, `include_played_free_games: true` adds free games the player has played (both default to `false`).
145
+
146
+ ```ruby
147
+ library = client.player_owned_games.retrieve(steamid: 76561198000000001, include_appinfo: true)
148
+ # => #<data SteamApiClient::OwnedGames game_count=375, games=[...]>
149
+
150
+ library.games.first
151
+ # => #<data SteamApiClient::Game app_id=440, name="Team Fortress 2", img_icon_url="f568912870a4684f9ec76277a1a404dda6bab213",
152
+ # playtime_forever=2090, playtime_2weeks=nil, has_community_visible_stats=true, has_leaderboards=true, ...>
153
+
154
+ library.games.sum(&:playtime_forever) / 60 # hours played
155
+ ```
156
+
157
+ `SteamApiClient::Game` fields: `app_id`, `name`, `img_icon_url`, `playtime_forever`, `playtime_2weeks`, `has_community_visible_stats`, `has_leaderboards`, `content_descriptor_ids`, `playtime_windows_forever`, `playtime_mac_forever`, `playtime_linux_forever`, `rtime_last_played`. Playtimes are in minutes. `playtime_2weeks` is only sent for games played recently, and Steam no longer sends the per-platform playtimes or `rtime_last_played` for most libraries.
158
+
159
+ #### `player_recent_games`
160
+
161
+ 🔑 The games played in the last two weeks, or `nil` when not public. `count:` defaults to 5.
162
+
163
+ ```ruby
164
+ recent = client.player_recent_games.retrieve(steamid: 76561198000000001, count: 3)
165
+ # => #<data SteamApiClient::RecentGames total_count=1, games=[#<data SteamApiClient::Game app_id=2868840, name="Slay the Spire 2", playtime_2weeks=922, ...>]>
166
+ ```
167
+
168
+ #### `player_achievements`
169
+
170
+ 🔑 A player's achievements in a game.
171
+
172
+ ```ruby
173
+ stats = client.player_achievements.retrieve(appid: 440, steamid: 76561198000000001)
174
+ stats.game_name # => "Team Fortress 2"
175
+ stats.achievements.first
176
+ # => #<data SteamApiClient::PlayerAchievements::Achievement api_name="TF_PLAY_GAME_EVERYCLASS", achieved=true, unlock_time=1262304000>
177
+ stats.achievements.count(&:achieved) # => 95
178
+ ```
179
+
180
+ `unlock_time` is a unix timestamp, `0` when locked. A private profile raises `Errors::HttpForbidden` (`Profile is not public`), and a game without stats `Errors::HttpBadRequest` (`Requested app has no stats`).
181
+
182
+ #### `player_game_stats`
183
+
184
+ 🔑 A player's stats and unlocked achievements in a game.
185
+
186
+ ```ruby
187
+ stats = client.player_game_stats.retrieve(appid: 440, steamid: 76561198000000001)
188
+ stats.stats.first # => #<data SteamApiClient::PlayerGameStats::Stat name="Scout.accum.iDamageDealt", value=26580>
189
+ stats.achievements.first # => #<data SteamApiClient::PlayerGameStats::Achievement name="TF_PLAY_GAME_EVERYCLASS", achieved=true>
190
+ ```
191
+
192
+ Stat values are Integers or Floats, as the game defines them. A private profile, or a game without stats, raises `Errors::HttpBadRequest`.
193
+
194
+ ### Apps
195
+
196
+ #### `app_details`
197
+
198
+ The store page of an app, from Steam's unofficial [Storefront API](https://wiki.teamfortress.com/wiki/User:RJackson/StorefrontAPI), or `nil` when the app is unavailable (unknown, removed, or not sold in that country). No key needed.
199
+
200
+ - `filters:` limits the fields, e.g. `["basic", "price_overview", "platforms"]`. Every field when empty (default). Fields left out are `nil`.
201
+ - `country_code:` (default `"US"`) sets the currency and the availability. `locale:` (default `"en"`) the language of the texts, e.g. `"portuguese"`.
202
+
203
+ ```ruby
204
+ app = client.app_details.retrieve(appid: 1091500, filters: ["basic", "price_overview"], country_code: "BR", locale: "portuguese")
205
+ app.name # => "Cyberpunk 2077"
206
+ app.price_overview # => #<data SteamApiClient::AppDetails::Price currency="BRL", initial=19990, final=5997, discount_percent=70,
207
+ # initial_formatted="R$ 199,90", final_formatted="R$ 59,97">
208
+ client.app_details.retrieve(appid: 440).platforms
209
+ # => #<data SteamApiClient::AppDetails::Platforms windows=true, mac=false, linux=true>
210
+ ```
211
+
212
+ `SteamApiClient::AppDetails` fields: `app_id`, `type`, `name`, `required_age`, `is_free`, `dlc`, `detailed_description`, `about_the_game`, `short_description`, `supported_languages`, `header_image`, `website`, `pc_requirements` / `mac_requirements` / `linux_requirements` (`AppDetails::Requirements`: `minimum`, `recommended`, as HTML), `developers`, `publishers`, `price_overview` (`AppDetails::Price`, in cents; `nil` for free apps), `packages`, `package_groups` (`AppDetails::PackageGroup` with `subs`: `AppDetails::Package`), `platforms`, `metacritic` (`score`, `url`), `categories` and `genres` (`id`, `description`), `screenshots`, `movies` (`AppDetails::Movie` with DASH and HLS streams), `recommendations` (the number of user reviews), `achievements` (`total`, `highlighted`), `release_date` (`coming_soon`, `date`), `support_info` (`url`, `email`), `background`, `background_raw`, `content_descriptors` (`ids`, `notes`).
213
+
214
+ #### `app_news`
215
+
216
+ The latest news of an app. `count:` is required, `maxlength:` (default 300) shortens each item's contents.
217
+
218
+ ```ruby
219
+ client.app_news.retrieve(appid: 440, count: 5).first
220
+ # => #<data SteamApiClient::NewsItem gid="...", title="Team Fortress 2 Update Released", url="https://steamstore-a.akamaihd.net/news/externalpost/...",
221
+ # is_external_url=true, author="erics", contents="...", feed_label="Community Announcements", date=1792179520,
222
+ # feed_name="steam_community_announcements", feed_type=1, app_id=440, tags=["patchnotes"]>
223
+ ```
224
+
225
+ #### `app_global_achievements`
226
+
227
+ The share of players who unlocked each achievement of an app, in percent.
228
+
229
+ ```ruby
230
+ client.app_global_achievements.retrieve(appid: 440).first
231
+ # => #<data SteamApiClient::GlobalAchievement name="TF_SCOUT_LONG_DISTANCE_RUNNER", percent=49.5>
232
+ ```
233
+
234
+ #### `game_current_players`
235
+
236
+ The number of players in a game right now.
237
+
238
+ ```ruby
239
+ client.game_current_players.retrieve(appid: 440) # => 88231
240
+ ```
241
+
242
+ An unknown app raises `Errors::HttpNotFound`.
243
+
244
+ ## Results
245
+
246
+ Results are immutable [`Data`](https://docs.ruby-lang.org/en/3.3/Data.html) objects, Integers, or Arrays of them:
247
+
248
+ - They compare by value, are frozen, and work with pattern matching:
249
+
250
+ ```ruby
251
+ case client.player_summaries.retrieve(steamids: [id]).first
252
+ in {community_visibility_state: 3, persona_name:} then "#{persona_name} has a public profile"
253
+ in nil then "no such player"
254
+ else "private profile"
255
+ end
256
+ ```
257
+
258
+ - `#to_h` returns a plain Hash, nested parts included (e.g. to serialize as JSON).
259
+ - Lists Steam leaves out are `[]`. Data that isn't visible (private profiles, unavailable apps, unmatched vanity URLs) is `nil`.
260
+
261
+ ## Errors
262
+
263
+ ```
264
+ StandardError
265
+ ├── SteamApiClient::Errors::ApiError # Steam answered with an HTTP error
266
+ │ ├── HttpBadRequest (400)
267
+ │ ├── HttpUnauthorized (401)
268
+ │ ├── HttpForbidden (403)
269
+ │ ├── HttpNotFound (404)
270
+ │ ├── HttpTimeout (408, or no answer within the timeouts)
271
+ │ ├── HttpTooManyRequests (429) #retry_after
272
+ │ ├── HttpInternalServerError (500)
273
+ │ ├── HttpBadGateway (502)
274
+ │ └── HttpServiceUnavailable (503) #retry_after
275
+ ├── SteamApiClient::Errors::ConnectionError # Steam couldn't be reached: DNS, refused connection, TLS
276
+ ├── SteamApiClient::Errors::ResponseParseError # the body isn't the JSON the endpoint returns
277
+ └── SteamApiClient::Errors::MissingApiToken # the endpoint needs a key and the client has none
278
+ ArgumentError # missing, unknown or invalid arguments
279
+ ```
280
+
281
+ - Other HTTP error statuses raise a plain `ApiError`. Its message has the status code and Steam's body.
282
+ - `ConnectionError` is **not** an `ApiError`, and has the adapter's error as `#cause`. A timed-out request is an `HttpTimeout` without a response.
283
+ - `ResponseParseError#response` is the response, and `#cause` the JSON error or the `SteamApiClient::Model::Error` naming the field that didn't match.
284
+ - None of the messages contain the API key.
285
+
286
+ To rescue everything that comes from Steam or the network:
287
+
288
+ ```ruby
289
+ rescue SteamApiClient::Errors::ApiError, SteamApiClient::Errors::ConnectionError, SteamApiClient::Errors::ResponseParseError
290
+ ```
291
+
292
+ ### Retrying
293
+
294
+ `HttpTooManyRequests` and `HttpServiceUnavailable` expose `#retry_after`, the seconds from Steam's `Retry-After` header (`nil` when it sends none). For example:
295
+
296
+ ```ruby
297
+ RETRYABLE = [
298
+ SteamApiClient::Errors::HttpTooManyRequests, SteamApiClient::Errors::HttpBadGateway,
299
+ SteamApiClient::Errors::HttpServiceUnavailable, SteamApiClient::Errors::HttpTimeout
300
+ ].freeze
301
+
302
+ def with_retries(attempts: 3)
303
+ tries = 0
304
+ begin
305
+ yield
306
+ rescue *RETRYABLE => error
307
+ tries += 1
308
+ raise if tries >= attempts
309
+
310
+ sleep((error.respond_to?(:retry_after) && error.retry_after) || 2**tries)
311
+ retry
312
+ end
313
+ end
314
+
315
+ with_retries { client.game_current_players.retrieve(appid: 440) }
316
+ ```
317
+
318
+ Don't retry other 4xx errors: they won't succeed on their own.
319
+
320
+ ## Limits and caveats
321
+
322
+ - **Rate limits.** Steam's [Web API terms](https://steamcommunity.com/dev/apiterms) allow 100,000 calls per day per key. The Storefront API (`app_details`) is rate limited per IP address, commonly reported at about 200 requests per 5 minutes. Both answer `429` when you go over. Cache what you can.
323
+ - **The Storefront API is unofficial.** `app_details` uses the API behind Steam's store and Big Picture mode. Valve doesn't document it, and its fields change without notice.
324
+ - **Private profiles.** What a player hides depends on their privacy settings. Depending on the endpoint, Steam answers with an error (`player_achievements`: 403, `player_game_stats`: 400, `player_friends`: 401) or with empty data, which the gem returns as `nil` (`player_owned_games`, `player_recent_games`, `player_level`). `player_summaries` still returns the public part of the profile.
325
+ - **The key is in the URL.** Steam requires `?key=...` in the query string. Treat logs of request URLs as secrets (see [Configuration](#configuration)), and [rotate your key](https://steamcommunity.com/dev/apikey) if it leaks.
326
+ - **SteamIDs are 64 bit Integers** (e.g. `76561197960435530`). Use `player_steamid` to get one from a vanity URL.