Integrating the Peloton API with GatsbyJS

I recently purchased a Peloton Bike+ and was amazed by the amount of raw statistics. It makes an ideal candidate for data integration (checkout my stats page) to showcase recent activity.

I’ll be using javascript/react/gatsby to demonstrate how it was achieved.

Peloton API

The Peloton API isn’t documented in an official capacity (although there have been some attempts by third parties). To understand what is possible requires using browser devtools on their member website to sniff out API calls.

Peloton DevTools

Authentication Token

Any API call requires an authentication token. This can be accessed as follows:

const body = {
  username_or_email: 'my-username',
  password: 'my-password',
}

const response = await fetch('https://api.onepeloton.com/auth/login', {
  method: 'post',
  body: JSON.stringify(body),
  headers: { 'Content-Type': 'application/json' },
})

Remember to use secrets for your username/password and don’t store in code!

A successful authentication will return an object as follows:

const authData = {
  session_id: 'random-session-id',
  user_id: 'unique-user-id',
}

You’ll use session_id as a header in API calls and user_id as a unique identifier for API paths.

Example API call

The following code calls the overview method which returns the current user’s workout information. It passes in the session_id in the header.

const opts = {
  headers: {
    cookie: `peloton_session_id=${authData.session_id};`,
    'peloton-platform': 'web',
  },
}

const responseOverview = await fetch(
  `https://api.onepeloton.com/api/user/${authData.user_id}/overview?version=2`,
  opts
)
const overviewData = await responseOverview.json()

note: methods on the Peloton API require that you pass peloton-platform in the header.

A full list of methods can be found at:

Gatsby Integration

Gatsby is a wonderful framework for integration due to GraphQL. This allows integration from many different data sources into a single graph with one query language for access.

Gatsby Source Nodes

Integration to the Peloton API can be configured in gatsby-node.js. The sourceNodes extension point allows a hook to source additional nodes into the graph — perfect for an API.

exports.sourceNodes = async ({
  actions,
  createNodeId,
  createContentDigest,
}) => {
  // step 1: authenticate with Peloton
  const body = {
    username_or_email: process.env.PELOTON_USERNAME,
    password: process.env.PELOTON_PASSWORD,
  }

  const response = await fetch('https://api.onepeloton.com/auth/login', {
    method: 'post',
    body: JSON.stringify(body),
    headers: { 'Content-Type': 'application/json' },
  })
  const authData = await response.json()

  const opts = {
    headers: {
      cookie: `peloton_session_id=${authData.session_id};`,
      'peloton-platform': 'web',
    },
  }

  // Step 2: retrieve workout data from the overview
  const responseOverview = await fetch(
    `https://api.onepeloton.com.au/api/user/${authData.user_id}/overview?version=1`,
    opts
  )
  const overviewData = await responseOverview.json()

  // Step 3: create nodes in the graph
  overviewData.workout_counts.workouts.forEach(workoutCount => {
    const newNode = {
      ...workoutCount,
      id: createNodeId(workoutCount.name),
      internal: {
        type: 'WorkoutCount',
        contentDigest: createContentDigest(workoutCount),
      },
    }
    actions.createNode(newNode)
  })
}

If you visit the GraphQL interface shipped with Gatsby (http://localhost:8000/___graphql), you’ll see the new allWorkoutCount node.

Gatsby GraphQL

More documentation for Gatsby nodes can be found here:

Gatsby/React Integration

Once the nodes are in GraphQL they can be integrated into a React component as follows (queries are mapped into props):

import React from 'react'
import { graphql } from 'gatsby'

import Layout from '../components/layout'
import Wrapper from '../components/Wrapper'
import SEO from '../components/SEO'
import Statistics from '../components/Statistics'

class Stats extends React.Component {
  render() {
    const workoutCounts = this.props.data.workoutCounts.nodes

    return (
      <Layout location={this.props.location}>
        <SEO title="My Peloton Stats" />
        <Wrapper>
          <Statistics workoutCounts={workoutCounts} />
        </Wrapper>
      </Layout>
    )
  }
}

export default Stats

export const pageQuery = graphql`
  query statQuery {
    workoutCounts: allWorkoutCount {
      nodes {
        id
        name
        count
        icon_url
        slug
      }
    }
  }
`

The full source on this blog can be found here.

Daily Schedule

Since statistics are generated on an ongoing basis, I wanted the statistics to be updated daily (not just on a pull request or push to master).

This site is hosted on Azure Static Websites with a CI/CD pipeline using Github Actions. My github actions workflow only required a small extension using schedule (running at 8am daily):

name: Azure Static Web Apps CI/CD

on:
  push:
    branches:
      - master
  pull_request:
    types: [opened, synchronize, reopened, closed]
    branches:
      - master
  schedule:
    - cron: '0 8 * * *'

The End Result

As you can see by the stats page, the integration to Peloton is relatively straight forward:

  • Retrieve an authentication token
  • Call a Peloton API method
  • Build nodes in Gatsby based on API data
  • Query GraphQL in React components
  • Modify github action yml file to trigger daily

Peloton Workout Overview

About the author

For the past two decades, Scott McCulloch has worked with a variety of distributed computing technologies. He is currently focused on cloud-native applications.