Whoop.Sdk
2.0.0
dotnet add package Whoop.Sdk --version 2.0.0
NuGet\Install-Package Whoop.Sdk -Version 2.0.0
<PackageReference Include="Whoop.Sdk" Version="2.0.0" />
<PackageVersion Include="Whoop.Sdk" Version="2.0.0" />
<PackageReference Include="Whoop.Sdk" />
paket add Whoop.Sdk --version 2.0.0
#r "nuget: Whoop.Sdk, 2.0.0"
#:package Whoop.Sdk@2.0.0
#addin nuget:?package=Whoop.Sdk&version=2.0.0
#tool nuget:?package=Whoop.Sdk&version=2.0.0
Whoop.Sdk
A dependency-light .NET client for the WHOOP v2 Developer API, generated by hand from the published OpenAPI document at https://api.prod.whoop.com/developer/doc/openapi.json.
- Targets
netstandard2.0,netstandard2.1,net8.0andnet10.0— usable from .NET Framework 4.6.2+, .NET Core, .NET 5 through .NET 10, Xamarin, MAUI, Unity and Mono. - Complete v2 surface: cycles, sleep, recovery, workouts, user profile and body measurements, the v1 activity ID mapping, and the trusted-partner lab endpoints.
- OAuth2 built in: authorization-code URL building, code exchange, automatic refresh with rotation, and the partner client-credentials flow.
- Cursor pagination as
IAsyncEnumerable<T>so you canawait foreacha whole date range. - No dependencies at all on
net8.0andnet10.0—System.Text.JsonandIAsyncEnumerable<T>are in-box there. The .NET Standard targets addSystem.Text.JsonandMicrosoft.Bcl.AsyncInterfaces, and nothing else.
Target frameworks
| Target | Dependencies | Notes |
|---|---|---|
net10.0 |
none | LTS, supported through 10 Nov 2028. Exact-match asset for current apps. |
net8.0 |
none | LTS, supported through 10 Nov 2026. |
netstandard2.1 |
System.Text.Json, Microsoft.Bcl.AsyncInterfaces |
.NET Core 3.x, Xamarin, Unity. |
netstandard2.0 |
System.Text.Json, Microsoft.Bcl.AsyncInterfaces |
.NET Framework 4.6.2+, Mono. |
There is no dedicated net9.0 asset and none is needed: a net9.0 app resolves the net8.0 one, still dependency-free. .NET 9 was STS and reached end of support on 12 May 2026.
The Microsoft.Extensions.* versions in the DI package are a floor, not a pin: an app already on a newer patch or major resolves upward.
Packages
| Package | Purpose | Latest |
|---|---|---|
Whoop.Sdk |
The client itself. | |
Whoop.Sdk.Extensions.DependencyInjection |
IServiceCollection / IHttpClientFactory wiring. |
dotnet add package Whoop.Sdk
dotnet add package Whoop.Sdk.Extensions.DependencyInjection # only if you want the DI wiring
Quick start
using Whoop.Sdk;
using Whoop.Sdk.Models;
using var whoop = new WhoopClient("<access-token>");
var profile = await whoop.User.GetBasicProfileAsync();
Console.WriteLine($"{profile.FirstName} {profile.LastName}");
var recovery = await whoop.Recovery.ListAsync(new WhoopCollectionRequest { Limit = 10 });
foreach (var record in recovery.Records)
{
Console.WriteLine($"{record.CreatedAt:d}: {record.Score?.RecoveryScorePercentage}%");
}
Streaming a date range
EnumerateAsync follows the next_token cursor for you and only fetches a page when you consume one.
var lastMonth = new WhoopCollectionRequest
{
Start = DateTimeOffset.UtcNow.AddDays(-30),
Limit = 25,
};
await foreach (var workout in whoop.Workouts.EnumerateAsync(lastMonth))
{
Console.WriteLine($"{workout.SportName} - strain {workout.Score?.Strain:F1}");
}
Authentication
Authorization code flow
using Whoop.Sdk.Authentication;
using var http = new HttpClient();
var oauth = new WhoopOAuthClient(http, "<client-id>", "<client-secret>");
// 1. Send the user here. WHOOP requires a state value of at least eight characters.
var consentUrl = oauth.CreateAuthorizationUrl(
new Uri("https://example.com/callback"),
new[] { WhoopScopes.ReadProfile, WhoopScopes.ReadRecovery, WhoopScopes.Offline },
state: Guid.NewGuid().ToString("N"));
// 2. Exchange the code from the redirect.
var token = await oauth.ExchangeAuthorizationCodeAsync(code, new Uri("https://example.com/callback"));
Keeping tokens fresh
RefreshingWhoopTokenProvider hands out the cached access token until it is about to expire, refreshes exactly once even under concurrency, and calls you back so you can persist the rotated refresh token — WHOOP invalidates the previous one on every refresh.
var tokens = new RefreshingWhoopTokenProvider(
oauth,
refreshToken: storedRefreshToken,
initialToken: token,
onTokenRefreshed: async (refreshed, ct) => await store.SaveAsync(refreshed.RefreshToken!, ct));
using var whoop = new WhoopClient(new WhoopClientOptions { TokenProvider = tokens });
Trusted partners
using var tokenTransport = new HttpClient { BaseAddress = WhoopClientOptions.DefaultBaseAddress };
var partnerTokens = new PartnerWhoopTokenProvider(tokenTransport, "<client-id>", "<client-secret>");
using var whoop = new WhoopClient(new WhoopClientOptions { TokenProvider = partnerTokens });
var requisition = await whoop.Partner.GetLabRequisitionAsync(requisitionId);
Dependency injection
using Whoop.Sdk.Extensions.DependencyInjection;
builder.Services.AddWhoopAccessToken(token); // or register your own IWhoopTokenProvider
builder.Services.AddWhoopClient() // typed HttpClient + auth handler
.AddStandardResilienceHandler(); // any IHttpClientBuilder extension works
For trusted partners, AddWhoopPartnerAuthentication registers a token provider that acquires tokens over a separate unauthenticated pipeline, so token acquisition cannot recurse through the auth handler.
Samples
Four runnable projects live in samples/ — see samples/README.md for how to run each.
| Sample | Shows |
|---|---|
Whoop.Sdk.Samples.QuickStart |
One access token, no DI: profile, today's cycle, recent recovery, workout totals. |
Whoop.Sdk.Samples.Worker |
Generic Host, IWhoopClient in a BackgroundService, static token or auto-refresh, resilience handler. |
Whoop.Sdk.Samples.OAuthWebApp |
Full authorization-code flow with a per-request scoped token provider. |
Whoop.Sdk.Samples.TrustedPartner |
Client-credentials partner auth, lab requisitions and result upload. |
Error handling
Every non-success response becomes a WhoopApiException carrying the status code, the raw body, and the request URI. HTTP 429 becomes a WhoopRateLimitExceededException exposing RetryAfter.
try
{
var cycle = await whoop.Cycles.GetAsync(cycleId);
}
catch (WhoopRateLimitExceededException ex)
{
await Task.Delay(ex.RetryAfter ?? TimeSpan.FromSeconds(30));
}
catch (WhoopApiException ex) when (ex.IsNotFound)
{
// no such cycle
}
WHOOP's documented limits are 100 requests per minute and 10,000 per day per user. Combine AddWhoopClient() with Microsoft.Extensions.Http.Resilience if you want automatic retries.
Scores may be absent
Cycle, Sleep, Recovery and Workout all carry a ScoreState. Only ScoreState.Scored guarantees a populated Score; PendingScore means "retry later" and Unscorable means it will never arrive. Unrecognised future values deserialize to ScoreState.Unknown rather than throwing.
API coverage
| Endpoint | Member |
|---|---|
GET /v2/cycle |
Cycles.ListAsync / Cycles.EnumerateAsync |
GET /v2/cycle/{cycleId} |
Cycles.GetAsync |
GET /v2/cycle/{cycleId}/sleep |
Cycles.GetSleepAsync, Sleep.GetForCycleAsync |
GET /v2/cycle/{cycleId}/recovery |
Cycles.GetRecoveryAsync, Recovery.GetForCycleAsync |
GET /v2/recovery |
Recovery.ListAsync / Recovery.EnumerateAsync |
GET /v2/activity/sleep |
Sleep.ListAsync / Sleep.EnumerateAsync |
GET /v2/activity/sleep/{sleepId} |
Sleep.GetAsync |
GET /v2/activity/workout |
Workouts.ListAsync / Workouts.EnumerateAsync |
GET /v2/activity/workout/{workoutId} |
Workouts.GetAsync |
GET /v2/user/profile/basic |
User.GetBasicProfileAsync |
GET /v2/user/measurement/body |
User.GetBodyMeasurementAsync |
DELETE /v2/user/access |
User.RevokeAccessAsync |
GET /v1/activity-mapping/{activityV1Id} |
ActivityMappings.GetAsync |
POST /v2/partner/token |
Partner.RequestTokenAsync |
GET /v2/partner/requisition/{id} |
Partner.GetLabRequisitionAsync |
PATCH /v2/partner/requisition/{id}/status |
Partner.UpdateLabRequisitionStatusAsync |
GET /v2/partner/service-request/{id} |
Partner.GetServiceRequestAsync |
PATCH /v2/partner/service-request/{id}/status |
Partner.UpdateServiceRequestStatusAsync |
POST /v2/partner/service-request/{id}/results |
Partner.UploadDiagnosticReportResultsAsync |
POST /v2/partner/development/add-test-data |
Partner.AddTestDataAsync |
Anything not wrapped yet is reachable through WhoopClient.Connection, which exposes the raw typed SendAsync used by every endpoint client.
Building
dotnet build Whoop.Sdk.slnx
dotnet test Whoop.Sdk.slnx
dotnet pack Whoop.Sdk.slnx -c Release # packages land in artifacts/packages
The vendored copy of the spec this client was written against lives in assets/whoop-openapi.json.
License
MIT. Not affiliated with or endorsed by WHOOP, Inc.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
| .NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.0 is compatible. netstandard2.1 is compatible. |
| .NET Framework | net461 was computed. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen40 was computed. tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.0
- Microsoft.Bcl.AsyncInterfaces (>= 10.0.11)
- System.Text.Json (>= 10.0.11)
-
.NETStandard 2.1
- Microsoft.Bcl.AsyncInterfaces (>= 10.0.11)
- System.Text.Json (>= 10.0.11)
-
net10.0
- No dependencies.
-
net8.0
- No dependencies.
NuGet packages (1)
Showing the top 1 NuGet packages that depend on Whoop.Sdk:
| Package | Downloads |
|---|---|
|
Whoop.Sdk.Extensions.DependencyInjection
Microsoft.Extensions.DependencyInjection and IHttpClientFactory integration for Whoop.Sdk. |
GitHub repositories
This package is not used by any popular GitHub repositories.