GET /api/v2/oauth/clients returns empty scopes for custom clients?

Can anyone clarify why the OAuth client list endpoint is stripping scope data for our custom app registrations?

Background

We’re building a tool to audit scope creep across the org before the next quarterly review. Got a script running against /api/v2/oauth/clients to pull the full registry. Works fine for the default platform clients, but our custom integrations are coming back looking weird. We’ve got a dozen custom agent desktop apps deployed and need to map the exact permissions without clicking through the UI for each one. It’s 2am here and this is blocking the deployment pipeline for the new screen pop handler because the validation script keeps failing on empty scope arrays. The embeddable client app sdk requires specific scopes for the widget to load, so i can’t just guess. Been doing this for 8 years, never seen the API lie like this.

Issue

Hitting the endpoint with a valid bearer token, getting 200 OK. The JSON payload has the client IDs and names, but the scopes array is either empty or just ["base"] for clients that definitely have custom:agent-desktop:write assigned in the UI.

{
 "id": "a1b2c3d4-5678",
 "name": "Custom Desktop Widget",
 "type": "confidential",
 "scopes": [] 
}

That’s the response. The UI clearly shows custom:agent-desktop:write and call:monitor attached. The goal is to iterate over the list and flag any client missing custom:agent-desktop:read. Since the array is empty, the script flags everything as missing, which is false. Checked the docs, nothing mentions a filter param or a separate endpoint for scope resolution. This feels like a bug or a permission quirk i’m missing.

Troubleshooting

Tried swapping tokens. Used a super-admin token, same result. Verified the client in the portal directly - scopes are there, active. Even tried the SDK method platformClient.oauthApi.getOAuthClients(), identical output.

Also tried filtering by type=confidential in the query string, no change. Response headers show X-Request-Id but no cache-control hints. Maybe i’m using the wrong endpoint entirely? saw some references to /oauth2/clients but that’s 404.

Is there a hidden query param i need to pass to expand the scope objects? or is this endpoint just cached badly on the backend?

This looks like a caching issue on the client list endpoint rather than a data loss problem. the /api/v2/oauth/clients GET request often returns stale data if the client was created or modified recently. you’ll need to hit the specific client endpoint directly to see the current scope configuration.

curl -X GET "https://api.mypurecloud.com/api/v2/oauth/clients/{clientId}" \
 -H "Authorization: Bearer {access_token}" \
 -H "Content-Type: application/json"

the response from the single-client endpoint always includes the full scopes array. if you’re building an audit tool, don’t rely on the list endpoint for scope details. it’s just a summary view. check this internal guide on OAuth client caching for more details on propagation delays.

yeah, is on the right track. the list endpoint is definitely stripped down for performance reasons. i ran into this exact mess last week while auditing scopes for a Five9 migration. you can’t trust the bulk GET for anything other than ID lookups.

you’ll need to hit the specific client endpoint to get the actual permissions. here’s what worked for me:

curl -X GET "https://api.mypurecloud.com/api/v2/oauth/clients/{clientId}" \
 -H "Authorization: Bearer {access_token}" \
 -H "Accept: application/json"

watch out though. if you’re looping through hundreds of clients, you’re gonna hit rate limits fast. i added a 100ms delay between calls in my python script. also, make sure your service account has oauth:client:read. without that, you’ll get a 403 instead of an empty array, which is way more confusing to debug.

it’s annoying that they don’t expose the full list with scopes, but it keeps the API responses lighter. just grind through the individual calls.

be careful just looping through the client IDs to fix this. if you have a large tenant, hitting /api/v2/oauth/clients/{clientId} for every single entry will get you rate-limited fast. the list endpoint is stripped down, yeah, but it’s intentional to save bandwidth.

if you’re building an audit script, you’ll want to implement a simple queue with exponential backoff. don’t just fire requests in a tight loop.

import time
import requests

def get_scopes_with_backoff(client_id, headers, base_delay=1):
 max_retries = 3
 for attempt in range(max_retries):
 resp = requests.get(f"https://api.mypurecloud.com/api/v2/oauth/clients/{client_id}", headers=headers)
 if resp.status_code == 429:
 delay = base_delay * (2 ** attempt)
 time.sleep(delay)
 continue
 return resp.json()
 return None

also, remember that some scopes are inherited from the application role, not explicitly listed on the client object. if you only check the scopes array, you might miss permissions granted via admin:org:read or similar role-based assignments. cross-reference with the user or service account linked to the client if the scope list looks incomplete.

thanks for the heads up on the rate limits. switched to the individual client endpoint and the scopes are showing up correctly now. the bulk endpoint really is just for IDs.