Quick question about the multi-org OAuth flow. Our Premium App receives a 403 Forbidden on the /oauth2/token endpoint when requesting a refresh token for a secondary org. The client_id and secret are correct, and the initial auth code exchange succeeded. Scopes include organization:read and user:read. The error payload indicates insufficient permissions, but the partner admin role has full access. Any insights on scope inheritance?
TL;DR: Scope mismatch in the token request.
This is caused by the refresh token request lacking the original scope parameter from the authorization code grant. The token endpoint defaults to the intersection of requested scopes if not explicitly provided, often stripping organization:read for secondary orgs. Include the full scope list in the POST body.
Make sure you are not just sending the scope string in the body but also verifying the token’s actual claims against the specific org ID. The suggestion above about including the scope parameter is correct, but it often misses the root cause in multi-tenant setups: the token might be valid for the primary org but lacks the specific org_id claim for the secondary environment if the initial auth code was generated without the multi_org flag.
I’ve seen this pattern in load tests where the OAuth service returns a 200 OK but the subsequent API calls fail with 403s because the token’s org_ids array is empty or restricted. For AppFoundry partners, the initial authorization request needs to explicitly pass the org_id parameter in the query string, not just in the token exchange.
Here is how the initial auth URL should look to ensure the token inherits the correct org context:
GET https://login.mypurecloud.com/oauth2/authorize?
client_id=YOUR_CLIENT_ID&
redirect_uri=YOUR_REDIRECT_URI&
response_type=code&
scope=organization:read user:read&
org_id=SECONDARY_ORG_ID&
state=random_state_string
If you skip the org_id in the authorize step, the resulting refresh token will only carry permissions for the default org associated with the client app. When you try to use it for the secondary org, the token endpoint doesn’t throw an error, but the token itself is insufficient for cross-org operations.
Also, check your JMeter or Postman logs for the exp and org_ids claims in the decoded JWT. If org_ids is missing the secondary ID, the 403 is expected behavior, not a bug.
Note: Always validate the token payload immediately after generation. If the org_ids claim doesn’t match your target environment, the refresh token exchange is technically successful but functionally useless for cross-org access.