hans-peter-ording/sleeper-api-client
A PHP client for the Sleeper fantasy sports API, giving Laravel and standalone PHP apps typed, convenient access to users, leagues, rosters, drafts, matchups, transactions, and player data without hand-rolling HTTP calls or parsing raw JSON responses.
This is a one-star, zero-dependent package, so treat the README and src/ as the documentation — read the source before trusting any blog post. The good news: the Sleeper API it wraps is a plain, unauthenticated JSON API (https://api.sleeper.app/v1/...), so there is no API key, OAuth dance, or config file waiting to trip you up. You only need a Sleeper user_id (or username, resolved via /v1/user/{username}).
composer require hans-peter-ording/sleeper-api-client
Skim the namespace layout in vendor/hans-peter-ording/sleeper-api-client/src. Most clients in this family follow the Saloon pattern — a connector plus one request class per endpoint — so you'll typically see something like a SleeperConnector and request classes such as GetUserRequest, GetLeagueRequest, GetRostersRequest.
In Laravel, bind the connector as a singleton in a service provider (auto-discovery may or may not be wired — check composer.json for extra.laravel.providers). Otherwise, resolve it from the container or instantiate it directly.
First use case: "given a user, list their NFL leagues for the current season, then show that league's rosters." The traversal is:
GET /v1/user/{user_id}
GET /v1/user/{user_id}/leagues/nfl/{season}
GET /v1/leagues/{league_id}
GET /v1/leagues/{league_id}/rosters
GET /v1/leagues/{league_id}/users
Don't hardcode {season}. Fetch /v1/state/nfl first — it returns season, week, league_season, and season_type, and the API's notion of "current" will surprise you mid-offseason.
Wrap the client in your own repository. Don't let request classes leak into controllers. A SleeperRepository that exposes intent-named methods (currentLeaguesFor($user), matchups($leagueId, $week)) gives you one place to add caching, retries, and DTO-to-model mapping. This is doubly important for a package with 1 star — you want a seam to swap it out.
Cache aggressively and at the right layer. Sleeper league data is essentially static within a season; rosters change weekly; draft picks change in seconds during a live draft. Use Cache::remember with different TTLs per endpoint (24h for user/league/rosters, 5–60s for drafts). Never cache /v1/players/nfl per request — see Gotchas.
Map to Eloquent, but keep raw IDs. DTOs from the package are fine for reads, but persist Sleeper's string IDs (user_id, league_id, roster_id, player_id) as string columns. Sleeper player IDs look numeric — storing them as integers will bite you when they don't fit or when you compare against other systems.
Poll, because there are no webhooks. Sleeper exposes no push notifications. Drafts and transactions are pull-only. Schedule a job (->everyFiveSeconds() during a live draft, hourly otherwise) that fetches the draft picks and diffs against what you have. Back the job off outside draft windows.
Fall back to Laravel's HTTP client for gaps. The package likely doesn't cover every endpoint (trending players, traded picks, transactions, drafts are easy to miss). A small SleeperApi class using Http::baseUrl('https://api.sleeper.app/v1') covers the long tail without forking.
Test by swapping the HTTP layer, not the package. If it's Saloon, mock the connector with MockClient; if it uses PSR-18 directly, inject a GuzzleHttp\Handler\MockHandler-backed client. Avoid hitting the live API in tests — it's public but you'll get flaky CI.
Validate inputs at the boundary. Season and week are strings in the URL; enforce is_numeric and reasonable bounds before they reach the request class.
"hans-peter-ording/sleeper-api-client": "1.0.*" or a commit hash) and keep a fork ready. With zero dependents and one star, upstream maintenance is a question mark. Read the last commit date before committing to it./v1/players/nfl dump is huge — tens of megabytes of JSON, containing every player ever rostered. Fetch it once in a scheduled command, write it to disk or a players table, and query locally. Do not call it in a request lifecycle.200 with sparse or null payloads for unknown IDs and empty weeks. The package may or may not surface this as an exception. Check ->successful() / ->failed() explicitly rather than assuming a DTO means the data exists.nfl and 2024, not enums. Passing an integer season will silently 404 on some endpoints and work on others.retry(3, 100) on transient 5xx./v1/state/nfl is your clock. Use it for "current week" in matchups and transactions, not Carbon::now(). NFL weeks don't align with calendar weeks, and the offseason flips season_type to off.$connector->withToken() style middleware and ->retry() are already there to use.$response->body() and compare against Sleeper's docs — the API evolves quietly, and a small client like this won't always keep up.How can I help you explore Laravel packages today?