Getting information about OAuth access tokens
When working with OAuth access tokens in Zendesk, you can't retrieve the full access token after creation. To protect these sensitive credentials from exposure or misuse, Zendesk shows only the first 10 characters of each token in API responses, allowing you to verify and differentiate tokens without revealing the full secret required for authentication. However, you can view other information about the token, such as its scope and id.
This article explains how to list OAuth access tokens to view their related information.
Listing OAuth access tokens
Listing OAuth tokens lets you see how tokens are being used, such as:
- Auditing active tokens: See which OAuth tokens are currently active in your Zendesk account.
- Reviewing token scopes: View the scopes assigned to each token to see the permissions granted, helping to ensure tokens have the appropriate access.
- Associating tokens with users and applications: Helps you track which users or apps can access these tokens.
You can use this information to detect unused or overly permissive tokens, and then revoke or renew them as necessary.
The List Tokens endpoint returns a list of access tokens for a Zendesk account. Only admins or agents with the Manage APIs permission can make the request. By default, the endpoint only returns tokens created through the API. Pass all=true to include tokens created through other means, such as the admin interface. This endpoint does not provide full user or client details or in-depth usage statistics.
curl -G "https://{subdomain}.zendesk.com/api/v2/oauth/tokens" \-H "Authorization: Bearer {access_token}"
Here is an example response:
{"tokens": [{"url": "https://example.zendesk.com/api/v2/oauth/tokens/15022151901588.json","id": 15022151901588,"user_id": 1905826600027,"client_id": 223443,"token": "52d7ef4ee0","expires_at": "2027-01-14T16:22:11Z","refresh_token_expires_at": null,..."scopes": ["tickets:read"]},...]}
- id: Unique identifier for the access token, used for revocation or inspection.
- user_id: The Zendesk user id associated with the token.
- client_id: The OAuth client application id that requested the token.
- token: Partial token string (first 10 characters) for identification only.
- expires_at: When the access token expires, if applicable.
- refresh_token_expires_at: When the associated refresh token expires, if applicable.
- scopes: Permissions granted to the token, such as read or write access to tickets.
Python script to list tokens
Here's a short Python script to list all tokens for your account.
import osimport requests# Load OAuth access token and subdomain from environment variablesZENDESK_ACCESS_TOKEN = os.getenv('ZENDESK_ACCESS_TOKEN')ZENDESK_SUBDOMAIN = os.getenv('ZENDESK_SUBDOMAIN')# Exit if any required environment variables are missingif not all([ZENDESK_ACCESS_TOKEN, ZENDESK_SUBDOMAIN]):print('Error: Missing required environment variables.')exit(1)# Set up authentication using OAuth access tokenHEADERS = {'Authorization': f'Bearer {ZENDESK_ACCESS_TOKEN}'}# The all=true parameter includes tokens created outside of the APIAPI_URL = f"https://{ZENDESK_SUBDOMAIN}.zendesk.com/api/v2/oauth/tokens.json?all=true"response = requests.get(API_URL, headers=HEADERS)response.raise_for_status()tokens = response.json().get("tokens", [])for token in tokens:print(f"Token id: {token['id']}, User: {token['user_id']}, Scopes: {token['scopes']}")
Here's an example output:
Token id: 33160940517527, User: 1509756491122, Scopes: ['read', 'write']Token id: 33188809869719, User: 1509756491122, Scopes: ['read', 'write']Token id: 1501173602742, User: 1509756491122, Scopes: ['any_channel:write']
Python script to show more token details
The following script expands the above example to show:
- Who owns each token (user details)
- Which application issued the token (client details)
- Each token's expiration date, as returned directly in the token list response
import osimport requests# Load OAuth access token and subdomain from environment variablesZENDESK_ACCESS_TOKEN = os.getenv('ZENDESK_ACCESS_TOKEN')ZENDESK_SUBDOMAIN = os.getenv('ZENDESK_SUBDOMAIN')# Exit if any required environment variables are missingif not all([ZENDESK_ACCESS_TOKEN, ZENDESK_SUBDOMAIN]):print("Error: Missing required environment variables.")exit(1)# Set up authentication using OAuth access tokenHEADERS = {'Authorization': f'Bearer {ZENDESK_ACCESS_TOKEN}'}# Base API URL constructed using the Zendesk subdomainBASE_API_URL = f"https://{ZENDESK_SUBDOMAIN}.zendesk.com/api/v2"def get_oauth_tokens():"""Get the list of OAuth access tokens for the Zendesk account."""# all=true includes tokens created outside of the API (e.g. admin interface)url = f"{BASE_API_URL}/oauth/tokens.json?all=true"response = requests.get(url, headers=HEADERS) # Send authenticated GET requestresponse.raise_for_status() # Raise exception on HTTP errorreturn response.json().get('tokens', []) # Return list of tokens or empty listdef get_users(user_ids):"""Retrieve detailed user info.Returns a dictionary mapping user id to user data."""if not user_ids:return {} # Return empty dict if no ids providedusers = {}for user_id in user_ids:url = f'{BASE_API_URL}/users/{user_id}'response = requests.get(url, headers=HEADERS)response.raise_for_status()user_data = response.json().get('user', {})users[user_data['id']] = user_datareturn usersdef get_oauth_client(client_id):"""Retrieve details for a single OAuth client application by client id.Returns a dictionary of client info or empty dict if not found."""url = f"{BASE_API_URL}/oauth/clients/{client_id}.json"response = requests.get(url, headers=HEADERS)if response.status_code == 404:# Client not found or no access, log and return emptyprint(f"OAuth client {client_id} not found or inaccessible.")return {}response.raise_for_status()return response.json().get('client', {})def main():tokens = get_oauth_tokens() # Get all OAuth tokensprint(f"Found {len(tokens)} OAuth tokens:")user_ids = set() # Collect unique user ids from tokensclient_ids = set() # Collect unique OAuth client ids from tokens# Iterate tokens to display summary info (including expiration) and collect related idsfor token in tokens:print(f"Token id: {token['id']}, Partial token: {token.get('token')}, "f"Scopes: {token.get('scopes')}, Expires At: {token.get('expires_at')}")user_ids.add(token.get('user_id'))client_ids.add(token.get('client_id'))# Get detailed user information for collected user idsusers = get_users(user_ids)print("\nFetched User Details:")for uid, user in users.items():print(f"User id: {uid}, Email: {user.get('email')}, Name: {user.get('name')}")# Get and display OAuth client details per collected client idprint("\nFetching OAuth Client Details:")for cid in client_ids:client = get_oauth_client(cid)print(f"Client id: {cid}, Name: {client.get('name')}, Redirect URIs: {client.get('redirect_uri')}")if __name__ == "__main__":main()
Here's an example output:
Found 3 OAuth tokens:Token id: 33160940517527, Partial token: 5b24bd1adc, Scopes: ['read', 'write'], Expires At: NoneToken id: 33188809869719, Partial token: b916c4b6b5, Scopes: ['read', 'write'], Expires At: NoneToken id: 1501173602742, Partial token: ea604660a1, Scopes: ['any_channel:write'], Expires At: 2027-01-14T16:22:11ZFetched User Details:User id: 1509756491122, Email: [email protected], Name: Cris P BaconFetching OAuth Client Details:OAuth client 218 not found or inaccessible.Client id: 218, Name: None, Redirect URIs: NoneClient id: 33160833707415, Name: Talk Make test, Redirect URIs: ['https://www.example.com/oauth/cb/zendesk/']
The example output shows:
- Token ids and partial tokens: Displays each token's unique id and a partial token string to differentiate tokens without exposing the full secrets.
- Scopes and expiration: Shows each token's granted permissions and, where applicable, its expiration date. This allows you to spot tokens that are overly permissive, unexpectedly long-lived, or already expired.
- User details: Maps each token to the human-readable email and name of the user it belongs to, making it easier to identify the person responsible for a given token.
- OAuth client details: Maps each token to the application (client) that requested it, along with its registered redirect URIs. Note that some clients (like id
218in the example) may return a 404 if the client was deleted or is otherwise inaccessible, which the script handles gracefully rather than crashing.