ConnectQuickstart: Python

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 requests

Environment 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.py

Open 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 tokens

Production 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=True and SESSION_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.