Frame.io Python SDK — Authentication Guide

This guide explains how to authenticate with the Frame.io API using the Frame.io Python SDK (frameio). The Frame.io V4 API uses Adobe Identity Management Service (IMS), Adobe’s OAuth 2.0 identity platform.

This is a standalone reference for Python developers. All code examples and flows below are for the frameio package only.


Authentication Types in the Python SDK

The Python SDK supports four authentication options:

MethodUse caseUser interaction?Requires client secret?
Static tokenQuick scripts, testing, or you already have a tokenNoNo
Server-to-ServerBackend services, cron jobs, automationNoYes
Web AppServer-side apps (Flask, Django, FastAPI)YesYes
SPA (PKCE)Browser apps, CLIs, or any app that can’t store a secretYesNo

Server-to-Server lets your app act as a service account with no user interaction. It’s only available to Frame.io V4 accounts administered via the Adobe Admin Console.

Web App and SPA let your app act as a specific user. Both use Adobe IMS under the hood: the user authorizes your app, and the SDK exchanges the resulting code for tokens. The Python SDK handles the IMS /authorize/v2 and /token/v3 flow for you. For Web App you need a client secret; for SPA you use PKCE instead.

Adobe’s Native App credential requires custom URI scheme handlers (e.g. adobe+<hash>://…) that intercept redirects at the OS level. Python has no standard way to register such handlers, so the Python SDK does not offer a NativeAppAuth class. For user-interactive Python apps, use WebAppAuth with a local callback server (e.g. Flask or FastAPI). For non-interactive workloads, use ServerToServerAuth.


Service Account Users

When you use Server-to-Server authentication, your application acts as a service account user, a distinct account type that can perform actions on behalf of the service. These are visible to other users in Frame.io: when a service account takes an action, its name is displayed in the UI.

You can grant and revoke service account access through the Adobe Admin Console and Developer Console. Service account names are managed from the Frame.io UI. By default, your first S2S connection is named Service Account User, the second Service Account User 2, and so on.


Quick Start

Prerequisites

  1. Credentials from the Adobe Developer Console:

    • Client ID — required for all OAuth flows
    • Client Secret — required for Server-to-Server and Web App flows
    • Redirect URI — required for Web App and SPA flows; must be registered in your Adobe project
  2. Install the SDK:

$pip install frameio

Choosing a method

  • No user involved? Use Server-to-Server (ServerToServerAuth).
  • User involved and you can store a secret? Use Web App (WebAppAuth).
  • User involved but you can’t store a secret? Use SPA (SPAAuth).

Access Token

If you already have an access token (from another OAuth system or a prior exchange, e.g. via our API Explorer) you can pass it directly:

1from frameio import Frameio
2
3client = Frameio(token="YOUR_ACCESS_TOKEN")

This is the simplest approach, but the token will eventually expire and the SDK won’t refresh it for you.

Legacy Developer Tokens

For V4-migrated accounts not yet administered via the Adobe Admin Console, you can continue to use Legacy Developer Tokens from the Frame.io developer site. You must include the x-frameio-legacy-token-auth header and set it to true:

1from frameio import Frameio
2
3client = Frameio(
4 token="YOUR_LEGACY_DEVELOPER_TOKEN",
5 headers={"x-frameio-legacy-token-auth": "true"},
6)

Legacy developer tokens do not expire, but they are a transitional mechanism. For new integrations and production workloads, we recommend using one of the OAuth 2.0 flows below. See the Migration Guide for details.


Server-to-Server (Client Credentials)

Use this for backend services and scripts that need Frame.io access without user interaction. This flow is only available to Frame.io V4 accounts administered via the Adobe Admin Console. Your application authenticates as a service account user with no human in the loop.

1from frameio import Frameio
2from frameio.auth import ServerToServerAuth
3
4auth = ServerToServerAuth(
5 client_id="YOUR_CLIENT_ID",
6 client_secret="YOUR_CLIENT_SECRET",
7)
8
9client = Frameio(token=auth.get_token)

That’s it. auth.get_token is a callable that the SDK invokes on every request. If the current token is still valid, it returns immediately. If it’s about to expire, it fetches a new one first, completely transparently.

How it works

Your client credentials (client ID + secret) never expire. You only rotate them manually for security hygiene. S2S gives you effectively permanent, uninterrupted API access with zero manual intervention.

Under the hood:

  1. On the first API call, get_token requests a new access token from Adobe IMS using the client_credentials grant.
  2. The token is cached in memory. Individual access tokens expire (typically 24 hours), but this is handled for you.
  3. When a cached token is within the refresh buffer (default: 60 seconds before expiry), the SDK fetches a fresh one automatically using the same client credentials.
  4. No refresh tokens are involved. The client credentials themselves are the long-lived secret, and they can always be used to mint a new access token.

Explicit authentication

If you want to fetch the token eagerly (for example, to fail fast on bad credentials at startup):

1auth = ServerToServerAuth(client_id="...", client_secret="...")
2auth.authenticate() # raises AuthenticationError if credentials are invalid
3client = Frameio(token=auth.get_token)

Web App (Authorization Code)

Use this for server-side applications where users sign in with their Adobe ID. This flow requires a client secret, which must be stored securely on your server.

1

Redirect the user to Adobe IMS

1from frameio.auth import WebAppAuth
2
3auth = WebAppAuth(
4 client_id="YOUR_CLIENT_ID",
5 client_secret="YOUR_CLIENT_SECRET",
6 redirect_uri="https://yourapp.com/callback",
7)
8
9# Generate a cryptographically random state value to prevent CSRF attacks
10import secrets
11state = secrets.token_urlsafe(32)
12
13authorization_url = auth.get_authorization_url(state=state)
14# Store `state` in the user's session, then redirect them to `authorization_url`
2

Handle the callback

When Adobe IMS redirects the user back to your redirect_uri, extract the code and state parameters. Verify the state matches what you stored, then exchange the code for tokens:

1# In your callback handler (e.g. a Flask/FastAPI route):
2auth.exchange_code(code=request.args["code"])

This exchanges the authorization code for an access token and a refresh token, storing both internally.

3

Use the client

1from frameio import Frameio
2
3client = Frameio(token=auth.get_token)

That’s it. From this point on, get_token manages the token lifecycle automatically. When the access token approaches expiry, the SDK uses the refresh token to obtain a new one. No user interaction required.

Full Flask example

1import secrets
2from flask import Flask, redirect, request, session
3from frameio import Frameio
4from frameio.auth import WebAppAuth
5
6app = Flask(__name__)
7app.secret_key = secrets.token_bytes(32)
8
9auth = WebAppAuth(
10 client_id="YOUR_CLIENT_ID",
11 client_secret="YOUR_CLIENT_SECRET",
12 redirect_uri="http://localhost:5000/callback",
13)
14
15@app.route("/login")
16def login():
17 state = secrets.token_urlsafe(32)
18 session["oauth_state"] = state
19 return redirect(auth.get_authorization_url(state=state))
20
21@app.route("/callback")
22def callback():
23 if request.args.get("state") != session.pop("oauth_state", None):
24 return "Invalid state parameter", 403
25
26 auth.exchange_code(code=request.args["code"])
27
28 client = Frameio(token=auth.get_token)
29 me = client.users.me()
30 return f"Hello, {me.name}!"

Single Page App / PKCE (Authorization Code + PKCE)

Use this for browser-based applications, desktop apps, or CLI tools that cannot securely store a client secret. This flow uses PKCE (RFC 7636) to protect the authorization code exchange.

1

Generate the authorization URL

1from frameio.auth import SPAAuth
2
3auth = SPAAuth(
4 client_id="YOUR_CLIENT_ID",
5 redirect_uri="https://yourapp.com/callback",
6)
7
8import secrets
9state = secrets.token_urlsafe(32)
10
11result = auth.get_authorization_url(state=state)
12# result.url -> redirect the user here
13# result.code_verifier -> store this securely until the callback

get_authorization_url returns an AuthorizationUrlResult containing the full URL (with the PKCE code_challenge embedded) and the code_verifier you’ll need in the next step.

2

Exchange the code with the verifier

When the user is redirected back:

1auth.exchange_code(
2 code="CODE_FROM_CALLBACK",
3 code_verifier=result.code_verifier,
4)
3

Use the client

1from frameio import Frameio
2
3client = Frameio(token=auth.get_token)

That’s it. Refresh works the same as Web App — the SDK uses the refresh token automatically. The difference is that no client secret is sent during refresh, since the SPA flow is designed for public clients.


Async Usage

Every auth class has an async counterpart prefixed with Async. The code examples above include Sync and Async tabs where applicable.

SyncAsync
ServerToServerAuthAsyncServerToServerAuth
WebAppAuthAsyncWebAppAuth
SPAAuthAsyncSPAAuth

The API is identical. get_authorization_url remains synchronous (no I/O), while exchange_code, refresh, revoke, and get_token are all async. Use the async classes with AsyncFrameio.

Manual token refresh

For Web App and SPA flows, the SDK refreshes tokens automatically via get_token. If you need explicit control, you can call refresh() directly:

1auth.refresh() # fetches a new access token using the refresh token

This is useful when you want to force a refresh ahead of a critical operation rather than relying on the automatic refresh buffer.


Token Persistence

All auth classes support export_tokens() and import_tokens() for persisting token state across restarts. For Web App and SPA flows this is especially important, since the access and refresh tokens live in memory by default — if your application restarts, users would need to re-authenticate unless you persist them. For Server-to-Server, persistence is optional (the client credentials can always mint a new token), but importing a cached token avoids an extra round-trip on startup.

Export and import

1# After exchange_code(), save the token state
2token_data = auth.export_tokens()
3# token_data is a dict: {"access_token": "...", "refresh_token": "...", "expires_at": 1234567890.0}
4# Save it to your database, file, or secret store
5
6# On next startup, restore it
7auth.import_tokens(token_data)
8client = Frameio(token=auth.get_token)
9# The SDK will automatically refresh if the token is near expiry

Automatic persistence with on_token_refreshed

To persist tokens automatically every time they’re refreshed, use the on_token_refreshed callback:

1import json
2from pathlib import Path
3
4TOKEN_FILE = Path("tokens.json")
5
6def save_tokens(tokens: dict):
7 TOKEN_FILE.write_text(json.dumps(tokens))
8
9auth = WebAppAuth(
10 client_id="...",
11 client_secret="...",
12 redirect_uri="...",
13 on_token_refreshed=save_tokens,
14)
15
16# On startup, restore if available
17if TOKEN_FILE.exists():
18 auth.import_tokens(json.loads(TOKEN_FILE.read_text()))

The callback receives the same dict shape as export_tokens() and fires after every successful token refresh.

For the async classes, on_token_refreshed can be either a regular function or an async function. Both are supported.


Revoking Tokens

To sign out a user and invalidate their tokens with Adobe IMS:

1auth.revoke()

This makes a best-effort revocation request to Adobe IMS for both the access token and the refresh token, then clears all local token state. After revoking, the user will need to re-authenticate.

For the async classes, use await auth.revoke().


Error Handling

All auth errors inherit from FrameioAuthError, so you can catch them broadly or handle specific cases:

1from frameio.auth import (
2 FrameioAuthError,
3 AuthenticationError,
4 TokenExpiredError,
5 ConfigurationError,
6 NetworkError,
7 RateLimitError,
8)
9
10try:
11 auth.exchange_code(code="...")
12except TokenExpiredError:
13 # The refresh token has expired; redirect the user to sign in again
14 pass
15except AuthenticationError as e:
16 # Token exchange failed
17 print(f"Error: {e.error_code} - {e.error_description}")
18except NetworkError:
19 # Timeout or connection failure (after retries)
20 pass
21except RateLimitError as e:
22 # 429 from Adobe IMS; retry after e.retry_after seconds
23 pass
24except FrameioAuthError:
25 # Catch-all for any other auth error
26 pass

Error reference

ExceptionWhen it’s raised
ConfigurationErrorMissing or invalid configuration (e.g. empty client_id, non-HTTPS redirect URI)
AuthenticationErrorToken exchange or refresh rejected by Adobe IMS (has .error_code and .error_description)
TokenExpiredErrorRefresh token itself is expired; user must re-authenticate
NetworkErrorHTTP timeout or connection failure after all retries
RateLimitErrorAdobe IMS returned 429; check .retry_after for backoff guidance
PKCEErrorPKCE verification failed (SPA flow)

Handling expired refresh tokens in production

For Web App and SPA flows, the refresh token will eventually expire. When that happens, get_token raises TokenExpiredError. You should catch this and redirect the user through the authorization flow again.

1from frameio.auth import TokenExpiredError
2
3try:
4 client = Frameio(token=auth.get_token)
5 assets = client.files.list(project_id="...")
6except TokenExpiredError:
7 # Clear persisted tokens and redirect user to login
8 auth.revoke()
9 return redirect("/login")

Configuration Reference

All auth classes accept these optional parameters:

ParameterDefaultDescription
scopesFlow-specific defaultsSpace-separated OAuth scopes. S2S defaults to openid AdobeID frame.s2s.all; user-facing flows default to openid email profile offline_access additional_info.roles.
ims_base_urlhttps://ims-na1.adobelogin.comAdobe IMS base URL. Override for staging or non-production environments.
http_clientNoneCustom httpx.Client (or httpx.AsyncClient) for proxy, mTLS, or connection pooling.
timeout30HTTP request timeout in seconds for token endpoint calls.
max_retries2Maximum retries for transient failures (5xx, timeouts). Rate-limit retries (429) are tracked separately.
refresh_buffer60Seconds before token expiry to trigger proactive refresh.
on_token_refreshedNoneCallback fired after every successful token refresh. Receives a dict with access_token, refresh_token, and expires_at.

Staging environments

Point at a staging Adobe IMS instance by overriding ims_base_url. The SDK also exports DEFAULT_IMS_BASE_URL (https://ims-na1.adobelogin.com) if you need to reference the production value programmatically.

1auth = ServerToServerAuth(
2 client_id="...",
3 client_secret="...",
4 ims_base_url="https://ims-na1-stg1.adobelogin.com",
5)

Custom HTTP client

For proxy support or custom TLS configuration:

1import httpx
2
3http_client = httpx.Client(
4 proxy="http://corporate-proxy:8080",
5 verify="/path/to/custom-ca-bundle.pem",
6)
7
8auth = ServerToServerAuth(
9 client_id="...",
10 client_secret="...",
11 http_client=http_client,
12)

Thread Safety

The sync auth classes are fully thread-safe. When multiple threads call get_token simultaneously and a refresh is needed, only one thread performs the refresh. The others wait and receive the same result. No external locking is required.

The async classes provide the same guarantee using asyncio.Lock, safe for concurrent coroutines within a single event loop.