Skip to content

Build a Streamlit app

A working Streamlit app on top of SweatStack in under fifteen minutes. It authenticates the user, lets them pick an activity, and renders a heart-rate chart from the timeseries data.

What you'll build

A Streamlit app that:

  • Authenticates with SweatStack via OAuth2
  • Fetches the user's activities
  • Renders a heart-rate chart for the selected activity
  • Supports switching between accessible users (useful when building for coaches)
In a hurry?
uv init
uv add "sweatstack[streamlit]"

Create a new app at app.sweatstack.no/applications/new with redirect URI http://localhost:8501. Save the credentials in a .env file:

.env
SWEATSTACK_CLIENT_ID=your_client_id_here
SWEATSTACK_CLIENT_SECRET=your_client_secret_here

Create app.py:

app.py
import os

import streamlit as st
from sweatstack.streamlit import StreamlitAuth

auth = StreamlitAuth(
    client_id=os.getenv("SWEATSTACK_CLIENT_ID"),
    client_secret=os.getenv("SWEATSTACK_CLIENT_SECRET"),
    redirect_uri="http://localhost:8501",
)

with st.sidebar:
    auth.authenticate()
    if auth.is_authenticated():
        auth.select_user()

if not auth.is_authenticated():
    st.write("# Heart-rate dashboard")
    st.write("Please log in to view your activity data.")
    st.stop()

st.write("# Heart-rate dashboard")

activity = auth.select_activity()

if not activity:
    st.warning("No activities found.")
    st.stop()

st.write(f"### {activity.sport.display_name()} on {activity.start.date()}")

data = auth.client.get_activity_data(activity.id)

if data is None or data.empty or "heart_rate" not in data.columns:
    st.info("No heart-rate data available for this activity.")
    st.stop()

st.line_chart(data["heart_rate"])

Run it:

uv run --env-file .env streamlit run app.py

Prerequisites

Create a SweatStack application

Register your app to get OAuth2 credentials:

  1. Go to app.sweatstack.no/applications/new.
  2. Use placeholder values for URL, image URL, and privacy statement for now.
  3. Set the redirect URI to http://localhost:8501.
  4. Save, then click Create Secret, name it, and copy the secret immediately. It's shown only once.

Save the credentials in a .env file:

.env
SWEATSTACK_CLIENT_ID=your_client_id_here
SWEATSTACK_CLIENT_SECRET=your_client_secret_here

Warning

Treat the client secret like a password. Add .env to .gitignore before committing anything.

Set up the project

Create a project folder and install the dependencies:

uv init
uv add "sweatstack[streamlit]"

Wire up authentication

Create app.py:

app.py
import os

import streamlit as st
from sweatstack.streamlit import StreamlitAuth

auth = StreamlitAuth(
    client_id=os.getenv("SWEATSTACK_CLIENT_ID"),
    client_secret=os.getenv("SWEATSTACK_CLIENT_SECRET"),
    redirect_uri="http://localhost:8501",
)

auth.authenticate()

if not auth.is_authenticated():
    st.write("# Heart-rate dashboard")
    st.write("Please log in to view your activity data.")
    st.stop()

st.write("# Heart-rate dashboard")
st.success("Connected to SweatStack.")

What this does:

  • StreamlitAuth handles the OAuth2 flow.
  • auth.authenticate() renders a login or logout button.
  • auth.is_authenticated() checks session state.
  • st.stop() short-circuits the rest of the script for unauthenticated users.

Use auth.client for API calls

StreamlitAuth.client is a per-session client tied to the authenticated user. The module-level singleton (import sweatstack) is shared across Streamlit sessions and can leak data between users. Always use auth.client.<method>().

Run the app

uv run --env-file .env streamlit run app.py

The browser opens automatically (or visit http://localhost:8501). Click the login button, authorize on SweatStack, and you're back in your app authenticated.

Add activity selection

Replace the st.success(...) line with an activity picker. auth.select_activity() renders a dropdown of the user's recent activities:

app.py
st.write("# Heart-rate dashboard")

activity = auth.select_activity()

if not activity:
    st.warning("No activities found.")
    st.stop()

st.write(f"### {activity.sport.display_name()} on {activity.start.date()}")

Add a heart-rate chart

Fetch the activity's timeseries data and render it. Add this after the activity-selection block:

app.py
data = auth.client.get_activity_data(activity.id)

if data is None or data.empty or "heart_rate" not in data.columns:
    st.info("No heart-rate data available for this activity.")
    st.stop()

st.line_chart(data["heart_rate"])

get_activity_data returns a pandas DataFrame indexed by timestamp, with columns for whichever metrics the activity recorded (heart_rate, power, speed, etc.). Not every activity has every metric; the check above guards for missing heart-rate data.

st.line_chart is Streamlit's native chart for a single time-series. For more control over styling, swap in matplotlib or altair.

Switch between users (coaches)

If your users are coaches with access to multiple athletes, add a user selector. Move the auth widget into the sidebar and add auth.select_user():

app.py
with st.sidebar:
    auth.authenticate()
    if auth.is_authenticated():
        auth.select_user()

For solo apps, skip this section. auth.client always defaults to the authenticated user.

What's next