Deploy a FastAPI App to Fly.io¶
Build and deploy an authenticated API with SweatStack fitness data using FastAPI and Fly.io.
What You'll Build¶
A FastAPI app that:
- Authenticates users with SweatStack OAuth
- Fetches activity data via the SweatStack API
- Deploys to Fly.io with a public URL
In a hurry?
uv init fastapi-app && cd fastapi-app
uv add 'sweatstack[fastapi]'
Create a new app at app.sweatstack.no with redirect URI http://localhost:8000/auth/sweatstack/callback.
Generate a session secret:
python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"
Create .env:
SWEATSTACK_CLIENT_ID=your_client_id
SWEATSTACK_CLIENT_SECRET=your_client_secret
SWEATSTACK_SESSION_SECRET=your_generated_session_secret
APP_URL=http://localhost:8000
Create main.py:
import os
from fastapi import FastAPI
from fastapi.responses import HTMLResponse
from sweatstack.fastapi import configure, instrument, OptionalUser
app = FastAPI(title="Training Dashboard")
configure()
instrument(app)
@app.get("/", response_class=HTMLResponse)
async def home(user: OptionalUser):
if not user:
return '<h1>Training Dashboard</h1><a href="/auth/sweatstack/login">Login</a>'
activities = await user.client.get_activities(limit=7)
total_hours = sum(a.duration_seconds or 0 for a in activities) / 3600
return f'''
<h1>Your Week</h1>
<p><strong>{len(activities)}</strong> activities, <strong>{total_hours:.1f}</strong> hours</p>
<form action="/auth/sweatstack/logout" method="post"><button>Logout</button></form>
'''
Run locally:
uv run --env-file .env fastapi dev
Deploy to Fly.io:
fly launch
fly secrets set SWEATSTACK_CLIENT_ID=... SWEATSTACK_CLIENT_SECRET=... SWEATSTACK_SESSION_SECRET=... APP_URL=https://YOUR_APP.fly.dev
fly deploy
Don't forget to update the redirect URI in your SweatStack app to https://YOUR_APP.fly.dev/auth/sweatstack/callback.
Prerequisites¶
- A SweatStack account (app.sweatstack.no)
- uv installed
- Fly.io CLI installed (for deployment)
Project Setup¶
Create a new project and install dependencies:
uv init fastapi-app
cd fastapi-app
uv add 'sweatstack[fastapi]'
Creating a SweatStack App¶
Register your app with SweatStack to get API credentials:
- Go to app.sweatstack.no
- Fill in the app details (use placeholder values for now)
- Set the redirect URI to
http://localhost:8000/auth/sweatstack/callback - Save and click "Create Secret" - copy it immediately
Generate a session secret (used to encrypt cookies):
python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"
Create a .env file with your credentials:
SWEATSTACK_CLIENT_ID=your_client_id
SWEATSTACK_CLIENT_SECRET=your_client_secret
SWEATSTACK_SESSION_SECRET=your_generated_session_secret
APP_URL=http://localhost:8000
Security
Add .env to your .gitignore to keep your secrets safe!
Creating the FastAPI App¶
Create main.py:
import os
from fastapi import FastAPI
from fastapi.responses import HTMLResponse
from sweatstack.fastapi import configure, instrument, OptionalUser
app = FastAPI(title="Training Dashboard")
configure() # (1)!
instrument(app) # (2)!
@app.get("/", response_class=HTMLResponse)
async def home(user: OptionalUser): # (3)!
if not user:
return """
<h1>Training Dashboard</h1>
<p>View your weekly training summary.</p>
<a href="/auth/sweatstack/login">Login with SweatStack</a>
"""
return f"""
<h1>Welcome!</h1>
<p>You're logged in as user {user.user_id}</p>
<form action="/auth/sweatstack/logout" method="post">
<button type="submit">Logout</button>
</form>
"""
- Reads credentials from environment variables automatically.
- Adds login, logout, and callback routes to your app.
OptionalUserprovides the user if logged in, orNoneotherwise.
Run Locally¶
uv run --env-file .env fastapi dev
Open http://localhost:8000 and click Login with SweatStack to test authentication.
Adding Activity Data¶
Let's display the user's recent training. Update the home route:
@app.get("/", response_class=HTMLResponse)
async def home(user: OptionalUser):
if not user:
return """
<h1>Training Dashboard</h1>
<p>View your weekly training summary.</p>
<a href="/auth/sweatstack/login">Login with SweatStack</a>
"""
activities = await user.client.get_activities(limit=7) # (1)!
total_duration = sum(a.duration_seconds or 0 for a in activities) / 3600
total_distance = sum(a.distance_meters or 0 for a in activities) / 1000
activity_list = "".join(
f"<li>{a.sport} - {(a.duration_seconds or 0) // 60} min</li>"
for a in activities
)
return f"""
<h1>Your Recent Training</h1>
<p><strong>{len(activities)}</strong> activities</p>
<p><strong>{total_duration:.1f}</strong> hours</p>
<p><strong>{total_distance:.1f}</strong> km</p>
<ul>{activity_list}</ul>
<form action="/auth/sweatstack/logout" method="post">
<button type="submit">Logout</button>
</form>
"""
user.clientis a pre-configured SweatStack client for API calls. See the Python client docs for available methods.
View complete code
import os
from fastapi import FastAPI
from fastapi.responses import HTMLResponse
from sweatstack.fastapi import configure, instrument, OptionalUser
app = FastAPI(title="Training Dashboard")
configure()
instrument(app)
@app.get("/", response_class=HTMLResponse)
async def home(user: OptionalUser):
if not user:
return """
<h1>Training Dashboard</h1>
<p>View your weekly training summary.</p>
<a href="/auth/sweatstack/login">Login with SweatStack</a>
"""
activities = await user.client.get_activities(limit=7)
total_duration = sum(a.duration_seconds or 0 for a in activities) / 3600
total_distance = sum(a.distance_meters or 0 for a in activities) / 1000
activity_list = "".join(
f"<li>{a.sport} - {(a.duration_seconds or 0) // 60} min</li>"
for a in activities
)
return f"""
<h1>Your Recent Training</h1>
<p><strong>{len(activities)}</strong> activities</p>
<p><strong>{total_duration:.1f}</strong> hours</p>
<p><strong>{total_distance:.1f}</strong> km</p>
<ul>{activity_list}</ul>
<form action="/auth/sweatstack/logout" method="post">
<button type="submit">Logout</button>
</form>
"""
Deploy to Fly.io¶
1. Create Fly App¶
fly launch
This auto-detects FastAPI and creates:
fly.toml- Fly.io configurationDockerfile- Container build instructions
Accept the defaults or customize as needed.
2. Update Redirect URI¶
In your SweatStack app settings, add a new redirect URI:
https://YOUR_APP.fly.dev/auth/sweatstack/callback
Replace YOUR_APP with your Fly.io app name.
3. Set Secrets¶
fly secrets set \
SWEATSTACK_CLIENT_ID=your_client_id \
SWEATSTACK_CLIENT_SECRET=your_client_secret \
SWEATSTACK_SESSION_SECRET=your_session_secret \
APP_URL=https://YOUR_APP.fly.dev
4. Deploy¶
fly deploy
Your app is now live at https://YOUR_APP.fly.dev
Building Your Own App¶
Available Routes¶
The instrument(app) call adds these routes:
| Route | Method | Description |
|---|---|---|
/auth/sweatstack/login |
GET | Start OAuth flow |
/auth/sweatstack/callback |
GET | OAuth callback |
/auth/sweatstack/logout |
POST | End session |
/auth/sweatstack/select-user/{user_id} |
POST | Switch to athlete (for coaches) |
/auth/sweatstack/select-self |
POST | Return to own data |
Protecting Routes¶
Use AuthenticatedUser when login is required:
from sweatstack.fastapi import AuthenticatedUser
@app.get("/dashboard")
async def dashboard(user: AuthenticatedUser): # (1)!
activities = await user.client.get_activities(limit=10)
return {"activities": activities}
- Unauthenticated users are redirected to login automatically.
Coach/Athlete Switching¶
If you're building for coaches, use SelectedUser to access athlete data:
from sweatstack.fastapi import SelectedUser
@app.get("/athlete-dashboard")
async def athlete_dashboard(user: SelectedUser): # (1)!
activities = await user.client.get_activities(limit=10)
return {"activities": activities}
- Returns selected athlete's data, or coach's own if none selected.
More API Examples¶
# Get athlete profile
profile = await user.client.get_athlete()
# List activities with filters
activities = await user.client.get_activities(
start_date="2024-01-01",
end_date="2024-01-31",
sport="cycling"
)
# Get activity streams (time-series data)
streams = await user.client.get_activity_streams(activity_id)
Next Steps¶
- Browse the SweatStack API Reference for all available endpoints
- Read the FastAPI integration docs for configuration options
- Add a frontend with HTMX, React, or Vue
- Set up webhooks to react to new activities