whoop

package
v1.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 20, 2026 License: MIT Imports: 17 Imported by: 0

Documentation

Overview

Package whoop provides a production-grade Go client for the WHOOP Developer API (v2).

The client handles authentication, rate limiting (100 req/min via token bucket), automatic retries with exponential backoff on 429 responses, webhook signature verification via HMAC-SHA256, and cursor-based pagination.

Quick Start

client := whoop.NewClient(
    whoop.WithToken("your_oauth2_token"),
)

profile, err := client.User.GetBasicProfile(ctx)

Pagination

List methods return page objects with a NextPage iterator:

page, _ := client.Cycle.List(ctx, &whoop.ListOptions{Limit: 25})
for {
    for _, c := range page.Records { /* process cycle */ }
    page, err = page.NextPage(ctx)
    if errors.Is(err, whoop.ErrNoNextPage) {
        break
    }
}

Webhooks

Use ParseWebhook to validate and decode incoming WHOOP webhook payloads:

event, err := whoop.ParseWebhook(r, "webhook_secret")

Index

Examples

Constants

View Source
const (

	// Version is the semantic version of this library.
	Version = "1.0.0"
)

Variables

View Source
var ErrNoNextPage = errors.New("no next page available")

ErrNoNextPage is returned by NextPage when there are no more pages to fetch.

Functions

This section is empty.

Types

type APIError

type APIError struct {
	StatusCode int
	Message    string
	URL        string
	Err        error // Underlying error, if any
}

APIError represents an error returned by the WHOOP API.

func (*APIError) Error

func (e *APIError) Error() string

Error implements the error interface.

func (*APIError) Unwrap

func (e *APIError) Unwrap() error

Unwrap implements errors.Unwrap so the underlying error can be extracted.

type AuthError

type AuthError struct {
	StatusCode int
	Message    string
	Err        error
}

AuthError represents an authentication or authorization failure (401, 403).

func (*AuthError) Error

func (e *AuthError) Error() string

Error implements the error interface.

func (*AuthError) Unwrap

func (e *AuthError) Unwrap() error

Unwrap implements errors.Unwrap.

type BasicProfile

type BasicProfile struct {
	UserID    int    `json:"user_id"`
	Email     string `json:"email"`
	FirstName string `json:"first_name"`
	LastName  string `json:"last_name"`
}

BasicProfile represents the user's basic profile information.

type BodyMeasurement

type BodyMeasurement struct {
	HeightMeter    float64 `json:"height_meter"`
	WeightKilogram float64 `json:"weight_kilogram"`
	MaxHeartRate   int     `json:"max_heart_rate"`
}

BodyMeasurement represents the user's physical body measurements.

type Client

type Client struct {

	// Services used for communicating with the WHOOP API endpoints.
	User     *UserService
	Cycle    *CycleService
	Sleep    *SleepService
	Workout  *WorkoutService
	Recovery *RecoveryService
	// contains filtered or unexported fields
}

Client is the core WHOOP API client.

func NewClient

func NewClient(opts ...Option) *Client

NewClient creates a new WHOOP API client with the given options.

Example

Create a client with default settings.

package main

import (
	"context"
	"fmt"
	"os"

	"github.com/arvarik/whoop-go/whoop"
)

func main() {
	client := whoop.NewClient(
		whoop.WithToken(os.Getenv("WHOOP_OAUTH_TOKEN")),
	)

	profile, err := client.User.GetBasicProfile(context.Background())
	if err != nil {
		fmt.Println("error:", err)
		return
	}
	fmt.Println("Hello,", profile.FirstName)
}
Example (WithOptions)

Customize backoff, retries, and base URL using functional options.

package main

import (
	"time"

	"github.com/arvarik/whoop-go/whoop"
)

func main() {
	client := whoop.NewClient(
		whoop.WithToken("your_token"),
		whoop.WithMaxRetries(5),
		whoop.WithBackoffBase(1*time.Second),
		whoop.WithBackoffMax(2*time.Minute),
		whoop.WithBaseURL("https://custom-proxy.example.com"),
	)
	_ = client
}

func (*Client) Do

func (c *Client) Do(ctx context.Context, req *http.Request) (*http.Response, error)

Do executes an HTTP request with context, authentication, rate limiting, and automatic retries on 429 Too Many Requests.

func (*Client) Get added in v0.3.0

func (c *Client) Get(ctx context.Context, path string, v any) (err error)

Get performs a GET request to the specified path and decodes the response into v.

func (*Client) GoString added in v0.3.0

func (c *Client) GoString() string

GoString implements the fmt.GoStringer interface. It ensures that even with %#v, the token is redacted.

func (*Client) String added in v0.3.0

func (c *Client) String() string

String implements the fmt.Stringer interface. It returns a string representation of the client with the token redacted.

type Cycle

type Cycle struct {
	ID             int        `json:"id"`
	UserID         int        `json:"user_id"`
	CreatedAt      time.Time  `json:"created_at"`
	UpdatedAt      time.Time  `json:"updated_at"`
	Start          time.Time  `json:"start"`
	End            *time.Time `json:"end"`
	TimezoneOffset string     `json:"timezone_offset"`
	ScoreState     string     `json:"score_state"`
	Score          *Score     `json:"score,omitempty"`
}

Cycle represents a physiological cycle (typically an awake period to the next awake period).

type CyclePage

type CyclePage struct {
	Records   []Cycle
	NextToken string
	// contains filtered or unexported fields
}

CyclePage represents a paginated set of Cycles.

func (*CyclePage) NextPage

func (p *CyclePage) NextPage(ctx context.Context) (*CyclePage, error)

NextPage fetches the subsequent page of cycles based on NextToken. Returns an error if there is no next page.

type CycleService

type CycleService struct {
	// contains filtered or unexported fields
}

CycleService handles communication with the cycle related methods.

func (*CycleService) GetByID

func (s *CycleService) GetByID(ctx context.Context, id int) (*Cycle, error)

GetByID fetches a single cycle by its ID.

Example

Fetch a single cycle by its numeric ID.

package main

import (
	"context"
	"fmt"

	"github.com/arvarik/whoop-go/whoop"
)

func main() {
	client := whoop.NewClient(whoop.WithToken("your_token"))
	cycle, err := client.Cycle.GetByID(context.Background(), 12345)
	if err != nil {
		fmt.Println("error:", err)
		return
	}
	fmt.Printf("Cycle %d: Strain=%.1f\n", cycle.ID, cycle.Score.Strain)
}

func (*CycleService) List

func (s *CycleService) List(ctx context.Context, opts *ListOptions) (*CyclePage, error)

List fetches a paginated collection of cycles.

Example

Iterate through all physiological cycles using cursor-based pagination.

package main

import (
	"context"
	"errors"
	"fmt"

	"github.com/arvarik/whoop-go/whoop"
)

func main() {
	client := whoop.NewClient(whoop.WithToken("your_token"))
	ctx := context.Background()

	page, err := client.Cycle.List(ctx, &whoop.ListOptions{Limit: 25})
	if err != nil {
		fmt.Println("error:", err)
		return
	}

	for {
		for _, c := range page.Records {
			fmt.Printf("Cycle %d: Strain=%.1f, ScoreState=%s\n",
				c.ID, c.Score.Strain, c.ScoreState)
		}

		page, err = page.NextPage(ctx)
		if err != nil {
			if errors.Is(err, whoop.ErrNoNextPage) {
				break // All pages consumed
			}
			fmt.Println("error:", err)
			return
		}
	}
}

type ListOptions

type ListOptions struct {
	// Maximum number of items to return. The API limits this to 50.
	Limit int `url:"limit,omitempty"`

	// Earliest date of data to fetch (inclusive). Time is ISO-8601 formatted.
	Start *time.Time `url:"start,omitempty"`

	// Latest date of data to fetch (inclusive). Time is ISO-8601 formatted.
	End *time.Time `url:"end,omitempty"`

	// Token used to fetch the next page of results. Usually handled automatically by the paginator.
	NextToken string `url:"nextToken,omitempty"`
}

ListOptions specifies the optional parameters to various List methods that support pagination.

type Option

type Option func(*Client)

Option is a functional option for configuring the Client.

func WithBackoffBase

func WithBackoffBase(base time.Duration) Option

WithBackoffBase sets the base duration for exponential backoff during retries. By default, this is 1 second.

func WithBackoffMax

func WithBackoffMax(max time.Duration) Option

WithBackoffMax sets the maximum duration for exponential backoff during retries. By default, this is 60 seconds.

func WithBaseURL

func WithBaseURL(url string) Option

WithBaseURL overrides the default WHOOP API base URL. This is primarily useful for testing or connecting to a proxy.

func WithHTTPClient

func WithHTTPClient(c *http.Client) Option

WithHTTPClient sets the underlying HTTP client used for requests. If this is not provided, a default http.Client is used.

func WithMaxRetries

func WithMaxRetries(retries int) Option

WithMaxRetries sets the maximum number of retries for 429 Too Many Requests responses. By default, the client will retry up to 3 times.

func WithRateLimiting added in v0.3.0

func WithRateLimiting(enabled bool) Option

WithRateLimiting enables or disables client-side rate limiting. This is primarily used for testing and benchmarking.

func WithToken

func WithToken(token string) Option

WithToken sets the OAuth2 access token for authentication. This will automatically set the Authorization: Bearer <token> header on all requests.

type RateLimitError

type RateLimitError struct {
	RetryAfter int // Suggested retry after duration in seconds, if provided by the API
	Err        error
}

RateLimitError represents an error indicating that the client is rate-limited. It can occur locally before the request is made or as a response from the API.

func (*RateLimitError) Error

func (e *RateLimitError) Error() string

Error implements the error interface.

func (*RateLimitError) Unwrap

func (e *RateLimitError) Unwrap() error

Unwrap implements errors.Unwrap.

type Recovery

type Recovery struct {
	CycleID    int            `json:"cycle_id"`
	SleepID    string         `json:"sleep_id"`
	UserID     int            `json:"user_id"`
	CreatedAt  time.Time      `json:"created_at"`
	UpdatedAt  time.Time      `json:"updated_at"`
	ScoreState string         `json:"score_state"`
	Score      *RecoveryScore `json:"score,omitempty"`
}

Recovery represents the quantified recovery status of the user for a given cycle.

type RecoveryPage

type RecoveryPage struct {
	Records   []Recovery
	NextToken string
	// contains filtered or unexported fields
}

RecoveryPage represents a paginated set of Recoveries.

func (*RecoveryPage) NextPage

func (p *RecoveryPage) NextPage(ctx context.Context) (*RecoveryPage, error)

NextPage fetches the subsequent page of Recoveries based on NextToken.

type RecoveryScore

type RecoveryScore struct {
	UserCalibrating  bool    `json:"user_calibrating"`
	RecoveryScore    float64 `json:"recovery_score"`
	RestingHeartRate float64 `json:"resting_heart_rate"`
	HrvRmssdMilli    float64 `json:"hrv_rmssd_milli"`
	Spo2Percentage   float64 `json:"spo2_percentage"`
	SkinTempCelsius  float64 `json:"skin_temp_celsius"`
}

RecoveryScore contains the metrics formulating the recovery calculation.

type RecoveryService

type RecoveryService struct {
	// contains filtered or unexported fields
}

RecoveryService handles communication with the recovery related methods.

func (*RecoveryService) GetByID

func (s *RecoveryService) GetByID(ctx context.Context, cycleID int) (*Recovery, error)

GetByID fetches a single recovery score by cycle ID.

Example

Fetch recovery data for a specific cycle.

package main

import (
	"context"
	"fmt"

	"github.com/arvarik/whoop-go/whoop"
)

func main() {
	client := whoop.NewClient(whoop.WithToken("your_token"))
	recovery, err := client.Recovery.GetByID(context.Background(), 12345)
	if err != nil {
		fmt.Println("error:", err)
		return
	}

	if recovery.Score != nil {
		fmt.Printf("Recovery: %.0f%%, SpO2: %.0f%%\n",
			recovery.Score.RecoveryScore,
			recovery.Score.Spo2Percentage)
	}
}

func (*RecoveryService) List

func (s *RecoveryService) List(ctx context.Context, opts *ListOptions) (*RecoveryPage, error)

List fetches a paginated collection of recovery records.

Example

List recovery scores across recent cycles.

package main

import (
	"context"
	"fmt"

	"github.com/arvarik/whoop-go/whoop"
)

func main() {
	client := whoop.NewClient(whoop.WithToken("your_token"))
	ctx := context.Background()

	page, err := client.Recovery.List(ctx, &whoop.ListOptions{Limit: 7})
	if err != nil {
		fmt.Println("error:", err)
		return
	}

	for _, r := range page.Records {
		if r.Score != nil {
			fmt.Printf("Cycle %d: Recovery=%.0f%%, HRV=%.1f, RHR=%.0f\n",
				r.CycleID,
				r.Score.RecoveryScore,
				r.Score.HrvRmssdMilli,
				r.Score.RestingHeartRate)
		}
	}
}

type Scope

type Scope string

Scope represents an OAuth2 scope required to access specific WHOOP API endpoints.

const (
	// ScopeOffline enables refresh token issuance for long-lived sessions.
	ScopeOffline Scope = "offline"

	// ScopeReadRecovery allows reading the user's recovery data.
	ScopeReadRecovery Scope = "read:recovery"

	// ScopeReadCycles allows reading the user's physiological cycles.
	ScopeReadCycles Scope = "read:cycles"

	// ScopeReadSleep allows reading the user's sleep data.
	ScopeReadSleep Scope = "read:sleep"

	// ScopeReadWorkout allows reading the user's workout data.
	ScopeReadWorkout Scope = "read:workout"

	// ScopeReadProfile allows reading the user's basic profile.
	ScopeReadProfile Scope = "read:profile"

	// ScopeReadBodyMeasurement allows reading the user's body measurements.
	ScopeReadBodyMeasurement Scope = "read:body_measurement"
)

type Score

type Score struct {
	Strain           float64 `json:"strain"`
	Kilojoule        float64 `json:"kilojoule"`
	AverageHeartRate int     `json:"average_heart_rate"`
	MaxHeartRate     int     `json:"max_heart_rate"`
}

Score summarizes physiological strains within a Cycle.

type Sleep

type Sleep struct {
	ID             string      `json:"id"`
	CycleID        int         `json:"cycle_id"`
	V1ID           *int        `json:"v1_id,omitempty"`
	UserID         int         `json:"user_id"`
	CreatedAt      time.Time   `json:"created_at"`
	UpdatedAt      time.Time   `json:"updated_at"`
	Start          time.Time   `json:"start"`
	End            time.Time   `json:"end"`
	TimezoneOffset string      `json:"timezone_offset"`
	Nap            bool        `json:"nap"`
	ScoreState     string      `json:"score_state"`
	Score          *SleepScore `json:"score,omitempty"`
}

Sleep represents a single sleep event.

type SleepNeeded

type SleepNeeded struct {
	BaselineMilli             int `json:"baseline_milli"`
	NeedFromSleepDebtMilli    int `json:"need_from_sleep_debt_milli"`
	NeedFromRecentStrainMilli int `json:"need_from_recent_strain_milli"`
	NeedFromRecentNapMilli    int `json:"need_from_recent_nap_milli"`
}

SleepNeeded defines baseline and calculated sleep needs for the individual.

type SleepPage

type SleepPage struct {
	Records   []Sleep
	NextToken string
	// contains filtered or unexported fields
}

SleepPage represents a paginated set of Sleep activities.

func (*SleepPage) NextPage

func (p *SleepPage) NextPage(ctx context.Context) (*SleepPage, error)

NextPage fetches the subsequent page of Sleep events based on NextToken.

type SleepScore

type SleepScore struct {
	StageSummary               *StageSummary `json:"stage_summary"`
	SleepNeeded                *SleepNeeded  `json:"sleep_needed"`
	RespiratoryRate            float64       `json:"respiratory_rate"`
	SleepPerformancePercentage float64       `json:"sleep_performance_percentage"`
	SleepConsistencyPercentage float64       `json:"sleep_consistency_percentage"`
	SleepEfficiencyPercentage  float64       `json:"sleep_efficiency_percentage"`
}

SleepScore provides calculated metrics for a Sleep.

type SleepService

type SleepService struct {
	// contains filtered or unexported fields
}

SleepService handles communication with the sleep related methods.

func (*SleepService) GetByID

func (s *SleepService) GetByID(ctx context.Context, id string) (*Sleep, error)

GetByID fetches a single sleep event by its UUID.

Example

Fetch a single sleep event by its UUID.

package main

import (
	"context"
	"fmt"
	"time"

	"github.com/arvarik/whoop-go/whoop"
)

func main() {
	client := whoop.NewClient(whoop.WithToken("your_token"))
	sleep, err := client.Sleep.GetByID(context.Background(), "abc-def-456")
	if err != nil {
		fmt.Println("error:", err)
		return
	}

	fmt.Printf("Sleep %s: Nap=%v\n", sleep.ID, sleep.Nap)
	if sleep.Score != nil && sleep.Score.StageSummary != nil {
		rem := time.Duration(sleep.Score.StageSummary.TotalRemSleepTimeMilli) * time.Millisecond
		fmt.Printf("  REM sleep: %s\n", rem)
	}
}

func (*SleepService) List

func (s *SleepService) List(ctx context.Context, opts *ListOptions) (*SleepPage, error)

List fetches a paginated collection of sleep events.

Example

Iterate through sleep events with pagination.

package main

import (
	"context"
	"fmt"

	"github.com/arvarik/whoop-go/whoop"
)

func main() {
	client := whoop.NewClient(whoop.WithToken("your_token"))
	ctx := context.Background()

	page, err := client.Sleep.List(ctx, &whoop.ListOptions{Limit: 10})
	if err != nil {
		fmt.Println("error:", err)
		return
	}

	for _, s := range page.Records {
		fmt.Printf("Sleep %s: Nap=%v, ScoreState=%s\n", s.ID, s.Nap, s.ScoreState)
		if s.Score != nil {
			fmt.Printf("  Performance: %.0f%%, Efficiency: %.0f%%\n",
				s.Score.SleepPerformancePercentage,
				s.Score.SleepEfficiencyPercentage)
		}
	}
}

type StageSummary

type StageSummary struct {
	TotalInBedTimeMilli         int `json:"total_in_bed_time_milli"`
	TotalAwakeTimeMilli         int `json:"total_awake_time_milli"`
	TotalNoDataTimeMilli        int `json:"total_no_data_time_milli"`
	TotalLightSleepTimeMilli    int `json:"total_light_sleep_time_milli"`
	TotalSlowWaveSleepTimeMilli int `json:"total_slow_wave_sleep_time_milli"`
	TotalRemSleepTimeMilli      int `json:"total_rem_sleep_time_milli"`
	SleepCycleCount             int `json:"sleep_cycle_count"`
	DisturbanceCount            int `json:"disturbance_count"`
}

StageSummary breaks down durations spent in different sleep stages.

type UserService

type UserService struct {
	// contains filtered or unexported fields
}

UserService handles communication with the user related methods.

func (*UserService) GetBasicProfile

func (s *UserService) GetBasicProfile(ctx context.Context) (profile *BasicProfile, err error)

GetBasicProfile fetches the athlete's basic profile.

Example

Fetch the authenticated user's basic profile.

package main

import (
	"context"
	"fmt"

	"github.com/arvarik/whoop-go/whoop"
)

func main() {
	client := whoop.NewClient(whoop.WithToken("your_token"))
	profile, err := client.User.GetBasicProfile(context.Background())
	if err != nil {
		fmt.Println("error:", err)
		return
	}
	fmt.Printf("User: %s %s (ID: %d)\n", profile.FirstName, profile.LastName, profile.UserID)
}

func (*UserService) GetBodyMeasurement

func (s *UserService) GetBodyMeasurement(ctx context.Context) (measurement *BodyMeasurement, err error)

GetBodyMeasurement fetches the athlete's body measurements.

Example

Fetch the authenticated user's body measurements.

package main

import (
	"context"
	"fmt"

	"github.com/arvarik/whoop-go/whoop"
)

func main() {
	client := whoop.NewClient(whoop.WithToken("your_token"))
	body, err := client.User.GetBodyMeasurement(context.Background())
	if err != nil {
		fmt.Println("error:", err)
		return
	}
	fmt.Printf("Height: %.2fm, Weight: %.1fkg, Max HR: %d\n",
		body.HeightMeter, body.WeightKilogram, body.MaxHeartRate)
}

type WebhookEvent

type WebhookEvent struct {
	UserID  int    `json:"user_id"`
	ID      string `json:"id"`
	Type    string `json:"type"`
	TraceID string `json:"trace_id"`
}

WebhookEvent represents a "Skinny Webhook" payload from WHOOP.

func ParseWebhook

func ParseWebhook(r *http.Request, secret string) (*WebhookEvent, error)

ParseWebhook reads and verifies an incoming HTTP request from a WHOOP Webhook. It validates the X-Whoop-Signature HMAC-SHA256 using the provided secret key. The request body is capped at 1 MB to prevent memory exhaustion. Ensure your HTTP handler does NOT consume r.Body before passing it to this function.

Example

Securely verify and parse incoming WHOOP webhook payloads.

package main

import (
	"fmt"
	"net/http"
	"os"

	"github.com/arvarik/whoop-go/whoop"
)

func main() {
	http.HandleFunc("/whoop/webhook", func(w http.ResponseWriter, r *http.Request) {
		event, err := whoop.ParseWebhook(r, os.Getenv("WHOOP_WEBHOOK_SECRET"))
		if err != nil {
			http.Error(w, "Unauthorized", http.StatusUnauthorized)
			return
		}

		fmt.Printf("Event: type=%s, id=%s, user=%d\n",
			event.Type, event.ID, event.UserID)
		w.WriteHeader(http.StatusOK)
	})
}

type Workout

type Workout struct {
	ID             string        `json:"id"`
	V1ID           *int          `json:"v1_id,omitempty"`
	UserID         int           `json:"user_id"`
	CreatedAt      time.Time     `json:"created_at"`
	UpdatedAt      time.Time     `json:"updated_at"`
	Start          time.Time     `json:"start"`
	End            time.Time     `json:"end"`
	TimezoneOffset string        `json:"timezone_offset"`
	SportID        int           `json:"sport_id"`
	SportName      string        `json:"sport_name"`
	ScoreState     string        `json:"score_state"`
	Score          *WorkoutScore `json:"score,omitempty"`
}

Workout represents a tracked workout session.

type WorkoutPage

type WorkoutPage struct {
	Records   []Workout
	NextToken string
	// contains filtered or unexported fields
}

WorkoutPage represents a paginated set of Workouts.

func (*WorkoutPage) NextPage

func (p *WorkoutPage) NextPage(ctx context.Context) (*WorkoutPage, error)

NextPage fetches the subsequent page of Workouts based on NextToken.

type WorkoutScore

type WorkoutScore struct {
	Strain              float64        `json:"strain"`
	AverageHeartRate    int            `json:"average_heart_rate"`
	MaxHeartRate        int            `json:"max_heart_rate"`
	Kilojoule           float64        `json:"kilojoule"`
	PercentRecorded     float64        `json:"percent_recorded"`
	DistanceMeter       *float64       `json:"distance_meter"`
	AltitudeGainMeter   *float64       `json:"altitude_gain_meter"`
	AltitudeChangeMeter *float64       `json:"altitude_change_meter"`
	ZoneDuration        *ZoneDurations `json:"zone_durations"`
}

WorkoutScore details the cardiovascular output of a given workout.

type WorkoutService

type WorkoutService struct {
	// contains filtered or unexported fields
}

WorkoutService handles communication with the workout related methods.

func (*WorkoutService) GetByID

func (s *WorkoutService) GetByID(ctx context.Context, id string) (*Workout, error)

GetByID fetches a single workout session by its UUID.

Example

Fetch a single workout by its UUID.

package main

import (
	"context"
	"fmt"

	"github.com/arvarik/whoop-go/whoop"
)

func main() {
	client := whoop.NewClient(whoop.WithToken("your_token"))
	workout, err := client.Workout.GetByID(context.Background(), "abc-def-123")
	if err != nil {
		fmt.Println("error:", err)
		return
	}

	fmt.Printf("Workout: %s (%s)\n", workout.SportName, workout.ID)
	if workout.Score != nil && workout.Score.ZoneDuration != nil {
		fmt.Printf("  Zone 5 Time: %dms\n", workout.Score.ZoneDuration.ZoneFiveMilli)
	}
}

func (*WorkoutService) List

func (s *WorkoutService) List(ctx context.Context, opts *ListOptions) (*WorkoutPage, error)

List fetches a paginated collection of workout sessions.

Example

List recent workouts with date filtering.

package main

import (
	"context"
	"fmt"
	"time"

	"github.com/arvarik/whoop-go/whoop"
)

func main() {
	client := whoop.NewClient(whoop.WithToken("your_token"))
	ctx := context.Background()

	start := time.Now().AddDate(0, 0, -7) // Last 7 days
	page, err := client.Workout.List(ctx, &whoop.ListOptions{
		Limit: 10,
		Start: &start,
	})
	if err != nil {
		fmt.Println("error:", err)
		return
	}

	for _, w := range page.Records {
		fmt.Printf("Workout %s: %s, Strain=%.1f\n", w.ID, w.SportName, w.Score.Strain)
	}
}

type ZoneDurations

type ZoneDurations struct {
	ZoneZeroMilli  int `json:"zone_zero_milli"`
	ZoneOneMilli   int `json:"zone_one_milli"`
	ZoneTwoMilli   int `json:"zone_two_milli"`
	ZoneThreeMilli int `json:"zone_three_milli"`
	ZoneFourMilli  int `json:"zone_four_milli"`
	ZoneFiveMilli  int `json:"zone_five_milli"`
}

ZoneDurations breaks down the duration spent in different heart rate zones.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL