For AI agents: a documentation index is available at the root level at /llms.txt and /llms-full.txt. Append /llms.txt to any URL for a page-level index, or .md for the markdown version of any page.
Developer Tools
CommunityContact UsConsole
DocsAPI Reference
DocsAPI Reference
  • Getting Started
    • Welcome to Frame.io V2 API
    • Key Concepts
    • Authentication
  • Migration
    • V2 to V4 Migration Guide
  • OAuth 2 Applications
    • OAuth 2 Code Authorization Flow
    • Building an OAuth2 App
    • Refreshing OAuth 2 Tokens
  • Workflows - Assets
    • Reading the File Tree
    • Uploading Assets
    • Manage Version Stacks
    • Working with Annotations
    • Search for Assets
  • Workflows - Projects
    • Working with Review Links
    • Gather All Comments from a Project
  • Workflows - Admin
    • User Management
    • Working with Audit Logs
  • Automations - Webhooks
    • Webhooks Overview
    • Comment Workflows with Zapier
  • Automations - Zapier
    • Zapier Basics
    • Frame.io Resources in Zapier
    • Upload Assets to Frame.io using Zapier
    • Webhooks in Zapier
  • Custom Actions
    • Custom Actions Overview
    • Three Ways to Deploy Custom Actions
    • Deploy Custom Actions to Zapier
  • Other Tools
    • Using ngrok
    • Using Glitch
  • Troubleshooting
    • API Error Codes
    • Rate Limits
    • Browser Support
  • Deprecated
    • How to - Authorize (Hardware)
    • How to - Authorize (Application)
    • How to - Manage Auth (Hardware)
    • How to - Manage Auth (Application)

© 2026 Adobe Inc. All rights reserved.

TermsPrivacyDo not sell or share my personal information
Developer-friendly docs for your API
Logo
Developer Tools
CommunityContact UsConsole
On this page
  • Application basics
  • Invoking the auth server
  • The callback
  • Without PKCE
  • With PKCE
  • Successful response
OAuth 2 Applications

Building an OAuth 2 App

Was this page helpful?
Previous

Refreshing OAuth 2 Tokens

Next
This guide assumes you've already registered an OAuth 2 app

If not, please refer to OAuth 2 Code Flow to configure your application.

Application basics

Note: you should store your client_id and (if not using PKCE) client_secret safely and access them via your environment. The below examples assume the presence of a .env file containing the variables CLIENT_ID and CLIENT_SECRET.

Python
1import urllib, requests, requests.auth
2import os
3
4CLIENT_ID = os.environ.get('CLIENT_ID')
5CLIENT_SECRET = os.environ.get('CLIENT_SECRET')
6
7AUTHORIZE_URL = "https://applications.frame.io/oauth2/auth"
8TOKEN_URL = "https://applications.frame.io/oauth2/token"
9# The scopes you've chosen for your app, space-delimited
10SCOPE = "offline account.read asset.read"
11# The callback URI for your app
12REDIRECT_URI = "https://yourapp.domain/callback"

Invoking the auth server

First, your application will need to call the Frame.io auth server, which will then redirect the user to a login page.

Python
1import uuid
2from urllib.parse import urlencode
3
4def create_auth_url():
5 credentials = {
6 'response_type': 'code',
7 'redirect_uri': REDIRECT_URI,
8 'client_id': CLIENT_ID,
9 'scope': SCOPE,
10 'state': str(uuid.uuid4())
11 }
12 url = (AUTHORIZE_URL + "?" + urlencode(credentials))
13 return url

The callback

The auth server will then make a GET request to your REDIRECT_URI, which in turn will need to call the TOKEN_URL. This callback will be slightly different depending on whether or not your application is configured to use PKCE.

Without PKCE

If you’re not using PKCE, your callback must include an Authorization header that includes your CLIENT_ID and CLIENT_SECRET.

Python
1def callback():
2 state = request.args.get('state')
3 scope = request.args.get('scope')
4 code = request.args.get('code')
5 error = request.args.get('error')
6
7 if error:
8 return "Error: " + error
9
10 # Set up for client authorization and set up the data you need to send.
11 client_auth = requests.auth.HTTPBasicAuth(CLIENT_ID, CLIENT_SECRET)
12
13 post_data = {
14 "grant_type": "authorization_code",
15 "code": code,
16 "redirect_uri": REDIRECT_URI,
17 "state": state,
18 "scope": SCOPE
19 }
20
21# Send a POST request with the data you need to receive an access token.
22# If everything goes well, it will be returned to you and you can use it with
23# Frame.io.
24
25 response = requests.post(TOKEN_URL, auth=client_auth, data=post_data)
26 return response.text

With PKCE

If you’re using PKCE, your callback must not include an Authorization header, but must include your CLIENT_ID in its POST request body when calling back to the TOKEN_URL.

Python
1def callback():
2 state = request.args.get('state')
3 scope = request.args.get('scope')
4 code = request.args.get('code')
5 error = request.args.get('error')
6
7 if error:
8 return "Error: " + error
9
10# If using PKCE, you must include the CLIENT_ID in your request body
11 post_data = {
12 "grant_type": "authorization_code",
13 "code": code,
14 "redirect_uri": REDIRECT_URI,
15 "state": state,
16 "scope": SCOPE
17 "client_id": CLIENT_ID
18 }
19
20# Send a POST request with the data you need to receive an access token.
21# If everything goes well, it will be returned to you and you can use it with
22# Frame.io
23
24 # If using PKCE, use the below request with no auth
25 response = requests.post(TOKEN_URL, data=post_data)
26
27 return response.text

Successful response

If your callback is successful, you will receive a JSON response that looks like this:

1{
2 "access_token":"BEARER_TOKEN",
3 "expires_in":3600,
4 "refresh_token":"REFRESH_TOKEN",
5 "scope":"account.read offline",
6 "token_type":"bearer"
7}

You can now use the access_token to make API calls to Frame.io on the logged-in user’s behalf, and the refresh_token to request a new access_token after this token expires.