https://github.com/popmonkey/irdata
Golang module to simplify access to the iRacing /data API
https://github.com/popmonkey/irdata
iracing iracing-api iracing-data-api
Last synced: 4 months ago
JSON representation
Golang module to simplify access to the iRacing /data API
- Host: GitHub
- URL: https://github.com/popmonkey/irdata
- Owner: popmonkey
- License: mit
- Created: 2024-06-05T20:28:00.000Z (about 2 years ago)
- Default Branch: main
- Last Pushed: 2026-02-17T18:44:50.000Z (4 months ago)
- Last Synced: 2026-02-17T23:53:57.317Z (4 months ago)
- Topics: iracing, iracing-api, iracing-data-api
- Language: Go
- Homepage:
- Size: 115 KB
- Stars: 5
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# irdata
[](https://github.com/popmonkey/irdata/actions/workflows/ci.yml)
[](https://github.com/popmonkey/irdata/actions/workflows/integration.yml)
A Go module for simplified access to the iRacing `/data` API.
## Features
* **Modern Authentication**: Supports iRacing's **OAuth2 Password Limited Flow**
* **Simplified Management**: Handles token acquisition, masking, and credential encryption locally.
* **Transparent Data Fetching**: Follows and dereferences iRacing's S3 links transparently.
* **Automatic Chunk Merging**: If an endpoint returns chunked data, `irdata` fetches all chunks and merges them into a single object.
* **Caching Layer**: An optional disk-based cache to minimize API calls.
* **Resiliency**: Built-in support for automatic retries on server errors (`5xx`) and configurable handling for rate limits (`429`).
## Installation
```sh
go get github.com/popmonkey/irdata
```
## Quick Start
The following is a complete example of setting up the client, authenticating, and fetching data with caching enabled.
```go
package main
import (
"context"
"encoding/json"
"fmt"
"log"
"time"
"github.com/popmonkey/irdata"
)
func main() {
// Create a new client instance.
api := irdata.Open(context.Background())
defer api.Close()
// Authenticate using credentials stored in a file.
err := api.AuthWithCredsFromFile("path/to/my.key", "path/to/my.creds")
if err != nil {
log.Fatalf("Authentication failed: %v", err)
}
// Enable the cache
if err := api.EnableCache(".ir-cache"); err != nil {
log.Fatalf("Failed to enable cache: %v", err)
}
// Fetch data from an API endpoint (member info for these creds)
jsonData, err := api.GetWithCache("/data/member/info", 15*time.Minute)
if err != nil {
log.Fatalf("Failed to get member info: %v", err)
}
// Unmarshal the JSON response into a struct.
var memberInfo struct {
DisplayName string `json:"display_name"`
CustomerID int `json:"cust_id"`
}
if err := json.Unmarshal(jsonData, &memberInfo); err != nil {
log.Fatalf("Failed to parse JSON: %v", err)
}
fmt.Printf("Successfully fetched data for %s (Customer ID: %d)\n",
memberInfo.DisplayName, memberInfo.CustomerID)
}
```
---
## Authentication
The library uses the **OAuth2 Password Limited Flow**. This requires you to register your "headless" client with iRacing support to obtain a **Client ID** and **Client Secret**, in addition to your standard iRacing username and password.
See: https://oauth.iracing.com/oauth2/book/password_limited_flow.html
> [!IMPORTANT]
> **Migration Note:** If you used previous versions of `irdata`, your existing `.creds` files are incompatible. You must delete them and regenerate them using the new flow to include your Client ID and Secret.
### Encrypted Credential File (Recommended)
You can store credentials in a file encrypted with a key file. First, generate a key file.
#### Create the Key File
The key must be a random string of 16, 24, or 32 bytes, base64 encoded, and stored in a file with user-only read permissions (`0400`).
```sh
# Example for Linux or macOS
openssl rand -base64 32 > ~/my.key && chmod 0400 ~/my.key
```
> [!WARNING]
> Do not commit your key file or credentials file to version control.
#### Using the `irfetch` utility (Recommended)
The `irfetch` command-line utility is included with releases, or can be built from source with `make`. It is the easiest way to generate your credentials file.
If the credentials file does not exist, `irfetch` will automatically prompt you for your iRacing credentials (Username, Password, Client ID, and Client Secret), and then create the encrypted file for you.
To generate the credentials file without fetching any data, use the `-a` (auth and stop) flag. You must still provide a valid API endpoint like `/data/doc` as the final argument.
```sh
# This will prompt for credentials and save them to ~/my.creds, then exit.
irfetch -a ~/my.key ~/my.creds /data/doc
```
After the `my.creds` file is created, your application can use `api.AuthWithCredsFromFile("~/my.key", "~/my.creds")` to authenticate without needing any interactive prompts.
#### Save and Load Credentials
Use the key file to save your credentials once. The helper `CredsFromTerminal` will prompt you for your Username, Password, Client ID, and Client Secret.
```go
// Define file paths for the key and encrypted credentials
keyFile := "my.key"
credsFile := "my.creds"
// First-time setup to save credentials from terminal input
var credsProvider irdata.CredsFromTerminal
err := api.AuthAndSaveProvidedCredsToFile(keyFile, credsFile, credsProvider)
if err != nil {
log.Fatalf("Failed to save credentials: %v", err)
}
// In subsequent runs, you can authenticate directly from the file
err = api.AuthWithCredsFromFile(keyFile, credsFile)
if err != nil {
log.Fatalf("Failed to auth from file: %v", err)
}
```
### Auth Token Persistence (Optional)
To reduce the number of full login requests, you can persist the OAuth2 token to a file. The library will attempt to reuse or refresh this token before falling back to full credentials authentication.
The token file is encrypted using the **same key file** as your credentials, so this feature works in tandem with `AuthWithCredsFromFile`.
```go
// Configure the persistence file
api.SetAuthTokenFile("my.token")
// Authenticate using the encrypted credentials.
// The library will check "my.token" first.
err := api.AuthWithCredsFromFile("my.key", "my.creds")
```
### Programmatic Credentials
You can provide credentials directly in your code by implementing the `CredsProvider` interface.
```go
type MyCredsProvider struct{}
func (p MyCredsProvider) GetCreds() ([]byte, []byte, []byte, []byte, error) {
username := "your_email@example.com"
password := "your_password"
clientId := "your_client_id"
clientSecret := "your_client_secret"
return []byte(username), []byte(password), []byte(clientId), []byte(clientSecret), nil
}
var provider MyCredsProvider
err := api.AuthWithProvideCreds(provider)
if err != nil {
log.Fatalf("Auth failed: %v", err)
}
```
### Terminal Prompt
To prompt for credentials from the terminal interactively:
```go
var provider irdata.CredsFromTerminal
err := api.AuthWithProvideCreds(provider)
if err != nil {
log.Fatalf("Auth failed: %v", err)
}
```
---
## API Usage
Once authenticated, use `Get()` or `GetWithCache()` to access API endpoints.
### Basic Fetch
`Get()` retrieves data directly from the iRacing API.
```go
// Get member info
data, err := api.Get("/data/member/info")
if err != nil {
// handle error
}
// Unmarshal and use data...
```
### Cached Fetch
The iRacing API has a rate limit. Using the cache is highly recommended to avoid interruptions.
First, enable the cache. This should only be done once.
```go
err := api.EnableCache(".cache")
if err != nil {
// handle error
}
```
You can optionally configure the cache block size (the maximum size of each individual data file in the cache) before enabling it. This does not limit the overall size of the cache. The default is 1MB.
```go
// Set individual cache data file size to 10MB
api.SetCacheMaxDatafileSize(10 * 1024 * 1024)
api.EnableCache(".cache")
```
Then use `GetWithCache()` which first checks the cache for the requested data. On a cache hit, it returns the cached data immediately. On a cache miss, it calls the main API, returns the result, and populates the cache with the new data for the specified time-to-live (TTL) duration.
```go
// This call will hit the iRacing API only if the data is not in the cache
// or if the cached data is older than 15 minutes.
data, err := api.GetWithCache("/data/member/info", 15*time.Minute)
```
#### Closing the Cache
When you are finished, you should call `Close()` to ensure the cache is properly compacted and closed.
```go
defer api.Close()
```
If you need to close the application quickly and do not want to wait for the cache to merge/compact, you can use `CloseFast()`.
```go
defer api.CloseFast()
```
---
## Handling Chunked Responses
Some API endpoints (e.g., `/data/results/search_series`) return large datasets in chunks. `irdata` automatically detects this, fetches all chunks, and merges the results into a new `_chunk_data` field in the JSON response.
For a response that originally contains a `chunk_info` block, `irdata` adds the `_chunk_data` array containing the merged content from all chunks.
```json
{
"some_other_data": "value",
"chunk_info": {
"num_chunks": 2,
"base_download_url": "...",
"chunk_file_names": ["chunk_0.json", "chunk_1.json"]
},
"_chunk_data": [
{ "result_id": 1 },
{ "result_id": 2 }
]
}
```
---
## S3 Link Callback
Some API endpoints return a link to data stored on S3 instead of the data itself. `irdata` automatically follows these links and returns the data from the S3 bucket.
If you need to know which S3 link is being followed for a given request, you can set a callback function. This is useful for debugging or logging.
```go
// Set a callback to print any S3 link that is followed.
api.SetS3LinkCallback(func(link string) {
fmt.Printf("Following S3 link: %s\n", link)
})
// When you make a request that returns an S3 link, the callback will be invoked.
data, err := api.Get("/data/some_endpoint_with_s3_link")
```
---
## Error Handling
### Rate Limit Management
`irdata` can handle rate limits in two ways. You can change the behavior with `SetRateLimitHandler()`.
#### Return an Error (Default)
By default, `Get()` or `GetWithCache()` will return an `irdata.RateLimitExceededError` if the rate limit is hit. You can check for this specific error to handle it gracefully. This error includes a timestamp value which is the reset time after which iRacing will no longer rate limit you.
```go
import "errors"
// ...
data, err := api.Get("/data/member/info")
if err != nil {
var rateLimitErr *irdata.RateLimitExceededError
if errors.As(err, &rateLimitErr) {
fmt.Printf("Rate limit exceeded. Please wait until %v to retry.\n", rateLimitErr.ResetTime)
} else {
// Handle other errors
log.Fatal(err)
}
}
```
#### Wait and Continue
Alternatively, configure `irdata` to pause and automatically retry the request after the rate limit resets. In this mode, the call will block until it succeeds.
```go
api.SetRateLimitHandler(irdata.RateLimitWait)
// This call will now block and wait if the rate limit is hit
// instead of returning an error.
data, err := api.Get("/data/member/info")
```
### Automatic Retries
For server-side errors (HTTP `5xx` status codes), `irdata` will automatically retry the request with an increasing backoff period. You can configure the number of retries. The default is 0 (no retries):
```go
// Set the number of retries to 10
api.SetRetries(10)
```
---
## Logging
`irdata` uses `logrus` for logging. By default, only errors are logged but more detailed logging can be enabled.
```go
api.SetLogLevel(irdata.LogLevelInfo)
```
---
## Development
Clone the repository:
```sh
git clone git@github.com:popmonkey/irdata.git
```
> [!NOTE]
> Key files included in the repository for testing must have their permissions set to `0400`.
> ```sh
> chmod 0400 testdata/test.key
> ```
Run tests:
```sh
# Run standard tests (no API calls)
go test .
# Run integration tests against the live iRacing API
# Requires valid key and creds files created beforehand
IRDATA_TEST_KEY_FILE=/path/to/key.file \
IRDATA_TEST_CREDS_FILE=/path/to/creds.file \
go test -tags=integration -v .
```
For CI environments (see `.github/workflows/integration.yml`), you can alternatively provide the base64-encoded content of the key and credentials files using `IRDATA_TEST_KEY_DATA` and `IRDATA_TEST_CREDS_DATA`.