Using OAuth to authenticate API requests
This guide explains how to use OAuth access tokens to authenticate API requests to Zendesk. It covers the two supported OAuth flows, how to implement token refresh, and security best practices.
OAuth is the recommended authentication method because it:
- Doesn’t require users to share their personal API token with your integration
- Is tied to a user account, so tokens inherit that user's permissions and can't be used to access resources beyond what the authorizing user can access
- Supports fine-grained permissions through scopes, so you can limit a token to only the access your integration actually needs
- Uses short-lived tokens that expire automatically, reducing the risk if a token is compromised
- Is optimal for long-term app security and maintenance
How OAuth authentication works
Instead of sending a static API token with every request, your integration obtains an OAuth access token and includes that in the request header. Access tokens are short-lived and paired with a refresh token that your integration uses to automatically get a new access token when the current one expires.
Before you begin
Before you start, make sure you have:
- Access to the app or integration code that currently uses API token authentication.
- Information about whether your integration uses API tokens directly or through a custom authentication flow.
- A Zendesk account with admin access if you need to set up a new client for your integration.
Which OAuth flow should you use?
Zendesk supports two OAuth flows. Identify which OAuth flow matches your integration:
- Authorization code: Your integration acts on behalf of a user who signs in to their Zendesk account and approves access. Use this for user-facing apps and integrations that need per-user permissions.
- Client credentials: Your integration authenticates on behalf of the user who created the OAuth client, with no user interaction at runtime. Use this for background jobs, data pipelines, and other server-to-server setups.
If you're unsure which applies, consider whether your integration needs to act on behalf of a specific Zendesk user. If yes, use the authorization code flow. If no, use the client credentials flow.
Authorization code flow
Use this flow when your integration acts on behalf of a specific user.
How the authorization code flow works
- Your app redirects the user to Zendesk.
- The user signs in and approves access.
- Zendesk redirects the user back to your app with an authorization code.
- Your app exchanges the authorization code for an access token.
- Your app uses the access token to make API requests.
- If Zendesk returns a refresh token, your app uses it to request a new access token when the current one expires.
Step 1: Create or configure an OAuth client
Before you start the authorization flow, create or configure an OAuth client in Zendesk.
This guide assumes you are using a confidential client, one where the client secret can be stored securely on a server. If your integration runs in an environment where the secret can't be kept confidential, such as a browser-based or mobile app, you should use PKCE instead.
When you configure the client, register the redirect URI that your app uses to receive the authorization response. The redirect URI in your request must match the value configured for your OAuth client.
Step 2: Send the user to Zendesk for authorization
Redirect the user to the Zendesk authorization endpoint to start the OAuth flow. Zendesk prompts the user to sign in and approve access for your app.
Include a state parameter in the request and verify it when the user returns to your app. The state parameter helps protect against cross-site request forgery (CSRF) attacks.
Example authorization request
https://{your_subdomain}.zendesk.com/oauth/authorizations/new?response_type=code&client_id=YOUR_CLIENT_ID&redirect_uri=YOUR_REDIRECT_URI&state=RANDOM_STRING
The following query parameters are required for the authorization request. Use your app's values when constructing the URL.
| Parameter | Required | Description |
|---|---|---|
| response_type | Yes | Must be code. |
| client_id | Yes | Your Zendesk OAuth client id. |
| redirect_uri | Yes | The URI Zendesk redirects the user back to after authorization. Must exactly match the value configured for your OAuth client. |
| state | Recommended | A random string used to help prevent CSRF attacks. |
Step 3: Handle the authorization response
After the user grants or denies access, Zendesk redirects them to your redirect URI.
If the user grants access, Zendesk redirects to your app with an authorization code and the same state value you sent:
https://example.com/oauth/callback?code=AUTHORIZATION_CODE&state=xyz789
If the user denies access, Zendesk redirects to your app with an error and the same state value you sent:
https://example.com/oauth/callback?error=access_denied&state=xyz789
After your app receives the redirect, handle the response as follows:
- Verify that the returned
statematches the value you sent. - If the response includes
code, continue to the token exchange step. - If the response includes
error, stop the flow and display an appropriate message to the user.
Step 4: Exchange the authorization code for an access token
To exchange the authorization code for an access token, send a POST request to the token endpoint. The authorization code is short-lived and can be used only once, so exchange it as soon as possible.
POST https://{your_subdomain}.zendesk.com/oauth/tokens
Include the authorization code, client id, client secret, and redirect URI in the request body.
{"grant_type": "authorization_code","code": "AUTHORIZATION_CODE","client_id": "YOUR_CLIENT_ID","client_secret": "YOUR_CLIENT_SECRET","redirect_uri": "YOUR_REDIRECT_URI"}
If the request is successful, Zendesk returns an access token. The response will also include a refresh token.
{"access_token": "ACCESS_TOKEN","token_type": "bearer","expires_in": 3600,"refresh_token": "REFRESH_TOKEN"}
The following table describes the fields returned in the token response.
| Field | Description |
|---|---|
| access_token | The token your app uses to make API requests. |
| token_type | The token type. Zendesk returns bearer. |
| expires_in | The number of seconds before the access token expires. |
| refresh_token | A token your app can use to request a new access token, if returned. |
Store both the access and refresh tokens securely.
Step 5: Use the access token to make API requests
Include the access token in the Authorization header when you make requests to Zendesk APIs.
Example request
GET https://{your_subdomain}.zendesk.com/api/v2/tickets.jsonAuthorization: Bearer ACCESS_TOKEN
Example using curl
curl https://example.zendesk.com/api/v2/tickets.json \-H "Authorization: Bearer ACCESS_TOKEN"
Step 6: Refresh the access token when needed
If Zendesk returns a refresh token, use it to request a new access token when the current one expires.
POST https://{your_subdomain}.zendesk.com/oauth/tokens
{"grant_type": "refresh_token","refresh_token": "REFRESH_TOKEN","client_id": "YOUR_CLIENT_ID","client_secret": "YOUR_CLIENT_SECRET"}
If the request is successful, Zendesk returns a new access token. The response may also include a new refresh token.
{"access_token": "NEW_ACCESS_TOKEN","token_type": "bearer","expires_in": 3600,"refresh_token": "NEW_REFRESH_TOKEN"}
If Zendesk returns a new refresh token, replace the old value with the new one.
Client credentials flow
Use this flow when your integration runs without user interaction, such as background jobs, data pipelines, or other server-to-server setups.
This flow requires a confidential client, one where the client secret can be kept secure on the server. Use client credentials only in trusted, server-side environments where the client secret can be stored securely and never exposed to end users. The access token inherits the permissions of the user associated with the OAuth client, so make sure that user has only the permissions your integration need.
Unlike the authorization code flow, no user redirect is required and Zendesk does not return a refresh token. When the access token expires, your app requests a new one using the same client credentials.
How the client credentials flow works
- Your app sends the client id and client secret directly to Zendesk.
- Zendesk returns an access token.
- Your app uses the access token to make API requests.
- When the access token expires, your app requests a new one.
Step 1: Create or configure an OAuth client
Create or configure an OAuth client in Zendesk as a confidential client. Only use this flow in server-side applications or other trusted environments where the client secret can be protected. No redirect URI is required for this flow.
Step 2: Request an access token
Send a POST request to the token endpoint with your client id, client secret, and the required scope.
POST https://{your_subdomain}.zendesk.com/oauth/tokens
{"grant_type": "client_credentials","client_id": "YOUR_CLIENT_ID","client_secret": "YOUR_CLIENT_SECRET","scope": "YOUR_SCOPE"}
If the request is successful, Zendesk returns an access token. No refresh token is returned for this flow.
{"access_token": "ACCESS_TOKEN","token_type": "bearer","expires_in": 3600,"scope": "read"}
Store the access token securely.
Step 3: Use the access token to make API requests
Include the access token in the Authorization header when you make requests to Zendesk APIs.
Example request
GET https://{your_subdomain}.zendesk.com/api/v2/tickets.jsonAuthorization: Bearer ACCESS_TOKEN
Example using curl
curl https://example.zendesk.com/api/v2/tickets.json \-H "Authorization: Bearer ACCESS_TOKEN"
Step 4: Request a new access token when needed
Because the client credentials flow does not return a refresh token, request a new access token when the current one expires. Use the same request as Step 2.
Security considerations
- Keep the
client_secretconfidential. This is especially critical for the client credentials flow, where the secret is used directly to obtain tokens. - Never expose access tokens or refresh tokens in client-side code, logs, or URLs.
- Validate the
statevalue returned by Zendesk before continuing. (Authorization code flow only.) - Make sure the
redirect_uriexactly matches the value configured for your OAuth client. (Authorization code flow only.) - Store tokens securely and restrict access to them.
- Handle expired, revoked, or already used authorization codes gracefully. (Authorization code flow only.)
- For the client credentials flow, make sure the OAuth client's associated user has only the permissions your integration needs.
- Only use the client credentials flow with confidential clients where the secret can be protected server-side.
Common errors
access_denied
(Authorization code flow only) The user denied access to your app. Prompt the user to retry the authorization flow if they still want to connect their account.
invalid_grant
(Authorization code flow only) The authorization code is invalid, expired, or already used. Start the authorization flow again and exchange the new code immediately.
redirect_uri_mismatch
(Authorization code flow only) The redirect URI in your request does not match the redirect URI configured for your OAuth client. Make sure the request uses the exact redirect URI registered for the client.
invalid_client
The client id or client secret is invalid. Verify that your OAuth client credentials are correct.
Next steps
After you complete the migration:
- Test the full flow in a development environment.
- Confirm that your app handles expired tokens correctly.
- If you used the authorization code flow, verify token refresh behavior and confirm that your app handles denied access correctly.