Setting up Frameio v4 for python

hi @joeleveson we have our developer docs and a specific python sdk to use to help with API calls (the repo is private atm but it is available on pypi to pull and the references are included).

to get accounts you would do something like:

from frameio import Frameio

client = Frameio(
    base_url="https://api.frame.io"
)

client.accounts.index()

Native Apps need to use PKCE for authorization so you would need to get a token using that method.

Here’s some pseudo code that might work for you for getting an auth token (FYI this is just what Claude Code spit out for me so YMMV):

import hashlib
import base64
import secrets
import urllib.parse
import webbrowser
from urllib.parse import urlparse, parse_qs

class AdobeIMSPKCEAuth:
    def __init__(self, client_id, redirect_uri, scopes):
        self.client_id = client_id
        self.redirect_uri = redirect_uri
        self.scopes = scopes
        
    def generate_pkce_challenge(self):
        # Generate code verifier (43-128 characters)
        code_verifier = base64.urlsafe_b64encode(
            secrets.token_bytes(32)
        ).decode('utf-8').rstrip('=')
        
        # Generate code challenge (SHA256 hash of verifier)
        code_challenge = base64.urlsafe_b64encode(
            hashlib.sha256(code_verifier.encode()).digest()
        ).decode('utf-8').rstrip('=')
        
        return code_verifier, code_challenge
    
    def get_authorization_url(self, code_challenge, state):
        base_url = "https://ims-na1.adobelogin.com/ims/authorize/v2"
        params = {
            "client_id": self.client_id,
            "redirect_uri": self.redirect_uri,
            "scope": " ".join(self.scopes),
            "response_type": "code",
            "code_challenge": code_challenge,
            "code_challenge_method": "S256",
            "state": state
        }
        return f"{base_url}?{urllib.parse.urlencode(params)}"
    
    def exchange_code_for_token(self, authorization_code, code_verifier):
        import requests
        
        token_url = "https://ims-na1.adobelogin.com/ims/token/v3"
        
        data = {
            "grant_type": "authorization_code",
            "client_id": self.client_id,
            "code": authorization_code,
            "redirect_uri": self.redirect_uri,
            "code_verifier": code_verifier
        }
        
        headers = {
            "Content-Type": "application/x-www-form-urlencoded"
        }
        
        response = requests.post(token_url, data=data, headers=headers)
        
        if response.status_code == 200:
            return response.json()
        else:
            raise Exception(f"Token exchange failed: {response.text}")
    
    def authenticate(self):
        # Step 1: Generate PKCE challenge
        code_verifier, code_challenge = self.generate_pkce_challenge()
        state = secrets.token_urlsafe(16)  # CSRF protection
        
        # Step 2: Build authorization URL
        auth_url = self.get_authorization_url(code_challenge, state)
        
        # Step 3: Open browser for user authorization
        print("Opening browser for Adobe IMS authorization...")
        print(f"Authorization URL: {auth_url}")
        webbrowser.open(auth_url)
        
        print("\nAfter authorization, you'll be redirected to your app's deep link.")
        print("If your app isn't installed, you may see an error page with the authorization code in the URL.")
        print("Look for 'code=' parameter in the URL or error message.")
        
        authorization_code = input("Enter authorization code: ").strip()
        
        if not authorization_code:
            raise Exception("No authorization code provided")
        
        # Step 4: Exchange authorization code for access token
        print("Exchanging authorization code for access token...")
        token_response = self.exchange_code_for_token(authorization_code, code_verifier)
        
        return token_response

# Usage example:
def main():
    # Configuration - Replace with your Adobe Developer Console credentials
    CLIENT_ID = "your_client_id_from_adobe_console"
    REDIRECT_URI = "your_redirect_uri_from_adobe_console"  # e.g., "adobe+your_scheme://adobeid/your_client_id"
    SCOPES = ["openid", "offline_access", "additional_info.roles", "email", "profile"]
    
    # Initialize authenticator
    auth = AdobeIMSPKCEAuth(CLIENT_ID, REDIRECT_URI, SCOPES)
    
    try:
        # Perform authentication flow
        token_data = auth.authenticate()
        
        # Extract tokens
        access_token = token_data.get("access_token")
        refresh_token = token_data.get("refresh_token")
        expires_in = token_data.get("expires_in")
        token_type = token_data.get("token_type")
        
        print("\nAuthentication successful!")
        print(f"Access Token: {access_token}")
        print(f"Refresh Token: {refresh_token}")
        print(f"Token Type: {token_type}")
        print(f"Expires in: {expires_in} seconds")
        
        # Use access_token for Adobe API calls:
        # headers = {"Authorization": f"Bearer {access_token}"}
        
    except Exception as e:
        print(f"Authentication failed: {e}")

if __name__ == "__main__":
    main()