P247 Strava Connect — iOS Implementation Brief
P247 Strava Connect — iOS Implementation Brief
Version: 1.0 Date: 23 March 2026 Status: Backend endpoints live on production
1. What This Does
Adds a “Connect Strava” button in the app’s Settings/Connections screen. When tapped, it opens Strava’s OAuth authorization page. The athlete logs into Strava, approves P247’s access, and Strava redirects back to the app with an auth code. The app sends that code to the backend, which exchanges it for access/refresh tokens and stores them per-user.
Once connected, the backend polls Strava for the athlete’s activities and includes training data in their daily briefs (workout names, pace, heart rate zones, distance, training load).
2. OAuth Flow (Step by Step)
┌──────────┐ 1. POST /connections/strava/initiate ┌──────────────┐
│ iOS App │ ───────────────────────────────────────────▶ │ P247 Backend │
│ │ ◀─────────────────────────────────────────── │ │
│ │ Returns: { authorize_url: "..." } └──────────────┘
│ │
│ │ 2. Open authorize_url in ASWebAuthenticationSession
│ │ ───────────────────────────────────────────▶ ┌──────────────┐
│ │ │ Strava.com │
│ │ 3. Athlete logs in + approves │ │
│ │ ◀─────────────────────────────────────────── │ │
│ │ Redirect to: redirect_uri?code=ABC123 └──────────────┘
│ │
│ │ 4. POST /connections/strava/callback
│ │ { "code": "ABC123", "scope": "read,activity:read_all" }
│ │ ───────────────────────────────────────────▶ ┌──────────────┐
│ │ │ P247 Backend │
│ │ 5. Backend exchanges code for tokens │ (stores │
│ │ ◀─────────────────────────────────────────── │ tokens) │
│ │ Returns: { status: "connected", └──────────────┘
│ │ athlete_name: "Myles B" }
└──────────┘
3. Backend Endpoints (All Live)
Base URL: https://app.p247.io
Auth: x-api-key header on all requests.
3.1 List Connections
GET /connections/
Returns all providers with connection status:
{
"connections": [
{
"provider": "strava",
"connected": true,
"athlete_name": "Myles Bruggeling",
"athlete_id": 39484557,
"connected_at": "2026-03-23T09:30:00+00:00",
"last_sync": "2026-03-23T10:00:00+00:00"
},
{ "provider": "garmin", "connected": false, ... },
{ "provider": "whoop", "connected": false, ... },
{ "provider": "oura", "connected": false, ... }
]
}
Use this to populate the Connections screen. Show a green checkmark and athlete name for connected providers, and a “Connect” button for disconnected ones. Garmin/Whoop/Oura are placeholders for now (always show as disconnected).
3.2 Initiate Strava OAuth
POST /connections/strava/initiate
Returns:
{
"authorize_url": "https://www.strava.com/oauth/authorize?client_id=205651&...",
"redirect_uri": "https://app.p247.io/connections/strava/callback",
"instructions": "Open authorize_url in ASWebAuthenticationSession..."
}
3.3 Complete Strava OAuth
POST /connections/strava/callback
Content-Type: application/json
{
"code": "abc123def456",
"scope": "read,activity:read_all,profile:read_all"
}
Returns:
{
"status": "connected",
"provider": "strava",
"athlete_id": 39484557,
"athlete_name": "Myles Bruggeling",
"connected_at": "2026-03-23T09:30:00+00:00"
}
3.4 Disconnect Strava
DELETE /connections/strava
Returns:
{
"status": "disconnected",
"provider": "strava"
}
Revokes the token at Strava and removes the connection. Show a confirmation dialog before calling this.
3.5 Refresh Token (Backend Use)
POST /connections/strava/refresh
This is primarily for backend use but the app can call it if needed. Strava access tokens expire every 6 hours. The backend handles refresh automatically during polling.
4. iOS Implementation Details
4.1 Use ASWebAuthenticationSession
Apple’s preferred method for OAuth in iOS apps. It handles the web view, shares cookies with Safari (so the athlete may already be logged into Strava), and returns the callback URL to your app.
import AuthenticationServices
func connectStrava() {
// 1. Call backend to get authorize URL
let initiateURL = URL(string: "https://app.p247.io/connections/strava/initiate")!
var request = URLRequest(url: initiateURL)
request.httpMethod = "POST"
request.setValue(apiKey, forHTTPHeaderField: "x-api-key")
URLSession.shared.dataTask(with: request) { data, _, _ in
guard let data = data,
let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
let authorizeURLString = json["authorize_url"] as? String,
let authorizeURL = URL(string: authorizeURLString) else { return }
// 2. Open Strava auth in ASWebAuthenticationSession
let session = ASWebAuthenticationSession(
url: authorizeURL,
callbackURLScheme: "https" // or your custom scheme
) { callbackURL, error in
guard let callbackURL = callbackURL, error == nil else { return }
// 3. Extract code from callback URL
let components = URLComponents(url: callbackURL, resolvingAgainstBaseURL: false)
let code = components?.queryItems?.first(where: { $0.name == "code" })?.value
let scope = components?.queryItems?.first(where: { $0.name == "scope" })?.value
guard let code = code else { return }
// 4. Send code to backend
self.completeStravaAuth(code: code, scope: scope)
}
session.presentationContextProvider = self
session.prefersEphemeralWebBrowserSession = false // Share Safari cookies
DispatchQueue.main.async {
session.start()
}
}.resume()
}
func completeStravaAuth(code: String, scope: String?) {
let callbackURL = URL(string: "https://app.p247.io/connections/strava/callback")!
var request = URLRequest(url: callbackURL)
request.httpMethod = "POST"
request.setValue(apiKey, forHTTPHeaderField: "x-api-key")
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
let body: [String: Any] = ["code": code, "scope": scope ?? ""]
request.httpBody = try? JSONSerialization.data(withJSONObject: body)
URLSession.shared.dataTask(with: request) { data, _, _ in
// Handle success: update UI to show connected state
// Refresh the connections list
}.resume()
}
4.2 Callback URL Handling
The redirect_uri is set to https://app.p247.io/connections/strava/callback. For ASWebAuthenticationSession to intercept this:
Option A: Universal Links (recommended)
- Register
app.p247.ioas an associated domain in your app - Add an
apple-app-site-associationfile athttps://app.p247.io/.well-known/apple-app-site-association - The redirect will open directly in your app
Option B: Custom URL Scheme
- Register a custom scheme like
p247:// - Change
STRAVA_REDIRECT_URIon the backend top247://strava/callback - Update Strava API settings to allow this redirect URI
Option A is cleaner for App Store review. Option B is faster to implement. Either works.
Important: The redirect URI must be registered in Strava’s API settings at https://www.strava.com/settings/api. Add the chosen URI to the “Authorization Callback Domain” field.
4.3 UI Design
Connections Screen (in Settings/Profile):
┌─────────────────────────────────────┐
│ Connected Services │
│ │
│ 🟠 Strava [Connect] │
│ ⚪ Garmin Coming Soon │
│ ⚪ Whoop Coming Soon │
│ ⚪ Oura Coming Soon │
└─────────────────────────────────────┘
After connecting:
┌─────────────────────────────────────┐
│ Connected Services │
│ │
│ 🟢 Strava Myles Bruggeling │
│ Connected 23 Mar 2026 │
│ Last sync: 2 min ago │
│ [Disconnect] │
│ │
│ ⚪ Garmin Coming Soon │
│ ⚪ Whoop Coming Soon │
│ ⚪ Oura Coming Soon │
└─────────────────────────────────────┘
4.4 Error Handling
| Scenario | Action |
|---|---|
| Athlete denies Strava permission | Show “Strava connection cancelled. You can connect later from Settings.” |
| Token exchange fails (400 from backend) | Show “Could not connect to Strava. Please try again.” |
| Network error during OAuth | Show “Connection failed. Check your internet and try again.” |
| Athlete connects a different Strava account | Backend overwrites the previous connection (one Strava per P247 user). |
5. Strava API Settings
The following must be configured in Strava’s API settings (https://www.strava.com/settings/api):
- Application Name: P247
- Category: Training & Analysis
- Website: https://p247.io
- Authorization Callback Domain:
app.p247.io(or your custom scheme domain) - Scopes requested:
read,activity:read_all,profile:read_all
The Strava API app is already registered (Client ID: 205651). The callback domain may need updating to include the production redirect URI.
6. What Happens After Connection
Once the backend has tokens:
- Backend polls Strava for activities every 30 minutes (using the stored access/refresh tokens)
- New activities are merged into the athlete’s training data
- The daily brief includes Strava activity details (workout names, pace, HR, training load)
- The coaching agent sees Strava data in its context
The athlete doesn’t need to do anything else. Data flows automatically.
7. Testing
- Tap “Connect” on the Strava row
- Strava auth page opens (may auto-login if already signed into Strava in Safari)
- Tap “Authorize” on Strava’s permission screen
- Redirect back to app
- Connections screen shows green indicator + athlete name
GET /connections/confirms"connected": true- Next brief generation includes Strava training data