sports-odds-api 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.
- checksums.yaml +7 -0
- data/.ignore +2 -0
- data/CHANGELOG.md +10 -0
- data/README.md +186 -0
- data/SECURITY.md +27 -0
- data/lib/sports_odds_api/client.rb +112 -0
- data/lib/sports_odds_api/errors.rb +228 -0
- data/lib/sports_odds_api/file_part.rb +55 -0
- data/lib/sports_odds_api/internal/next_cursor_page.rb +86 -0
- data/lib/sports_odds_api/internal/transport/base_client.rb +580 -0
- data/lib/sports_odds_api/internal/transport/pooled_net_requester.rb +201 -0
- data/lib/sports_odds_api/internal/type/array_of.rb +168 -0
- data/lib/sports_odds_api/internal/type/base_model.rb +534 -0
- data/lib/sports_odds_api/internal/type/base_page.rb +55 -0
- data/lib/sports_odds_api/internal/type/boolean.rb +77 -0
- data/lib/sports_odds_api/internal/type/converter.rb +327 -0
- data/lib/sports_odds_api/internal/type/enum.rb +131 -0
- data/lib/sports_odds_api/internal/type/file_input.rb +108 -0
- data/lib/sports_odds_api/internal/type/hash_of.rb +188 -0
- data/lib/sports_odds_api/internal/type/request_parameters.rb +42 -0
- data/lib/sports_odds_api/internal/type/union.rb +243 -0
- data/lib/sports_odds_api/internal/type/unknown.rb +81 -0
- data/lib/sports_odds_api/internal/util.rb +914 -0
- data/lib/sports_odds_api/internal.rb +20 -0
- data/lib/sports_odds_api/models/account_get_usage_params.rb +14 -0
- data/lib/sports_odds_api/models/account_usage.rb +91 -0
- data/lib/sports_odds_api/models/event.rb +686 -0
- data/lib/sports_odds_api/models/event_get_params.rb +195 -0
- data/lib/sports_odds_api/models/league.rb +39 -0
- data/lib/sports_odds_api/models/league_get_params.rb +30 -0
- data/lib/sports_odds_api/models/league_get_response.rb +8 -0
- data/lib/sports_odds_api/models/player.rb +128 -0
- data/lib/sports_odds_api/models/player_get_params.rb +58 -0
- data/lib/sports_odds_api/models/rate_limit_interval.rb +92 -0
- data/lib/sports_odds_api/models/sport.rb +197 -0
- data/lib/sports_odds_api/models/sport_get_params.rb +14 -0
- data/lib/sports_odds_api/models/sport_get_response.rb +8 -0
- data/lib/sports_odds_api/models/stat.rb +144 -0
- data/lib/sports_odds_api/models/stat_get_params.rb +43 -0
- data/lib/sports_odds_api/models/stat_get_response.rb +8 -0
- data/lib/sports_odds_api/models/stream_events_params.rb +38 -0
- data/lib/sports_odds_api/models/stream_events_response.rb +120 -0
- data/lib/sports_odds_api/models/team.rb +162 -0
- data/lib/sports_odds_api/models/team_get_params.rb +58 -0
- data/lib/sports_odds_api/models.rb +76 -0
- data/lib/sports_odds_api/request_options.rb +78 -0
- data/lib/sports_odds_api/resources/account.rb +33 -0
- data/lib/sports_odds_api/resources/events.rb +94 -0
- data/lib/sports_odds_api/resources/leagues.rb +39 -0
- data/lib/sports_odds_api/resources/players.rb +48 -0
- data/lib/sports_odds_api/resources/sports.rb +33 -0
- data/lib/sports_odds_api/resources/stats.rb +44 -0
- data/lib/sports_odds_api/resources/stream.rb +40 -0
- data/lib/sports_odds_api/resources/teams.rb +48 -0
- data/lib/sports_odds_api/version.rb +5 -0
- data/lib/sports_odds_api.rb +82 -0
- data/manifest.yaml +15 -0
- data/rbi/sports_odds_api/client.rbi +83 -0
- data/rbi/sports_odds_api/errors.rbi +205 -0
- data/rbi/sports_odds_api/file_part.rbi +37 -0
- data/rbi/sports_odds_api/internal/next_cursor_page.rbi +22 -0
- data/rbi/sports_odds_api/internal/transport/base_client.rbi +305 -0
- data/rbi/sports_odds_api/internal/transport/pooled_net_requester.rbi +80 -0
- data/rbi/sports_odds_api/internal/type/array_of.rbi +104 -0
- data/rbi/sports_odds_api/internal/type/base_model.rbi +310 -0
- data/rbi/sports_odds_api/internal/type/base_page.rbi +43 -0
- data/rbi/sports_odds_api/internal/type/boolean.rbi +58 -0
- data/rbi/sports_odds_api/internal/type/converter.rbi +225 -0
- data/rbi/sports_odds_api/internal/type/enum.rbi +82 -0
- data/rbi/sports_odds_api/internal/type/file_input.rbi +59 -0
- data/rbi/sports_odds_api/internal/type/hash_of.rbi +104 -0
- data/rbi/sports_odds_api/internal/type/request_parameters.rbi +31 -0
- data/rbi/sports_odds_api/internal/type/union.rbi +128 -0
- data/rbi/sports_odds_api/internal/type/unknown.rbi +58 -0
- data/rbi/sports_odds_api/internal/util.rbi +487 -0
- data/rbi/sports_odds_api/internal.rbi +18 -0
- data/rbi/sports_odds_api/models/account_get_usage_params.rbi +32 -0
- data/rbi/sports_odds_api/models/account_usage.rbi +173 -0
- data/rbi/sports_odds_api/models/event.rbi +1269 -0
- data/rbi/sports_odds_api/models/event_get_params.rbi +286 -0
- data/rbi/sports_odds_api/models/league.rbi +74 -0
- data/rbi/sports_odds_api/models/league_get_params.rbi +60 -0
- data/rbi/sports_odds_api/models/league_get_response.rbi +11 -0
- data/rbi/sports_odds_api/models/player.rbi +247 -0
- data/rbi/sports_odds_api/models/player_get_params.rbi +95 -0
- data/rbi/sports_odds_api/models/rate_limit_interval.rbi +176 -0
- data/rbi/sports_odds_api/models/sport.rbi +371 -0
- data/rbi/sports_odds_api/models/sport_get_params.rbi +29 -0
- data/rbi/sports_odds_api/models/sport_get_response.rbi +11 -0
- data/rbi/sports_odds_api/models/stat.rbi +273 -0
- data/rbi/sports_odds_api/models/stat_get_params.rbi +72 -0
- data/rbi/sports_odds_api/models/stat_get_response.rbi +11 -0
- data/rbi/sports_odds_api/models/stream_events_params.rbi +71 -0
- data/rbi/sports_odds_api/models/stream_events_response.rbi +247 -0
- data/rbi/sports_odds_api/models/team.rbi +305 -0
- data/rbi/sports_odds_api/models/team_get_params.rbi +92 -0
- data/rbi/sports_odds_api/models.rbi +35 -0
- data/rbi/sports_odds_api/request_options.rbi +59 -0
- data/rbi/sports_odds_api/resources/account.rbi +21 -0
- data/rbi/sports_odds_api/resources/events.rbi +96 -0
- data/rbi/sports_odds_api/resources/leagues.rbi +29 -0
- data/rbi/sports_odds_api/resources/players.rbi +41 -0
- data/rbi/sports_odds_api/resources/sports.rbi +21 -0
- data/rbi/sports_odds_api/resources/stats.rbi +34 -0
- data/rbi/sports_odds_api/resources/stream.rbi +32 -0
- data/rbi/sports_odds_api/resources/teams.rbi +39 -0
- data/rbi/sports_odds_api/version.rbi +5 -0
- data/sig/sports_odds_api/client.rbs +45 -0
- data/sig/sports_odds_api/errors.rbs +117 -0
- data/sig/sports_odds_api/file_part.rbs +21 -0
- data/sig/sports_odds_api/internal/next_cursor_page.rbs +13 -0
- data/sig/sports_odds_api/internal/transport/base_client.rbs +133 -0
- data/sig/sports_odds_api/internal/transport/pooled_net_requester.rbs +45 -0
- data/sig/sports_odds_api/internal/type/array_of.rbs +48 -0
- data/sig/sports_odds_api/internal/type/base_model.rbs +104 -0
- data/sig/sports_odds_api/internal/type/base_page.rbs +24 -0
- data/sig/sports_odds_api/internal/type/boolean.rbs +26 -0
- data/sig/sports_odds_api/internal/type/converter.rbs +79 -0
- data/sig/sports_odds_api/internal/type/enum.rbs +32 -0
- data/sig/sports_odds_api/internal/type/file_input.rbs +25 -0
- data/sig/sports_odds_api/internal/type/hash_of.rbs +48 -0
- data/sig/sports_odds_api/internal/type/request_parameters.rbs +19 -0
- data/sig/sports_odds_api/internal/type/union.rbs +52 -0
- data/sig/sports_odds_api/internal/type/unknown.rbs +26 -0
- data/sig/sports_odds_api/internal/util.rbs +185 -0
- data/sig/sports_odds_api/internal.rbs +10 -0
- data/sig/sports_odds_api/models/account_get_usage_params.rbs +15 -0
- data/sig/sports_odds_api/models/account_usage.rbs +116 -0
- data/sig/sports_odds_api/models/event.rbs +860 -0
- data/sig/sports_odds_api/models/event_get_params.rbs +168 -0
- data/sig/sports_odds_api/models/league.rbs +50 -0
- data/sig/sports_odds_api/models/league_get_params.rbs +32 -0
- data/sig/sports_odds_api/models/league_get_response.rbs +7 -0
- data/sig/sports_odds_api/models/player.rbs +162 -0
- data/sig/sports_odds_api/models/player_get_params.rbs +56 -0
- data/sig/sports_odds_api/models/rate_limit_interval.rbs +67 -0
- data/sig/sports_odds_api/models/sport.rbs +241 -0
- data/sig/sports_odds_api/models/sport_get_params.rbs +15 -0
- data/sig/sports_odds_api/models/sport_get_response.rbs +7 -0
- data/sig/sports_odds_api/models/stat.rbs +166 -0
- data/sig/sports_odds_api/models/stat_get_params.rbs +38 -0
- data/sig/sports_odds_api/models/stat_get_response.rbs +7 -0
- data/sig/sports_odds_api/models/stream_events_params.rbs +38 -0
- data/sig/sports_odds_api/models/stream_events_response.rbs +151 -0
- data/sig/sports_odds_api/models/team.rbs +201 -0
- data/sig/sports_odds_api/models/team_get_params.rbs +56 -0
- data/sig/sports_odds_api/models.rbs +33 -0
- data/sig/sports_odds_api/request_options.rbs +36 -0
- data/sig/sports_odds_api/resources/account.rbs +11 -0
- data/sig/sports_odds_api/resources/events.rbs +32 -0
- data/sig/sports_odds_api/resources/leagues.rbs +13 -0
- data/sig/sports_odds_api/resources/players.rbs +16 -0
- data/sig/sports_odds_api/resources/sports.rbs +11 -0
- data/sig/sports_odds_api/resources/stats.rbs +14 -0
- data/sig/sports_odds_api/resources/stream.rbs +14 -0
- data/sig/sports_odds_api/resources/teams.rbs +16 -0
- data/sig/sports_odds_api/version.rbs +3 -0
- metadata +215 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: ed23b744aefd6897f1ffe2c1b8b6ec7d2017a5c38f9f61407e9036a64c4b539c
|
|
4
|
+
data.tar.gz: 330d785ea83722954b4a1861bce87df552f61bf5640cd43d28a19b24ce39699b
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: a294de700247d92d72620d6ecd464313dc711d36bcccba1c848dbe1ce8a7da399559a4dcb59bfe3fcaf70982c6a86423af8d45ef3cb6bc0fbcf02bdedc20337b
|
|
7
|
+
data.tar.gz: b482d8805041a5b6d2efd02090c05f598b3b998619ba553502633be7406285304e8608f88f4c6d548b0b27d7c2a8c333639638d9faf8770757eb44fc21be5e2d
|
data/.ignore
ADDED
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 1.0.0 (2025-09-23)
|
|
4
|
+
|
|
5
|
+
Full Changelog: [v0.0.1...v1.0.0](https://github.com/SportsGameOdds/sports-odds-api-ruby/compare/v0.0.1...v1.0.0)
|
|
6
|
+
|
|
7
|
+
### Chores
|
|
8
|
+
|
|
9
|
+
* configure new SDK language ([9c4b469](https://github.com/SportsGameOdds/sports-odds-api-ruby/commit/9c4b4699bebfe2c5b954d381bd7e9e80d0fac2ad))
|
|
10
|
+
* update SDK settings ([cbf7543](https://github.com/SportsGameOdds/sports-odds-api-ruby/commit/cbf7543231a240400a972d7f4ef862387ffae409))
|
data/README.md
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
# Sports Odds API - Live Sports Data & Sportsbook Betting Odds - Powered by SportsGameOdds Ruby Library
|
|
2
|
+
|
|
3
|
+
Get live betting odds, spreads, and totals for NFL, NBA, MLB, and 50 additional sports and leagues. Production-ready Ruby SDK with connection pooling, 99.9% uptime, and sub-minute updates during live games. Perfect for developers building sportsbook platforms, odds comparison tools, positive EV models, and anything else that requires fast, accurate sports data.
|
|
4
|
+
|
|
5
|
+
[)](https://rubygems.org/gems/sports-odds-api)
|
|
6
|
+
|
|
7
|
+
This library provides convenient access to the Sports Game Odds REST API from any Ruby 3.2.0+ application.
|
|
8
|
+
|
|
9
|
+
The REST API documentation can be found on [sportsgameodds.com](https://sportsgameodds.com/docs/). The full API of this library can be found in [api.md](api.md).
|
|
10
|
+
|
|
11
|
+
## Features
|
|
12
|
+
|
|
13
|
+
**For developers building the next generation of sports stats and/or betting applications:**
|
|
14
|
+
|
|
15
|
+
- 📈 **3k+ odds markets** including moneylines, spreads, over/unders, team props, player props & more
|
|
16
|
+
- 🏈 **50+ leagues covered** including NFL, NBA, MLB, NHL, NCAAF, NCAAB, EPL, UCL, UFC, PGA, ATP & more
|
|
17
|
+
- 📊 **80+ sportsbooks** with unified odds formats, alt lines & deeplinks
|
|
18
|
+
- 📺 **Live scores & stats** coverage on all games, teams, and players
|
|
19
|
+
- ⚡ **Sub-100ms response times** and sub-minute updates for fast data
|
|
20
|
+
- 💎 **Comprehensive Ruby type support** with Yard, RBS, and RBI definitions
|
|
21
|
+
- 💰 **Developer-friendly pricing** with a generous free tier
|
|
22
|
+
- ⏱️ **5-minute setup** with copy-paste examples
|
|
23
|
+
|
|
24
|
+
## Installation
|
|
25
|
+
|
|
26
|
+
Add to your application's `Gemfile`:
|
|
27
|
+
|
|
28
|
+
```ruby
|
|
29
|
+
gem "sports-odds-api", "~> 1.0.0"
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Or install manually:
|
|
33
|
+
|
|
34
|
+
```sh
|
|
35
|
+
gem install sports-odds-api
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Obtain an API Key
|
|
39
|
+
|
|
40
|
+
Get a free API key from [sportsgameodds.com](https://sportsgameodds.com/pricing).
|
|
41
|
+
|
|
42
|
+
Unlike enterprise-only solutions, the Sports Game Odds API offers a developer-friendly experience, transparent pricing, comprehensive documentation, and a generous free tier.
|
|
43
|
+
|
|
44
|
+
## Usage
|
|
45
|
+
|
|
46
|
+
The full API of this library can be found in [api.md](api.md).
|
|
47
|
+
|
|
48
|
+
```ruby
|
|
49
|
+
require "bundler/setup"
|
|
50
|
+
require "sports_odds_api"
|
|
51
|
+
|
|
52
|
+
client = SportsOddsAPI::Client.new(
|
|
53
|
+
api_key_param: ENV["SPORTS_ODDS_API_KEY_HEADER"] # This is the default and can be omitted
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
page = client.events.get
|
|
57
|
+
event = page.data[0]
|
|
58
|
+
|
|
59
|
+
puts(event.activity)
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
# Real-Time Event Streaming API
|
|
63
|
+
|
|
64
|
+
This API endpoint is only available to **AllStar** and **custom plan** subscribers. It is not included with basic subscription tiers. [Contact support](mailto:api@sportsgameodds.com) to get access.
|
|
65
|
+
|
|
66
|
+
This streaming API is currently in **beta**. API call patterns, response formats, and functionality may change. Fully managed streaming via SDK may be available in future releases.
|
|
67
|
+
|
|
68
|
+
Our Streaming API provides real-time updates for Event objects through WebSocket connections. Instead of polling our REST endpoints, you can maintain a persistent connection to receive instant notifications when events change. This is ideal for applications that need immediate updates with minimal delay.
|
|
69
|
+
|
|
70
|
+
We use [Pusher Protocol](https://pusher.com/docs/channels/library_auth_reference/pusher-websockets-protocol/) for WebSocket communication. While you can connect using any WebSocket library, we recommend using the [pusher-client-ruby](https://github.com/pusher/pusher-client-ruby) gem.
|
|
71
|
+
|
|
72
|
+
## How It Works
|
|
73
|
+
|
|
74
|
+
The streaming process involves two steps:
|
|
75
|
+
|
|
76
|
+
1. **Get Connection Details**: Make a request using `client.stream.events()` to receive:
|
|
77
|
+
- WebSocket authentication credentials
|
|
78
|
+
- WebSocket URL/channel info
|
|
79
|
+
- Initial snapshot of current data
|
|
80
|
+
|
|
81
|
+
2. **Connect and Stream**: Use the provided details to connect via Pusher and receive real-time `eventID` notifications for changed events.
|
|
82
|
+
|
|
83
|
+
Your API key will have limits on concurrent streams.
|
|
84
|
+
|
|
85
|
+
## Available Feeds
|
|
86
|
+
|
|
87
|
+
Subscribe to different feeds using the `feed` query parameter:
|
|
88
|
+
|
|
89
|
+
| Feed | Description | Required Parameters |
|
|
90
|
+
| ----------------- | --------------------------------------------------------------------------- | ------------------- |
|
|
91
|
+
| `events:live` | All events currently in progress (started but not finished) | None |
|
|
92
|
+
| `events:upcoming` | Upcoming events with available odds for a specific league | `leagueID` |
|
|
93
|
+
| `events:byid` | Updates for a single specific event | `eventID` |
|
|
94
|
+
|
|
95
|
+
The number of supported feeds will increase over time. Please reach out if you have a use case which can't be covered by these feeds.
|
|
96
|
+
|
|
97
|
+
## Quick Start Example
|
|
98
|
+
|
|
99
|
+
Here's the minimal code to connect to live events:
|
|
100
|
+
|
|
101
|
+
```ruby
|
|
102
|
+
require "sports_odds_api"
|
|
103
|
+
require "pusher-client"
|
|
104
|
+
|
|
105
|
+
STREAM_FEED = "events:live" # ex: events:upcoming, events:byid, events:live
|
|
106
|
+
API_KEY = "YOUR API KEY"
|
|
107
|
+
|
|
108
|
+
client = SportsOddsAPI::Client.new(api_key_param: API_KEY)
|
|
109
|
+
|
|
110
|
+
# Initialize a data structure where we'll save the event data
|
|
111
|
+
events = {}
|
|
112
|
+
|
|
113
|
+
# Call this endpoint to get initial data and connection parameters
|
|
114
|
+
stream_info = client.stream.events(feed: STREAM_FEED)
|
|
115
|
+
|
|
116
|
+
# Seed initial data
|
|
117
|
+
stream_info.data.each { |event| events[event.eventID] = event }
|
|
118
|
+
|
|
119
|
+
# Connect to WebSocket server
|
|
120
|
+
pusher = PusherClient::Socket.new(stream_info.pusherKey, stream_info.pusherOptions)
|
|
121
|
+
channel = pusher.subscribe(stream_info.channel)
|
|
122
|
+
|
|
123
|
+
channel.bind("data") do |changed_events|
|
|
124
|
+
event_ids = changed_events.map { |ev| ev["eventID"] }.join(",")
|
|
125
|
+
|
|
126
|
+
client.events.get(eventIDs: event_ids).each do |event|
|
|
127
|
+
events[event.eventID] = event
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
trap("SIGINT") { pusher.disconnect }
|
|
132
|
+
pusher.connect
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## Pagination
|
|
136
|
+
|
|
137
|
+
List methods in the Sports Game Odds API are paginated.
|
|
138
|
+
|
|
139
|
+
This library provides auto-paginating iterators with each list response:
|
|
140
|
+
|
|
141
|
+
```ruby
|
|
142
|
+
page = client.events.get(limit: 30)
|
|
143
|
+
|
|
144
|
+
page.auto_paging_each do |event|
|
|
145
|
+
puts(event.activity)
|
|
146
|
+
end
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Or, you can use `#next_page?` and `#next_page` for manual pagination.
|
|
150
|
+
|
|
151
|
+
## Handling Errors
|
|
152
|
+
|
|
153
|
+
When the library is unable to connect to the API, or if the API returns a non-success status code (4xx or 5xx), a subclass of `SportsOddsAPI::Errors::APIError` will be thrown.
|
|
154
|
+
|
|
155
|
+
```ruby
|
|
156
|
+
begin
|
|
157
|
+
page = client.events.get
|
|
158
|
+
rescue SportsOddsAPI::Errors::APIError => e
|
|
159
|
+
puts(e.message)
|
|
160
|
+
end
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
Error codes map to specific exception classes, such as `AuthenticationError`, `RateLimitError`, `NotFoundError`, etc.
|
|
164
|
+
|
|
165
|
+
## Retries
|
|
166
|
+
|
|
167
|
+
Certain errors are retried automatically with exponential backoff. Configure retries globally or per-request with the `max_retries` option.
|
|
168
|
+
|
|
169
|
+
## Timeouts
|
|
170
|
+
|
|
171
|
+
Requests time out after 60 seconds by default. Configure with `timeout` globally or per-request.
|
|
172
|
+
|
|
173
|
+
## Advanced Usage
|
|
174
|
+
|
|
175
|
+
- Access undocumented params via `extra_query`, `extra_body`, and `extra_headers`
|
|
176
|
+
- Use `client.request` for undocumented endpoints
|
|
177
|
+
- Threadsafe client with connection pooling
|
|
178
|
+
- Sorbet integration via RBI & RBS definitions
|
|
179
|
+
|
|
180
|
+
## Requirements
|
|
181
|
+
|
|
182
|
+
- Ruby 3.2.0+
|
|
183
|
+
|
|
184
|
+
## Contributing
|
|
185
|
+
|
|
186
|
+
See [the contributing documentation](https://github.com/SportsGameOdds/sports-odds-api-ruby/tree/main/CONTRIBUTING.md).
|
data/SECURITY.md
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Security Policy
|
|
2
|
+
|
|
3
|
+
## Reporting Security Issues
|
|
4
|
+
|
|
5
|
+
This SDK is generated by [Stainless Software Inc](http://stainless.com). Stainless takes security seriously, and encourages you to report any security vulnerability promptly so that appropriate action can be taken.
|
|
6
|
+
|
|
7
|
+
To report a security issue, please contact the Stainless team at security@stainless.com.
|
|
8
|
+
|
|
9
|
+
## Responsible Disclosure
|
|
10
|
+
|
|
11
|
+
We appreciate the efforts of security researchers and individuals who help us maintain the security of
|
|
12
|
+
SDKs we generate. If you believe you have found a security vulnerability, please adhere to responsible
|
|
13
|
+
disclosure practices by allowing us a reasonable amount of time to investigate and address the issue
|
|
14
|
+
before making any information public.
|
|
15
|
+
|
|
16
|
+
## Reporting Non-SDK Related Security Issues
|
|
17
|
+
|
|
18
|
+
If you encounter security issues that are not directly related to SDKs but pertain to the services
|
|
19
|
+
or products provided by Sports Game Odds, please follow the respective company's security reporting guidelines.
|
|
20
|
+
|
|
21
|
+
### Sports Game Odds Terms and Policies
|
|
22
|
+
|
|
23
|
+
Please contact api@sportsgameodds.com for any questions or concerns regarding the security of our services.
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
Thank you for helping us keep the SDKs and systems they interact with secure.
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SportsOddsAPI
|
|
4
|
+
class Client < SportsOddsAPI::Internal::Transport::BaseClient
|
|
5
|
+
# Default max number of retries to attempt after a failed retryable request.
|
|
6
|
+
DEFAULT_MAX_RETRIES = 2
|
|
7
|
+
|
|
8
|
+
# Default per-request timeout.
|
|
9
|
+
DEFAULT_TIMEOUT_IN_SECONDS = 60.0
|
|
10
|
+
|
|
11
|
+
# Default initial retry delay in seconds.
|
|
12
|
+
# Overall delay is calculated using exponential backoff + jitter.
|
|
13
|
+
DEFAULT_INITIAL_RETRY_DELAY = 0.5
|
|
14
|
+
|
|
15
|
+
# Default max retry delay in seconds.
|
|
16
|
+
DEFAULT_MAX_RETRY_DELAY = 8.0
|
|
17
|
+
|
|
18
|
+
# API key via header
|
|
19
|
+
# @return [String, nil]
|
|
20
|
+
attr_reader :api_key_header
|
|
21
|
+
|
|
22
|
+
# API key via query param
|
|
23
|
+
# @return [String, nil]
|
|
24
|
+
attr_reader :api_key_param
|
|
25
|
+
|
|
26
|
+
# @return [SportsOddsAPI::Resources::Events]
|
|
27
|
+
attr_reader :events
|
|
28
|
+
|
|
29
|
+
# @return [SportsOddsAPI::Resources::Teams]
|
|
30
|
+
attr_reader :teams
|
|
31
|
+
|
|
32
|
+
# @return [SportsOddsAPI::Resources::Players]
|
|
33
|
+
attr_reader :players
|
|
34
|
+
|
|
35
|
+
# @return [SportsOddsAPI::Resources::Leagues]
|
|
36
|
+
attr_reader :leagues
|
|
37
|
+
|
|
38
|
+
# @return [SportsOddsAPI::Resources::Sports]
|
|
39
|
+
attr_reader :sports
|
|
40
|
+
|
|
41
|
+
# @return [SportsOddsAPI::Resources::Stats]
|
|
42
|
+
attr_reader :stats
|
|
43
|
+
|
|
44
|
+
# @return [SportsOddsAPI::Resources::Account]
|
|
45
|
+
attr_reader :account
|
|
46
|
+
|
|
47
|
+
# @return [SportsOddsAPI::Resources::Stream]
|
|
48
|
+
attr_reader :stream
|
|
49
|
+
|
|
50
|
+
# @api private
|
|
51
|
+
#
|
|
52
|
+
# @return [Hash{String=>String}]
|
|
53
|
+
private def auth_headers
|
|
54
|
+
{"x-api-key" => @api_key_header}
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# @api private
|
|
58
|
+
#
|
|
59
|
+
# @return [Hash{String=>String}]
|
|
60
|
+
private def auth_query
|
|
61
|
+
{"apiKey" => @api_key_param}
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Creates and returns a new client for interacting with the API.
|
|
65
|
+
#
|
|
66
|
+
# @param api_key_header [String, nil] API key via header Defaults to `ENV["SPORTS_ODDS_API_KEY_HEADER"]`
|
|
67
|
+
#
|
|
68
|
+
# @param api_key_param [String, nil] API key via query param Defaults to `ENV["SPORTS_ODDS_API_KEY_HEADER"]`
|
|
69
|
+
#
|
|
70
|
+
# @param base_url [String, nil] Override the default base URL for the API, e.g.,
|
|
71
|
+
# `"https://api.example.com/v2/"`. Defaults to `ENV["SPORTS_GAME_ODDS_BASE_URL"]`
|
|
72
|
+
#
|
|
73
|
+
# @param max_retries [Integer] Max number of retries to attempt after a failed retryable request.
|
|
74
|
+
#
|
|
75
|
+
# @param timeout [Float]
|
|
76
|
+
#
|
|
77
|
+
# @param initial_retry_delay [Float]
|
|
78
|
+
#
|
|
79
|
+
# @param max_retry_delay [Float]
|
|
80
|
+
def initialize(
|
|
81
|
+
api_key_header: ENV["SPORTS_ODDS_API_KEY_HEADER"],
|
|
82
|
+
api_key_param: ENV["SPORTS_ODDS_API_KEY_HEADER"],
|
|
83
|
+
base_url: ENV["SPORTS_GAME_ODDS_BASE_URL"],
|
|
84
|
+
max_retries: self.class::DEFAULT_MAX_RETRIES,
|
|
85
|
+
timeout: self.class::DEFAULT_TIMEOUT_IN_SECONDS,
|
|
86
|
+
initial_retry_delay: self.class::DEFAULT_INITIAL_RETRY_DELAY,
|
|
87
|
+
max_retry_delay: self.class::DEFAULT_MAX_RETRY_DELAY
|
|
88
|
+
)
|
|
89
|
+
base_url ||= "https://api.sportsgameodds.com/v2"
|
|
90
|
+
|
|
91
|
+
@api_key_header = api_key_header&.to_s
|
|
92
|
+
@api_key_param = api_key_param&.to_s
|
|
93
|
+
|
|
94
|
+
super(
|
|
95
|
+
base_url: base_url,
|
|
96
|
+
timeout: timeout,
|
|
97
|
+
max_retries: max_retries,
|
|
98
|
+
initial_retry_delay: initial_retry_delay,
|
|
99
|
+
max_retry_delay: max_retry_delay
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
@events = SportsOddsAPI::Resources::Events.new(client: self)
|
|
103
|
+
@teams = SportsOddsAPI::Resources::Teams.new(client: self)
|
|
104
|
+
@players = SportsOddsAPI::Resources::Players.new(client: self)
|
|
105
|
+
@leagues = SportsOddsAPI::Resources::Leagues.new(client: self)
|
|
106
|
+
@sports = SportsOddsAPI::Resources::Sports.new(client: self)
|
|
107
|
+
@stats = SportsOddsAPI::Resources::Stats.new(client: self)
|
|
108
|
+
@account = SportsOddsAPI::Resources::Account.new(client: self)
|
|
109
|
+
@stream = SportsOddsAPI::Resources::Stream.new(client: self)
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SportsOddsAPI
|
|
4
|
+
module Errors
|
|
5
|
+
class Error < StandardError
|
|
6
|
+
# @!attribute cause
|
|
7
|
+
#
|
|
8
|
+
# @return [StandardError, nil]
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
class ConversionError < SportsOddsAPI::Errors::Error
|
|
12
|
+
# @return [StandardError, nil]
|
|
13
|
+
def cause = @cause.nil? ? super : @cause
|
|
14
|
+
|
|
15
|
+
# @api private
|
|
16
|
+
#
|
|
17
|
+
# @param on [Class<StandardError>]
|
|
18
|
+
# @param method [Symbol]
|
|
19
|
+
# @param target [Object]
|
|
20
|
+
# @param value [Object]
|
|
21
|
+
# @param cause [StandardError, nil]
|
|
22
|
+
def initialize(on:, method:, target:, value:, cause: nil)
|
|
23
|
+
cls = on.name.split("::").last
|
|
24
|
+
|
|
25
|
+
message = [
|
|
26
|
+
"Failed to parse #{cls}.#{method} from #{value.class} to #{target.inspect}.",
|
|
27
|
+
"To get the unparsed API response, use #{cls}[#{method.inspect}].",
|
|
28
|
+
cause && "Cause: #{cause.message}"
|
|
29
|
+
].filter(&:itself).join(" ")
|
|
30
|
+
|
|
31
|
+
@cause = cause
|
|
32
|
+
super(message)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
class APIError < SportsOddsAPI::Errors::Error
|
|
37
|
+
# @return [URI::Generic]
|
|
38
|
+
attr_accessor :url
|
|
39
|
+
|
|
40
|
+
# @return [Integer, nil]
|
|
41
|
+
attr_accessor :status
|
|
42
|
+
|
|
43
|
+
# @return [Hash{String=>String}, nil]
|
|
44
|
+
attr_accessor :headers
|
|
45
|
+
|
|
46
|
+
# @return [Object, nil]
|
|
47
|
+
attr_accessor :body
|
|
48
|
+
|
|
49
|
+
# @api private
|
|
50
|
+
#
|
|
51
|
+
# @param url [URI::Generic]
|
|
52
|
+
# @param status [Integer, nil]
|
|
53
|
+
# @param headers [Hash{String=>String}, nil]
|
|
54
|
+
# @param body [Object, nil]
|
|
55
|
+
# @param request [nil]
|
|
56
|
+
# @param response [nil]
|
|
57
|
+
# @param message [String, nil]
|
|
58
|
+
def initialize(url:, status: nil, headers: nil, body: nil, request: nil, response: nil, message: nil)
|
|
59
|
+
@url = url
|
|
60
|
+
@status = status
|
|
61
|
+
@headers = headers
|
|
62
|
+
@body = body
|
|
63
|
+
@request = request
|
|
64
|
+
@response = response
|
|
65
|
+
super(message)
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
class APIConnectionError < SportsOddsAPI::Errors::APIError
|
|
70
|
+
# @!attribute status
|
|
71
|
+
#
|
|
72
|
+
# @return [nil]
|
|
73
|
+
|
|
74
|
+
# @!attribute body
|
|
75
|
+
#
|
|
76
|
+
# @return [nil]
|
|
77
|
+
|
|
78
|
+
# @api private
|
|
79
|
+
#
|
|
80
|
+
# @param url [URI::Generic]
|
|
81
|
+
# @param status [nil]
|
|
82
|
+
# @param headers [Hash{String=>String}, nil]
|
|
83
|
+
# @param body [nil]
|
|
84
|
+
# @param request [nil]
|
|
85
|
+
# @param response [nil]
|
|
86
|
+
# @param message [String, nil]
|
|
87
|
+
def initialize(
|
|
88
|
+
url:,
|
|
89
|
+
status: nil,
|
|
90
|
+
headers: nil,
|
|
91
|
+
body: nil,
|
|
92
|
+
request: nil,
|
|
93
|
+
response: nil,
|
|
94
|
+
message: "Connection error."
|
|
95
|
+
)
|
|
96
|
+
super
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
class APITimeoutError < SportsOddsAPI::Errors::APIConnectionError
|
|
101
|
+
# @api private
|
|
102
|
+
#
|
|
103
|
+
# @param url [URI::Generic]
|
|
104
|
+
# @param status [nil]
|
|
105
|
+
# @param headers [Hash{String=>String}, nil]
|
|
106
|
+
# @param body [nil]
|
|
107
|
+
# @param request [nil]
|
|
108
|
+
# @param response [nil]
|
|
109
|
+
# @param message [String, nil]
|
|
110
|
+
def initialize(
|
|
111
|
+
url:,
|
|
112
|
+
status: nil,
|
|
113
|
+
headers: nil,
|
|
114
|
+
body: nil,
|
|
115
|
+
request: nil,
|
|
116
|
+
response: nil,
|
|
117
|
+
message: "Request timed out."
|
|
118
|
+
)
|
|
119
|
+
super
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
class APIStatusError < SportsOddsAPI::Errors::APIError
|
|
124
|
+
# @api private
|
|
125
|
+
#
|
|
126
|
+
# @param url [URI::Generic]
|
|
127
|
+
# @param status [Integer]
|
|
128
|
+
# @param headers [Hash{String=>String}, nil]
|
|
129
|
+
# @param body [Object, nil]
|
|
130
|
+
# @param request [nil]
|
|
131
|
+
# @param response [nil]
|
|
132
|
+
# @param message [String, nil]
|
|
133
|
+
#
|
|
134
|
+
# @return [self]
|
|
135
|
+
def self.for(url:, status:, headers:, body:, request:, response:, message: nil)
|
|
136
|
+
kwargs =
|
|
137
|
+
{
|
|
138
|
+
url: url,
|
|
139
|
+
status: status,
|
|
140
|
+
headers: headers,
|
|
141
|
+
body: body,
|
|
142
|
+
request: request,
|
|
143
|
+
response: response,
|
|
144
|
+
message: message
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
case status
|
|
148
|
+
in 400
|
|
149
|
+
SportsOddsAPI::Errors::BadRequestError.new(**kwargs)
|
|
150
|
+
in 401
|
|
151
|
+
SportsOddsAPI::Errors::AuthenticationError.new(**kwargs)
|
|
152
|
+
in 403
|
|
153
|
+
SportsOddsAPI::Errors::PermissionDeniedError.new(**kwargs)
|
|
154
|
+
in 404
|
|
155
|
+
SportsOddsAPI::Errors::NotFoundError.new(**kwargs)
|
|
156
|
+
in 409
|
|
157
|
+
SportsOddsAPI::Errors::ConflictError.new(**kwargs)
|
|
158
|
+
in 422
|
|
159
|
+
SportsOddsAPI::Errors::UnprocessableEntityError.new(**kwargs)
|
|
160
|
+
in 429
|
|
161
|
+
SportsOddsAPI::Errors::RateLimitError.new(**kwargs)
|
|
162
|
+
in (500..)
|
|
163
|
+
SportsOddsAPI::Errors::InternalServerError.new(**kwargs)
|
|
164
|
+
else
|
|
165
|
+
SportsOddsAPI::Errors::APIStatusError.new(**kwargs)
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
# @!parse
|
|
170
|
+
# # @return [Integer]
|
|
171
|
+
# attr_accessor :status
|
|
172
|
+
|
|
173
|
+
# @api private
|
|
174
|
+
#
|
|
175
|
+
# @param url [URI::Generic]
|
|
176
|
+
# @param status [Integer]
|
|
177
|
+
# @param headers [Hash{String=>String}, nil]
|
|
178
|
+
# @param body [Object, nil]
|
|
179
|
+
# @param request [nil]
|
|
180
|
+
# @param response [nil]
|
|
181
|
+
# @param message [String, nil]
|
|
182
|
+
def initialize(url:, status:, headers:, body:, request:, response:, message: nil)
|
|
183
|
+
message ||= {url: url.to_s, status: status, body: body}
|
|
184
|
+
super(
|
|
185
|
+
url: url,
|
|
186
|
+
status: status,
|
|
187
|
+
headers: headers,
|
|
188
|
+
body: body,
|
|
189
|
+
request: request,
|
|
190
|
+
response: response,
|
|
191
|
+
message: message&.to_s
|
|
192
|
+
)
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
class BadRequestError < SportsOddsAPI::Errors::APIStatusError
|
|
197
|
+
HTTP_STATUS = 400
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
class AuthenticationError < SportsOddsAPI::Errors::APIStatusError
|
|
201
|
+
HTTP_STATUS = 401
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
class PermissionDeniedError < SportsOddsAPI::Errors::APIStatusError
|
|
205
|
+
HTTP_STATUS = 403
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
class NotFoundError < SportsOddsAPI::Errors::APIStatusError
|
|
209
|
+
HTTP_STATUS = 404
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
class ConflictError < SportsOddsAPI::Errors::APIStatusError
|
|
213
|
+
HTTP_STATUS = 409
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
class UnprocessableEntityError < SportsOddsAPI::Errors::APIStatusError
|
|
217
|
+
HTTP_STATUS = 422
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
class RateLimitError < SportsOddsAPI::Errors::APIStatusError
|
|
221
|
+
HTTP_STATUS = 429
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
class InternalServerError < SportsOddsAPI::Errors::APIStatusError
|
|
225
|
+
HTTP_STATUS = (500..)
|
|
226
|
+
end
|
|
227
|
+
end
|
|
228
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SportsOddsAPI
|
|
4
|
+
class FilePart
|
|
5
|
+
# @return [Pathname, StringIO, IO, String]
|
|
6
|
+
attr_reader :content
|
|
7
|
+
|
|
8
|
+
# @return [String, nil]
|
|
9
|
+
attr_reader :content_type
|
|
10
|
+
|
|
11
|
+
# @return [String, nil]
|
|
12
|
+
attr_reader :filename
|
|
13
|
+
|
|
14
|
+
# @api private
|
|
15
|
+
#
|
|
16
|
+
# @return [String]
|
|
17
|
+
private def read
|
|
18
|
+
case content
|
|
19
|
+
in Pathname
|
|
20
|
+
content.read(binmode: true)
|
|
21
|
+
in StringIO
|
|
22
|
+
content.string
|
|
23
|
+
in IO
|
|
24
|
+
content.read
|
|
25
|
+
in String
|
|
26
|
+
content
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# @param a [Object]
|
|
31
|
+
#
|
|
32
|
+
# @return [String]
|
|
33
|
+
def to_json(*a) = read.to_json(*a)
|
|
34
|
+
|
|
35
|
+
# @param a [Object]
|
|
36
|
+
#
|
|
37
|
+
# @return [String]
|
|
38
|
+
def to_yaml(*a) = read.to_yaml(*a)
|
|
39
|
+
|
|
40
|
+
# @param content [Pathname, StringIO, IO, String]
|
|
41
|
+
# @param filename [String, nil]
|
|
42
|
+
# @param content_type [String, nil]
|
|
43
|
+
def initialize(content, filename: nil, content_type: nil)
|
|
44
|
+
@content = content
|
|
45
|
+
@filename =
|
|
46
|
+
case content
|
|
47
|
+
in Pathname
|
|
48
|
+
filename.nil? ? content.basename.to_path : ::File.basename(filename)
|
|
49
|
+
else
|
|
50
|
+
filename.nil? ? nil : ::File.basename(filename)
|
|
51
|
+
end
|
|
52
|
+
@content_type = content_type
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|