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:
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.
See Automate your setup using Frame.io server to server support for more information.
Quick Start
Prerequisites
-
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
-
Install the SDK:
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:
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:
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.
Sync
Async
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:
- On the first API call,
get_tokenrequests a new access token from Adobe IMS using theclient_credentialsgrant. - The token is cached in memory. Individual access tokens expire (typically 24 hours), but this is handled for you.
- 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.
- 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):
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.
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:
Sync
Async
This exchanges the authorization code for an access token and a refresh token, storing both internally.
Full Flask example
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.
Async Usage
Every auth class has an async counterpart prefixed with Async. The code examples above include Sync and Async tabs where applicable.
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:
Sync
Async
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
Automatic persistence with on_token_refreshed
To persist tokens automatically every time they’re refreshed, use the on_token_refreshed callback:
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:
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:
Error reference
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.
Configuration Reference
All auth classes accept these optional parameters:
Parameter reference
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.
Custom HTTP client
For proxy support or custom TLS configuration:
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.