Quickstart: Python / Flask
This guide walks through a working Flask app that implements Connect with Loop. By the end you will have a server that redirects users to the Loop consent screen, exchanges the authorization code for tokens, and makes an authenticated API call.
Prerequisites: Python 3.10+, an OAuth client registered at developers.platform.loop.health (see the main quickstart).
Install dependencies
pip install flask requestsEnvironment variables
export LOOP_CLIENT_ID="client_01HXY..."
export LOOP_CLIENT_SECRET="secret_..."
export LOOP_REDIRECT_URI="http://localhost:5000/callback"
export FLASK_SECRET_KEY="$(python3 -c 'import secrets; print(secrets.token_hex(32))')"Full example — app.py
import hashlib
import base64
import os
import secrets
import requests
from flask import Flask, redirect, request, session, jsonify
app = Flask(__name__)
app.secret_key = os.environ["FLASK_SECRET_KEY"]
AUTHORIZE_URL = "https://identity.platform.loop.health/v1/oauth/authorize"
TOKEN_URL = "https://identity.platform.loop.health/v1/oauth/token"
CLIENT_ID = os.environ["LOOP_CLIENT_ID"]
CLIENT_SECRET = os.environ["LOOP_CLIENT_SECRET"]
REDIRECT_URI = os.environ["LOOP_REDIRECT_URI"]
def generate_pkce():
verifier = secrets.token_urlsafe(32)
digest = hashlib.sha256(verifier.encode("ascii")).digest()
challenge = base64.urlsafe_b64encode(digest).rstrip(b"=").decode("ascii")
return verifier, challenge
@app.route("/login")
def login():
verifier, challenge = generate_pkce()
state = secrets.token_hex(16)
session["oauth_state"] = state
session["code_verifier"] = verifier
params = {
"response_type": "code",
"client_id": CLIENT_ID,
"redirect_uri": REDIRECT_URI,
"scope": "openid profile read:biomarkers",
"state": state,
"code_challenge": challenge,
"code_challenge_method": "S256",
}
qs = "&".join(f"{k}={requests.utils.quote(v)}" for k, v in params.items())
return redirect(f"{AUTHORIZE_URL}?{qs}")
@app.route("/callback")
def callback():
error = request.args.get("error")
if error:
return jsonify({"error": error}), 400
state = request.args.get("state")
if state != session.pop("oauth_state", None):
return jsonify({"error": "Invalid state — possible CSRF"}), 400
code = request.args.get("code")
verifier = session.pop("code_verifier")
token_resp = requests.post(
TOKEN_URL,
data={
"grant_type": "authorization_code",
"code": code,
"redirect_uri": REDIRECT_URI,
"client_id": CLIENT_ID,
"client_secret": CLIENT_SECRET,
"code_verifier": verifier,
},
)
if not token_resp.ok:
return jsonify({"error": "Token exchange failed", "detail": token_resp.json()}), 502
tokens = token_resp.json()
# Make an authenticated API call
bio_resp = requests.get(
"https://clinical.platform.loop.health/v1/biomarkers/me",
headers={"Authorization": f"Bearer {tokens['access_token']}"},
)
return jsonify({
"message": "Connected to Loop!",
"scopes": tokens["scope"],
"biomarkers": bio_resp.json() if bio_resp.ok else None,
})
if __name__ == "__main__":
app.run(port=5000, debug=True)Run it
python app.pyOpen http://localhost:5000/login in a browser. After granting access on the Loop consent screen, the callback endpoint exchanges the code and calls the biomarkers API.
Token refresh
def refresh_tokens(refresh_token: str) -> dict:
resp = requests.post(
TOKEN_URL,
data={
"grant_type": "refresh_token",
"refresh_token": refresh_token,
"client_id": CLIENT_ID,
"client_secret": CLIENT_SECRET,
},
)
resp.raise_for_status()
tokens = resp.json()
# IMPORTANT: always store the new refresh_token — the old one is invalidated
return tokensProduction considerations
- Store tokens in an encrypted database, not in the Flask session.
- Use
requests.Session()with retry/backoff for production API calls. - Set
SESSION_COOKIE_SECURE=TrueandSESSION_COOKIE_HTTPONLY=True. - Never log access tokens or refresh tokens.
Next steps
- Authorization flow — understand the full state machine and error paths.
- Scopes — choose the right scopes for your integration.
- Security — PKCE details, redirect URI rules, what to never log.