OAuth Web App (Authorization Code / PKCE) token exchange returns "Unauthorized" — account works fine via API Explorer

Hi Frame.io team,

We’re building an internal integration (via n8n, self-hosted) to automate media uploads to Frame.io V4 from our post-production pipeline. We’re stuck at the OAuth token exchange step and would appreciate help diagnosing it, since we’ve run out of things we can check on our end.

Account context

  • Frame.io account: “Madam&Cie”
  • Authenticated by email: production@madamcie.fr (confirmed Adobe ID, linked)
  • Account fields from GET /v4/accounts: "v4_migrated_at": null, "adobe_id": null — so this looks like a Frame-managed (V4F) account, not yet Adobe-managed (V4A).

What works

Using the interactive API Explorer at List accounts | Frame.io API Documentation , logging in with our account email and calling List accounts succeeds and returns our account correctly. So the account itself has full V4 API access — this isn’t an entitlement issue.

What we’re building

An Adobe Developer Console project with a Frame.io API added, using an OAuth Web App credential (confidential client, with Client Secret). Redirect URI is a temporary HTTPS tunnel pointing to our self-hosted automation server (n8n), used for the one-time authorization step. We added our account email as a Beta user on the Developer Console project (this fixed an earlier “Please contact the application developer to gain access to Project” error, per your forum’s guidance on a similar thread).

The problem

When we run the standard Authorization Code / PKCE flow:

  1. GET https://ims-na1.adobelogin.com/ims/authorize/v2 with client_id, redirect_uri, response_type=code, scope=openid,profile,email,offline_access,additional_info.roles, state, and PKCE code_challenge/code_challenge_method=S256 — this appears to succeed (browser is auto-authenticated via existing Adobe session, no visible error, redirects back within 2-3 seconds).
  2. Our client (n8n’s OAuth2 module) then calls POST https://ims-na1.adobelogin.com/ims/token/v3?client_id={CLIENT_ID} with code, grant_type=authorization_code, code_verifier in the body (also tried with client_secret included, and with authorizationCode grant type instead of PKCE) — this consistently returns an Unauthorized error with no additional body/detail surfaced to us.

We’ve tried:

  • Scope as comma-separated vs space-separated
  • Grant type: Authorization Code (with client_secret) and PKCE (with code_verifier)
  • client_id as a query parameter on the token URL (per your docs’ curl example) vs. only in the body

All combinations fail the same way at the token exchange step.

What we’d like to know

  1. Is there anything else required for a V4F (Frame-managed) account to support the OAuth Web App authorization code flow — e.g., does the account/org need any additional linking beyond the individual user’s Adobe ID?
  2. Could you check server-side logs for token requests from our OAuth Web App integration around today’s date, to see the actual rejection reason? Happy to share the Client ID privately (not posting it here publicly).
  3. Is there a recommended exact parameter set (grant type, whether client_secret is expected alongside PKCE, where client_id should be passed) for this specific account type?

Happy to provide more detail (timestamps, Client ID, full request logs) privately on request.

Thanks for your help!

Hi @percevigne!

The first thing I would try is removing email from your scopes to the authorize endpoint. This is unlikely to be your issue, but it’s low lift and worth giving a try. If this does not solve your issue, go ahead and add it back.

Typically for the Web App type, you don’t need to use the code challenge and code verifier. The full PKCE flow is generally for Single Page and Native App types, where the client ID and client secret can’t be stored. Additionally, you can pass both the client ID and the client secret when requesting the token.

For my call to the authorize endpoint, this is what I use:

URL: GET https://ims-na1.adobelogin.com/ims/authorize/v2

Parameters:

{
  'client_id': client_id,
  'scope': scopes,
  'response_type': 'code',
  'redirect_uri': redirect_uri
}

For my callback request using WebApp, this is what I use:

URL: POST https://ims-na1.adobelogin.com/ims/token/v3

Parameters:

{
  'grant_type': 'authorization_code',
  'client_id': client_id,
  'client_secret': client_secret,
  'code': authorization_code
}

It’s possible that it’s rejecting the PKCE parameters on the token endpoint.