Migrating from API tokens to OAuth access tokens

Zendesk API tokens will be permanently deactivated on April 30, 2027. If your integration, script, or app currently uses an API token to call a Zendesk API, you need to migrate to OAuth before that date.

This guide shows you how to:

  • Choose the right OAuth flow
  • Get your first OAuth credentials
  • Keep your integration working automatically when tokens expire

Disclaimer: This article is for instructional purposes only. Zendesk doesn't provide support for the example code in this article or for third-party tools such as Postman.

Why API tokens are being retired

API tokens have limitations that make them a poor fit for long-term use:

  • No expiry: If a token is compromised, it stays valid until it’s manually deactivated.
  • No scope restrictions: A token can access any endpoint the authenticating user is allowed to use.
  • No application binding: Any system that has the token and its associated email address can use it.

Migration timeline

DateWhat changes
July 28, 2026New accounts can no longer create new API tokens. For all accounts, any API token that hasn’t been used for 30 days is automatically deactivated. Deactivated tokens are permanently deleted 60 days after deactivation. Existing tokens continue working unless they become inactive.
October 27, 2026All accounts can no longer create new API tokens. Existing tokens continue working unless they become inactive or are later deactivated on April 30, 2027.
April 30, 2027All remaining API tokens are permanently deactivated and can’t be reactivated. All API calls using API tokens will fail.

What this means for your existing tokens

  • Existing API tokens continue working until April 30, 2027, unless they trigger the inactivity rule first.
  • The 30-day inactivity rule applies to all accounts from July 28, 2026, onward.
  • Zendesk will notify you before tokens are deactivated.
  • Deleted tokens can’t be reactivated. If a token is deleted before you migrate, use One-time browser setup to get new OAuth credentials.

Before you start

Before migrating, take a few minutes to:

  • Make an inventory of your API tokens: In Admin Center, go to Apps and integrations > APIs > API tokens and list every active token.
  • Identify what uses each token: Check your integrations, scripts, and third-party apps to find every place a token is used.
  • Check for shared tokens: If multiple integrations use the same token, create a separate OAuth client for each one.
  • Plan your migration: OAuth access and API tokens can run in parallel until April 30, 2027, so you can migrate one integration at a time.

How OAuth works

With API token authentication, your integration sends a static credential (your email address and API token) with every request. If that token is compromised, anyone who has it can use it until it is revoked.

OAuth works differently. Instead of a static credential, your integration goes through a one-time setup to get an access token from Zendesk. It uses that token to make API requests. The token expires after a short time, so even if it’s compromised, the exposure window is limited.

When the access token expires, your integration can automatically exchange the refresh token for a new one without any manual steps or user re-authentication.

The one-time setup varies depending on whether you want the user to grant access to your integration or not::

  • If you want the user to grant access in a browser, use the authorization code flow.
  • If you want your integration to run automatically with no user involved, use the client credentials flow.

Use the section below to choose the flow that applies to you.

Which OAuth flow should I use?

Choose the flow that matches how your integration runs:

FlowClient typeBest for
Authorization codePublicBrowser or mobile apps where a client secret can’t be stored securely. Requires PKCE.
Authorization codeConfidentialServer-side apps or scripts acting on behalf of a specific user.
Client credentialsConfidentialBackground scripts, data pipelines, and server-to-server automation with no user interaction.

Public clients must use the authorization code flow. Confidential clients can use either flow. The grant type is determined by the parameters you send with the request.

If you’re unsure, use the authorization code flow. It works for most integrations. Use client credentials only if your integration can’t perform the one-time browser approval step.

For more information, see Using OAuth access to authenticate API requests.

Migration overview

All integrations follow the same basic migration steps. What changes is how you get your first token and how you handle expiry.

StepAuthorization codeClient credentials
1. Set up an OAuth clientSame for both public and confidential.Confidential clients only.
2. Get your first tokenOpen /oauth/authorizations/new in a browser, approve access, receive a code, then exchange it for tokens through /oauth/tokens. Public clients use PKCE. Confidential clients use client_secret. See One-time browser setup.Exchange directly for tokens through /oauth/tokens.
3. Update API requestsUse the access token in the Authorization header.Use the access token in the Authorization header.
4. Handle token expiryExchange the refresh token for a new token pair automatically.Request a new token using the same client credentials.

Setting up an OAuth client

When creating your OAuth client, choose the client kind for the flow you want:

  • Public clients can only use the authorization code flow.
  • Confidential clients can use either flow.

Create an OAuth client in Admin Center (Apps and integrations > APIs > OAuth clients) if you haven’t already. See Registering your application with Zendesk in Zendesk help. This is required for both flows.

After you create the client, note these values:

  • Identifier: Used as client_id in API requests.
  • Secret: Used as client_secret in API requests. This is shown only once after saving, so store it safely.
  • Redirect URLs: Used as redirect_uri. Not needed for the client credentials flow.

After you have these values, choose your flow:

Authorization code grant

Use the authorization code flow if you want a user to grant access to your integration. After the initial setup, your integration can renew tokens automatically.

This section covers both public and confidential clients. Steps that differ between client types are clearly marked.

How tokens work

This flow uses two tokens:

  • Access token: Used to make API requests to Zendesk. The token can last from 5 minutes up to 2 days, depending on the expires_in value used when it’s issued.
  • Refresh token: Used behind the scenes to get a new access token when the current one expires.

The lifecycle looks as follows from the perspective of your integration:

  1. Get an access token and a refresh token.
  2. Use the access token to make API requests.
  3. Before the access token expires, or after a request fails with 401 Unauthorized, use the refresh token to get a new access token and refresh token.
  4. The old tokens become invalid immediately.
  5. The new tokens replace them.

Default token lifespans

TokenExpires after
Access token30 minutes by default, configurable from 5 minutes to 48 hours
Refresh token30 days by default, configurable from 7 to 90 days

Getting your first token

The examples below use tickets:read as a sample scope. Your integration will likely need different scopes depending on which endpoints it calls. See Scope reference for the full list.

For most migrations, explicitly request the narrowest scope your integration actually needs. This is one of OAuth’s key security advantages over API tokens.

One-time browser setup

If you’re setting up OAuth for the first time, or your API token is no longer working, you’ll need a one-time browser setup. You only do this once. After that, your integration handles token renewal automatically.

The authorization code flow works as follows:

  1. Send the user to Zendesk’s authorization URL.
  2. The user signs in and approves access.
  3. Zendesk redirects back with a short-lived authorization code.
  4. Your integration exchanges that code for an access token and refresh token.

If you use Postman for testing, see Using Postman for testing.

Step 1: Build the authorization URL

Use this template:

https://{subdomain}.zendesk.com/oauth/authorizations/new?response_type=code&client_id={your_client_id}&redirect_uri={your_redirect_uri}&scope=tickets:read

Replace {subdomain}, {your_client_id}, and {your_redirect_uri} with your values.

ParameterDescription
response_type=codeTells Zendesk to return an authorization code
client_idIdentifies your OAuth client
redirect_uriWhere Zendesk sends the user after approval. Must exactly match the redirect URI configured in Admin Center
scopeThe access your token needs. See Scope reference
stateA random string recommended for security. Verify it after Zendesk redirects back

Public clients only — PKCE is required

If you registered your OAuth client as Public, you must generate a code_verifier and derive a code_challenge from it before building the authorization URL.

import osimport hashlibimport base64
code_verifier = base64.urlsafe_b64encode(    os.urandom(32)).rstrip(b"=").decode()
code_challenge = base64.urlsafe_b64encode(    hashlib.sha256(code_verifier.encode()).digest()).rstrip(b"=").decode()

Store the code_verifier. You’ll need it in Step 3. Then add these parameters to the authorization URL:

ParameterValue
code_challengeThe code_challenge value generated above
code_challenge_methodS256

For full examples in other languages, see Using PKCE to make Zendesk OAuth access tokens more secure.

Step 2: Open the URL in a browser and approve access

The following steps walk through the authorization code flow manually, which is useful for understanding how it works and testing your OAuth client. When you’re ready to implement this in your application, see Using OAuth to authenticate Zendesk API requests in a web app and Using OAuth authentication with your application for code examples.

Paste the URL into a browser and press Enter. Zendesk asks the user to sign in, then shows an approval screen.

Click Allow. Zendesk redirects to your redirect_uri and adds a code parameter:

https://example.com/callback?code=abc123xyz

Copy the code value. It expires after 120 seconds.

If you don’t have a real callback server, set your redirect_uri to https://localhost in your OAuth client settings. After approval, the browser will try to load https://localhost and show an error. That’s expected. Copy the code from the address bar.

Step 3: Exchange the code for tokens

Now exchange the authorization code for an access token and refresh token.

All examples below use application/x-www-form-urlencoded, which is the OAuth 2.0 specification default and works with the /oauth/tokens endpoint. The endpoint also accepts application/json if your integration requires it.

Confidential clients

The following parameters identify your OAuth client:

ParameterValue
client_idThe Identifier from your OAuth client in Admin Center
client_secretThe Secret from your OAuth client in Admin Center

curl request

curl -X POST https://{subdomain}.zendesk.com/oauth/tokens \  -H "Content-Type: application/x-www-form-urlencoded" \  --data-urlencode "grant_type=authorization_code" \  --data-urlencode "code={your_authorization_code}" \  --data-urlencode "client_id={your_client_id}" \  --data-urlencode "client_secret={your_client_secret}" \  --data-urlencode "redirect_uri={your_redirect_uri}" \  --data-urlencode "scope=tickets:read"

Python request

import requests
response = requests.post(    f"https://{subdomain}.zendesk.com/oauth/tokens",    data={        "grant_type": "authorization_code",        "code": your_authorization_code,        "client_id": your_client_id,        "client_secret": your_client_secret,        "redirect_uri": your_redirect_uri,        "scope": "tickets:read",    },)response.raise_for_status()
data = response.json()access_token = data["access_token"]refresh_token = data["refresh_token"]

Response

{  "access_token": "gErypPlm4dOVgGRvA1ZzMH5MQ3nLo8bo",  "refresh_token": "31048ba4d7c601302f3173f243da835f",  "token_type": "bearer",  "scope": "tickets:read",  "expires_in": 1800,  "refresh_token_expires_in": 2592000}
Public clients

Omit client_secret and include code_verifier instead.

ParameterValue
code_verifierThe original random string you generated in Step 1

For details, see Using PKCE to make Zendesk OAuth access tokens more secure.

curl request

curl -X POST https://{subdomain}.zendesk.com/oauth/tokens \  -H "Content-Type: application/x-www-form-urlencoded" \  --data-urlencode "grant_type=authorization_code" \  --data-urlencode "code={your_authorization_code}" \  --data-urlencode "client_id={your_client_id}" \  --data-urlencode "code_verifier={your_code_verifier}" \  --data-urlencode "redirect_uri={your_redirect_uri}" \  --data-urlencode "scope=tickets:read"

Python request

import requests
response = requests.post(    f"https://{subdomain}.zendesk.com/oauth/tokens",    data={        "grant_type": "authorization_code",        "code": your_authorization_code,        "client_id": your_client_id,        "code_verifier": your_code_verifier,        "redirect_uri": your_redirect_uri,        "scope": "tickets:read",    },)response.raise_for_status()
data = response.json()access_token = data["access_token"]refresh_token = data["refresh_token"]

Response

{  "access_token": "gErypPlm4dOVgGRvA1ZzMH5MQ3nLo8bo",  "refresh_token": "31048ba4d7c601302f3173f243da835f",  "token_type": "bearer",  "scope": "tickets:read",  "expires_in": 1800,  "refresh_token_expires_in": 2592000}

The refresh_token in this response is the only time Zendesk issues you a refresh token for this authorization. Store it securely. You’ll use it to renew your access token automatically going forward.

The scope field shows the actual scope granted to the token. If you omit scope in the request, Zendesk defaults it to read write.

Store the access_token and refresh_token securely. See Storing tokens and client secrets securely.

This is the only time you need a browser. From here, your integration handles token renewal automatically using the refresh token.

Next: Go to Update your API requests, then set up automatic token refresh. See Implement token refresh.

Update your API requests

After you get an access token, API requests themselves change very little. Instead of sending your email address and API token, send the access token in the Authorization header.

Before (API token)

AUTH = (f"{email}/token", api_token)response = requests.get(url, auth=AUTH)

After (access token)

headers = {"Authorization": f"Bearer {access_token}"}response = requests.get(url, headers=headers)

That’s the only change needed in the request itself.

Implement token refresh

Access tokens expire after 30 minutes by default. Rather than generating a new token manually, your integration should refresh it automatically. See Working with OAuth refresh tokens.

When you need a new access token, send a refresh request using your refresh token.

curl request

curl -X POST https://{subdomain}.zendesk.com/oauth/tokens \  -H "Content-Type: application/x-www-form-urlencoded" \  --data-urlencode "grant_type=refresh_token" \  --data-urlencode "refresh_token={your_refresh_token}" \  --data-urlencode "client_id={your_client_id}" \  --data-urlencode "client_secret={your_client_secret}"

Python request

def refresh_oauth_token(subdomain, client_id, client_secret, refresh_token):    url = f"https://{subdomain}.zendesk.com/oauth/tokens"
    payload = {        "grant_type": "refresh_token",        "refresh_token": refresh_token,        "client_id": client_id,        "client_secret": client_secret,    }
    response = requests.post(url, data=payload)    response.raise_for_status()
    data = response.json()
    return {        "access_token": data["access_token"],        "refresh_token": data["refresh_token"],        "expires_in": data["expires_in"],    }

Response

{  "access_token": "NEW_ACCESS_TOKEN",  "refresh_token": "NEW_REFRESH_TOKEN",  "token_type": "bearer",  "scope": "tickets:read",  "expires_in": 1800,  "refresh_token_expires_in": 2592000}

Save both the new access_token and refresh_token immediately. The old tokens stop working as soon as the refresh succeeds.

Add auto-refresh to your code

A simple way to handle expiry is to retry when Zendesk returns 401 Unauthorized and the error body confirms the token is invalid.

import requests
def refresh_oauth_token(subdomain, client_id, client_secret, refresh_token):    url = f"https://{subdomain}.zendesk.com/oauth/tokens"    payload = {        "grant_type": "refresh_token",        "refresh_token": refresh_token,        "client_id": client_id,        "client_secret": client_secret,    }
    response = requests.post(url, data=payload)    response.raise_for_status()    data = response.json()
    return {        "access_token": data["access_token"],        "refresh_token": data["refresh_token"],        "expires_in": data["expires_in"],    }
def make_request_with_refresh(url, access_token, refresh_token, client_id,                              client_secret, subdomain):    headers = {"Authorization": f"Bearer {access_token}"}    response = requests.get(url, headers=headers)
    if response.status_code == 401:        body = {}        try:            body = response.json()        except ValueError:            pass
        if body.get("error") == "invalid_token":            new_tokens = refresh_oauth_token(                subdomain, client_id, client_secret, refresh_token            )            headers["Authorization"] = f"Bearer {new_tokens['access_token']}"            response = requests.get(url, headers=headers)            return response, new_tokens
    return response, None

When an access token has expired, Zendesk returns a 401 response with this body:

{  "error": "invalid_token",  "error_description": "The access token provided is expired, revoked, malformed or invalid for other reasons."}

For a more complete implementation that refreshes tokens before they expire, see Using OAuth to authenticate Zendesk API requests in a web app.

Client credentials grant

Use the client credentials flow for server-to-server automation and background jobs where no user is available at runtime to approve access. This flow does not issue a refresh token. When the access token expires, your integration simply requests a new one.

The client credentials flow is only suitable for secure server-side environments where your client_secret can be kept private. Never use it in a browser-based app, mobile app, or any environment where the secret could be exposed.

Getting an access token

The examples below use tickets:read as a sample scope. Your integration will likely need different scopes. See Scope reference for the full list.

curl request

curl -X POST https://{subdomain}.zendesk.com/oauth/tokens \  -H "Content-Type: application/x-www-form-urlencoded" \  --data-urlencode "grant_type=client_credentials" \  --data-urlencode "client_id={your_client_id}" \  --data-urlencode "client_secret={your_client_secret}" \  --data-urlencode "scope=tickets:read"

Python request

import requests
response = requests.post(    f"https://{subdomain}.zendesk.com/oauth/tokens",    data={        "grant_type": "client_credentials",        "client_id": your_client_id,        "client_secret": your_client_secret,        "scope": "tickets:read",    },)response.raise_for_status()
data = response.json()access_token = data["access_token"]

Response

{  "access_token": "gErypPlm4dOVgGRvA1ZzMH5MQ3nLo8bo",  "token_type": "bearer",  "scope": "tickets:read",  "expires_in": 1800}

This response does not include a refresh_token. Store the access token securely. See Storing tokens and client secrets securely.

If you use Postman for testing, see Using Postman for testing.

Next, go to Update your API requests, then set up automatic token renewal for when the token expires. See Request a new token when it expires.

Update your API requests

Use the access token in the request header, just as you would with the authorization code flow.

Before (API token)

AUTH = (f"{email}/token", api_token)response = requests.get(url, auth=AUTH)

After (access token)

headers = {"Authorization": f"Bearer {access_token}"}response = requests.get(url, headers=headers)

Request a new token when it expires

Because the client credentials flow doesn’t issue a refresh token, your integration requests a new token by repeating the original token request.

def get_client_credentials_token(subdomain, client_id, client_secret,                                  scope="tickets:read"):    response = requests.post(        f"https://{subdomain}.zendesk.com/oauth/tokens",        data={            "grant_type": "client_credentials",            "client_id": client_id,            "client_secret": client_secret,            "scope": scope,        },    )    response.raise_for_status()
    data = response.json()    return {        "access_token": data["access_token"],        "expires_in": data["expires_in"],    }

You can call this proactively before each request or reactively after a 401 Unauthorized response.

def make_request(url, subdomain, client_id, client_secret,                 scope="tickets:read"):    token_data = get_client_credentials_token(        subdomain, client_id, client_secret, scope    )    headers = {"Authorization": f"Bearer {token_data['access_token']}"}    return requests.get(url, headers=headers)

When an access token has expired, Zendesk returns a 401 response with this body:

{  "error": "invalid_token",  "error_description": "The access token provided is expired, revoked, malformed or invalid for other reasons."}

Scope reference

When you request a token, the scope parameter controls which Zendesk resources the token can access and whether it can read, write, or both.

If you don’t specify a scope, the token defaults to full read and write access across all Zendesk resources. For most migrations, explicitly request the narrowest scope your integration needs. This is one of OAuth’s biggest security advantages over API tokens.

For the full list of supported scopes and resources, see Scopes in the API reference.

Choosing the right scope when migrating

API tokens have no scope restrictions. When you migrate to OAuth, take the opportunity to apply least-privilege access:

  1. Review which Zendesk API endpoints your integration uses.
  2. Check whether each requires read access (GET) or write access (POST, PUT, DELETE).
  3. Request a scope that covers exactly those resources and access types.

For example, if your integration only reads tickets and updates user records, use tickets:read users:read users:write instead of read write.

Setting custom token lifespans

This section is optional. The default lifespans work for most integrations. Only read on if you have a specific reason to change them, such as needing tokens to last longer between refreshes.

By default, access tokens last 30 minutes and refresh tokens last 30 days. If that doesn’t suit your integration, you can set custom values when requesting or refreshing a token.

Add expires_in and/or refresh_token_expires_in to your request in seconds:

response = requests.post(    f"https://{subdomain}.zendesk.com/oauth/tokens",    data={        "grant_type": "refresh_token",        "refresh_token": refresh_token,        "client_id": client_id,        "client_secret": client_secret,        "expires_in": 3600,                  # 1 hour        "refresh_token_expires_in": 7776000, # 90 days    },)
ParameterMinimumMaximum
expires_in (access token)300 seconds (5 minutes)172800 seconds (48 hours)
refresh_token_expires_in604800 seconds (7 days)7776000 seconds (90 days)

Storing tokens and client secrets securely

Where to store credentialsBest for
Environment variablesSimple scripts and local development
AWS Secrets Manager, HashiCorp Vault, or Azure Key VaultProduction integrations running on servers
Encrypted database fieldsApplications managing tokens for multiple accounts

Multi-tenant integrations: If your integration manages tokens for many different Zendesk accounts, use encrypted database fields to store each account’s tokens separately. Use a secrets manager for your OAuth client credentials, which are shared across all accounts.

Using Postman for testing

The following Postman setup is for testing and development only. For production integrations, implement token acquisition and refresh in your application code.

Built-in OAuth 2.0 flow

If you use Postman, you can complete the authorization flow without manually building URLs or copying authorization codes. Postman handles the browser redirect and token exchange for you.

  1. In your Postman request, open the Authorization tab.

  2. Set Auth Type to OAuth 2.0.

  3. Click Configure New Token and fill in the following fields:

    FieldValue
    Grant TypeAuthorization Code
    Callback URLhttps://oauth.pstmn.io/v1/callback
    Authorization URLhttps://{subdomain}.zendesk.com/oauth/authorizations/new
    Access Token URLhttps://{subdomain}.zendesk.com/oauth/tokens
    Client idYour client_id
    Client SecretYour client_secret
    Scopetickets:read or your required scope
    StateAny random string
  4. Click Get New Access Token. Postman opens a browser window to the Zendesk authorization page.

  5. Sign in and click Allow. Postman captures the authorization code and exchanges it automatically.

  6. Postman shows the token response. Click Use Token to apply the access token, then save the access token and refresh token as environment variables. See Storing tokens in Postman.

Note on the callback URL: If you use https://oauth.pstmn.io/v1/callback, add that exact URL as a redirect URI in your OAuth client settings in Admin Center before running this flow. If you’d rather not, use https://localhost instead. Your browser will show an error after approval, but Postman will still capture the token successfully.

Manual token exchange

Authorization code flow

  1. Create a new POST request to:

    https://{subdomain}.zendesk.com/oauth/tokens
  2. Under Headers, add:

    Content-Type: application/x-www-form-urlencoded
  3. Under Body, choose x-www-form-urlencoded, then add:

    KeyValue
    grant_typeauthorization_code
    codeyour_authorization_code
    client_idyour_client_id
    client_secretyour_client_secret
    redirect_uriyour_redirect_uri
    scopetickets:read
  4. Click Send.

Client credentials flow

  1. Create a new POST request to:

    https://{subdomain}.zendesk.com/oauth/tokens
  2. Under Headers, add:

    Content-Type: application/x-www-form-urlencoded
  3. Under Body, select x-www-form-urlencoded, then add:

    KeyValue
    grant_typeclient_credentials
    client_idyour_client_id
    client_secretyour_client_secret
    scopetickets:read
  4. Click Send.

Save the access_token and refresh_token from the response as Postman environment variables. See Storing tokens in Postman.

Pre-request script for automatic token refresh

If you use a Postman collection, you can add a pre-request script that refreshes the access token automatically before it expires.

In your collection, open the Scripts tab and add this code to the Pre-request section:

const subdomain = pm.environment.get("subdomain")const clientId = pm.environment.get("client_id")const clientSecret = pm.environment.get("client_secret")const refreshToken = pm.environment.get("refresh_token")const expiresAt = parseInt(pm.environment.get("token_expires_at") || "0")
// Refresh if the token expires within the next 60 secondsif (Date.now() >= expiresAt - 60000) {  pm.sendRequest(    {      url: `https://${subdomain}.zendesk.com/oauth/tokens`,      method: "POST",      header: { "Content-Type": "application/x-www-form-urlencoded" },      body: {        mode: "urlencoded",        urlencoded: [          { key: "grant_type", value: "refresh_token" },          { key: "refresh_token", value: refreshToken },          { key: "client_id", value: clientId },          { key: "client_secret", value: clientSecret }        ]      }    },    (err, response) => {      if (err || response.code >= 300) {        console.error("Token refresh failed:", err || response.json())        return      }
      const data = response.json()      pm.environment.set("access_token", data.access_token)      pm.environment.set("refresh_token", data.refresh_token)      pm.environment.set(        "token_expires_at",        String(Date.now() + data.expires_in * 1000)      )    }  )}

This script reads your tokens from Postman environment variables and stores the new ones after each refresh. Make sure your requests use {{access_token}} in the Authorization header as a Bearer token.

Storing tokens in Postman

Store your tokens as Postman environment variables so they’re available across your collection and can be updated automatically by the pre-request script.

Required environment variables

VariableValue
subdomainYour Zendesk subdomain, for example mycompany
client_idYour OAuth client id
client_secretYour OAuth client secret
access_tokenThe access token from your token response
refresh_tokenThe refresh token from your token response, authorization code flow only
token_expires_atSet this to Date.now() + (expires_in * 1000)

To set the initial values:

  1. In the bottom-left sidebar, expand ENVIRONMENTS, then click + to create a new environment and give it a name. You can also use the No environment dropdown in the top-right corner and select Add new environment.
  2. Add each variable from the table above as a new row, entering the name in the Variable column and its value in the Initial value column.
  3. For client_secret, access_token, and refresh_token, change the Type column from default to secret so Postman masks the values in the UI.
  4. Click Save.
  5. Select your new environment from the No environment dropdown in the top-right corner to make it active. Your requests can now reference variables like {{access_token}}.

In your requests, set Authorization to Bearer Token and use {{access_token}} as the token value. The pre-request script will keep it current automatically.

Frequently asked questions

What about webhooks that use API tokens?

Webhook authentication using API tokens will stop working on April 30, 2027. To migrate your webhooks before that date, update the webhook authentication configuration in Admin Center to use a Bearer token instead. See Creating or cloning a webhook for instructions on updating webhook authentication settings.

If your webhook triggers an integration that itself uses an API token, that integration also needs to migrate separately. See the migration steps in this guide for details.

Can I set up OAuth through Admin Center without writing code?

Admin Center lets you create and manage OAuth clients, but generating tokens still requires code or a tool like Postman.

If you want to use Postman, see Using Postman for testing. It handles the full authorization flow for you.

Can I run OAuth and API tokens in parallel during my migration?

Yes. OAuth access tokens and API tokens both work until April 30, 2027. You can migrate integrations one at a time, testing each one before switching over. There is no need to migrate everything at once.

What if my refresh token expires or my API token has already stopped working?

Use the one-time browser setup. It only takes a few minutes and you only need to do it once. After that, as long as you keep renewing your refresh token before it expires, you won’t need to touch the browser again.

I don’t have a real callback server. Is that okay?

Yes. If you’re doing one-time setup with Postman, use Using Postman for testing. If you’re completing the process manually, set your redirect_uri to https://localhost in your OAuth client settings. After approval, your browser will show an error because nothing is listening on localhost. That’s expected. Copy the code from the address bar and use it in Step 3.

Can I make my refresh token last longer?

Yes. Set refresh_token_expires_in to 7776000 seconds (90 days) when requesting a token. Make sure your integration reliably saves the new refresh token each time it refreshes, or you’ll need to repeat the setup if a refresh token is lost.

I have several integrations using the same API token. What should I do?

Create a separate OAuth client for each integration instead of sharing one. That way, if one integration has a problem, you can revoke its access without affecting the others.

I use a third-party app from the Zendesk Marketplace. Do I need to update it?

Per developer policy, third-party apps, integrations, and bots — whether listed on the Zendesk Marketplace or not — must not use a customer’s API credentials, including API tokens or the customer’s own OAuth client, to authenticate API calls.

Apps that migrate to OAuth will continue working. Apps that don’t migrate will stop working when API tokens are deactivated.

What happens if I don’t migrate before the deadline?

On April 30, 2027, all API tokens are deactivated and can’t be reactivated. If that happens, use the one-time browser setup to set up OAuth from scratch.

My integration returns 403 Forbidden even though the token was created successfully. What’s wrong?

This usually means the token’s scope doesn’t cover the endpoint you’re calling. For example, a token with "scope": "tickets:read" can’t make POST, PUT, or DELETE requests.

Check the scope field in the token response and request a new token with a scope that includes the access type you need. See Scope reference.

What scope should I use for my integration?

Start by listing every Zendesk API endpoint your integration calls, then check whether each one is a GET (needs read) or a POST, PUT, or DELETE (needs write). Set a scope that covers exactly those resources and access types. For example, if you only read tickets and update users, use tickets:read users:read users:write instead of read write. See Scope reference for the full list.