Authorization code flow (3-legged OAuth)

Authenticate your app to act on behalf of other Indeed user accounts and the users' associated employers.



By using this API and its documentation and building an integration, you agree to the Additional API Terms and Guidelines.

Overview of the authorization code flow (3-legged OAuth)

Use this flow to authorize your app to act on behalf of other Indeed user accounts and those user accounts' associated employers. To use this flow, complete these steps:



Register your app to get a client ID and secret.

During registration, you select the authorization code grant type, which indicates that you receive an authorization code after the user authorizes your app through the OAuth consent screen.

For each user


Get an authorization code.

To get this code, call the oauth/v2/authorize endpoint with your client ID. In response to this call, Indeed shows the OAuth consent screen to the user, the user authorizes your app, then Indeed passes an authorization code to the redirect URL that you specified during app registration.


Get an access token.

To make API calls, you must include an access token in each call. The token identifies an Indeed account and proves that your app is authorized to make calls on behalf of this account.

To get an access token, you exchange your client ID and secret and either an authorization code or, if the access token expires, a refresh token, for one.

For each API call


Pass the access token each time that you call an Indeed API.

Step 1. Get a client ID and secret

  1. Go to Manage app credentials, sign in to your Indeed account, then click Register new application.

  2. Enter your app name and description, select the OAuth 2.0 credential type, then click Save and continue.

    📘 Note: Add these suffixes to these versions of your app:

    • Test: -dev
    • Production: -prod

    For example, AceRecruitersApp-dev and AceRecruitersApp-prod.

  1. Indicate whether the app is a public client, select the authorization code grant type, add one or more redirect URLs, then click Save and continue.

  2. Indicate whether you want your app to be reviewed, then click Save and continue.

  3. Optionally, provide your company name, homepage, support email address and link to your public privacy policy, then click Save and continue.

  4. Preview your app information, then click Complete registration.

    The page shows information about your app, including the client ID, and if your app is not a public client, your client secret.

    📘 Important:

    Store your client ID and secret securely. Other than including these credentials in API calls, never share your credentials or store them in a public Git repository.

Step 2. Get an authorization code

  1. To add an Indeed authorization link to your app, call the endpoint with request body parameters:

    The call shows the OAuth consent screen to the user.

    If the user clicks Allow on the OAuth consent screen, Indeed redirects them to your app at the redirect_uri, with the code and state response fields appended.

  2. Capture the code parameter from the redirect_uri page:


Step 3. Get an access token

  1. Make a POST request to the endpoint:

    curl -X POST -H 'Content-Type: application/x-www-form-urlencoded' -H 'Accept: application/json' \
      -d 'code=rXZSMNyYQHQ' \
      -d 'client_id=6nwwcdklwgktryjw2j5fxh5t2fyneule7zg7mvw3pf9jbx3wmewzlxkdz1jxvs6b' \
      -d 'client_secret=02KKpg6yLXw2v3FKf5lqyFGtMQCvPBNbJIw89SoSd9fts1LAdlvwUQQ6dwhAhEXv' \
      -d 'redirect_uri=http://localhost:3000/oauth/callback' \
      -d 'grant_type=authorization_code' \

    In the request, include the:

    • Content-Type and Accept request headers.
    • code, client_id, client_secret, redirect_uri, and the grant_type=authorization_code parameters.

    📘 Note:

    When you get an access token, you can use the basic authentication scheme instead of passing a client ID and secret in the /oauth/v2/tokens request body. To use this scheme, you encode and pass these credentials in an Authorization Basic header.

    The JSON response includes the response fields for oauth/v2/tokens:

      "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXV[...]",
      "id_token": "eyJraWQiOiJlMzEzZTc4My1lM2YwLTQ3ZWMtY[...]",
      "refresh_token": "rXZSMNyYQHQ",
      "expires_in": 3600,
      "token_type": "Bearer",
      "scope": "email offline_access",
      "consented_scope": "email offline_access"

    The JSON response for an error includes these fields:

      "error": "invalid_request",
      "error_description": "Invalid authentication request."
  2. Securely store the access token and refresh token.

Step 4. Call an Indeed API

Prefix the string Bearer to the access token value, and pass the concatenated string in an Authorization header with each API call.

Additional tasks

Refresh an access token

Use a refresh token to refresh your access token.

Indeed returns a refresh token with the access token when your app requests and is granted the offline_access scope.

Access tokens are valid for one hour (3600 seconds). Refresh tokens are valid for 60 days from when they are issued. With each refresh, the refresh token's expiration date is extended to 60 days from the most recent refresh.

To refresh your access token:

  1. Make a POST request to the endpoint.

    In the call, include the Content-Type and Accept request headers and the refresh_token, client_id, client_secret, and the grant_type=refresh_token request body parameters.

    curl -X POST -H 'Content-Type: application/x-www-form-urlencoded' -H 'Accept: application/json' \
      -d 'refresh_token=8qE_nKXUng4' \
      -d 'client_id=6nwwcdklwgktryjw2j5fxh5t2fyneule7zg7mvw3pf9jbx3wmewzlxkdz1jxvs6b' \
      -d 'client_secret=02KKpg6yLXw2v3FKf5lqyFGtMQCvPBNbJIw89SoSd9fts1LAdlvwUQQ6dwhAhEXv' \
      -d 'grant_type=refresh_token' \

    The JSON response includes these fields:

      "access_token": "eyJhbGciOiJIUzI1NiIslKxwRJSMeKKeJf36POk6yJV_adQs[...]",
      "id_token": "eyJraWQiOiJlMzEzZTc4My1lM2YwLTQ3ZWMtYmJlMi1jMWRlNGYy[...]",
      "refresh_token": "rXZSMNyYQHQ",
      "convid": "1c1a1s8540kkt89p",
      "scope": "email offline_access",
      "token_type": "Bearer",
      "expires_in": 3600

    If an error occurs, the JSON response includes these fields:

      "error": "invalid_request",
      "error_description": "Invalid authentication request."
  2. Pass your access token in each API request.



    The access token is valid for only one hour. Refresh this token after that.

Associate user account with employer account

You can associate an Indeed user account with one or more employer accounts. When you call an Indeed API, the API might require you to represent a particular employer. To associate an employer with an access token, use either the Indeed employer selection screen or your own employer selection screen.



An access token can represent only one employer at a time. To switch employers, generate an access token. For example, use a refresh token to generate an access token.

Show the Indeed employer selection screen

To request the creation of an authorization link to display the Indeed employer selection screen, use the prompt=select_employer parameter.

When you specify this parameter, your OAuth app must also request scope=employer_access. Otherwise, the Indeed employer selection screen does not appear.

For example, this link includes both the prompt=select_employer parameter and a request for the employer_access scope:

This authorization link triggers these screens to appear:

Screen Description
Authentication Appears when the user is logged out of Indeed.
OAuth consent Enables a user to grant consent for any scopes that the OAuth app requests, such as the employer_access scope.
Indeed employer selection Enables a user to select an employer from a list of employers associated with the user account.

The Indeed employer selection screen lists the employers associated with the user account:

  • US Robotics and Mechanical Men
  • Umbrella Corporation

If the user selects one of the employers and submits the page, a redirect URI parameter transmits the employer ID to your OAuth app:

The URL includes an employer parameter that represents an employer ID. Use this ID when your request an access token to associate the access token with an employer, like this:

curl -X POST -H 'Content-Type: application/x-www-form-urlencoded' -H 'Accept: application/json' \
 -d 'code=e_IEr5UlBys' \
 -d 'client_id=bf7622efee705862320df0f5c7690b22f60ba24f236aaf4adf5b7a36fa0adcf1' \
 -d 'client_secret=02KKpg6yLXw2v3FKf5lqyFGtMQCvPBNbJIw89SoSd9fts1LAdlvwUQQ6dwhAhEXv' \
 -d 'redirect_uri=' \
 -d 'grant_type=authorization_code' \
 -d 'employer=6d2f02224e30d401810b1726eb246d8d' \

When you use a refresh token to get an access token, you can also use the employer parameter. If the user does not select an employer or the user account has no associated employers, then the redirect URI does not include an employer parameter.



Don't assume that the redirect URI includes an employer parameter even when you use the prompt=select_employer parameter.

Show a custom employer selection screen

Indeed recommends that you use the standard Indeed employer selection screen to enable users to select a particular employer. To customize the employer selection screen, then you also have the option of getting employers and creating an employer selection screen.

Follow these steps:

  1. Get credentials. Get an access token to trigger the OAuth consent screen, and get an access token with the employer_access and offline_access scopes.

    The query returns an access token, ID token, and refresh token.

  2. To list employers for the current user, decode the ID token. See Get user info.

  3. Create a custom employer selection screen. You can build a user interface that enables the user to select an employer.

  4. Get an access token that represents an employer.

    To get an access token that represents the employer, pass the employer ID in the employer parameter of the oauth/v2/tokens endpoint.

    curl -X POST -H 'Content-Type: application/x-www-form-urlencoded' -H 'Accept: application/json' \
      -d 'refresh_token=4U0xku8M9u0' \
      -d 'client_id=b0c3b1092225d3e99f85d7aa3fe1e6001f9a0bb798717cbc2008e58fbda3ef16' \
      -d 'client_secret=2YFoyZOWmr83njlsIuyCL9QQq5jZkRCR4UtmHGp22MRzjIhe5RbynnAGmuYLFbYx' \
      -d 'grant_type=refresh_token' \
      -d 'employer=13ef9940a7c1f0500a7e411e74178c4e' \



    In the request:

    • The grant_type parameter has the refresh_token value.
    • The refresh_token parameter contains the refresh token.
    • The employer parameter has the Dharma Initiative employer ID value.

    When you use a refresh token to get an access token, an OAuth consent screen is not necessary.

    Use this access token for API calls on behalf of the consenting user and only for this employer.

Get user info

When using authorization code flow (3-legged OAuth), to get information about the user that registered the app, you can use an ID token or the userinfo endpoint.

ID token

The OpenID Connect specification describes an ID token. Indeed always returns an ID token with the access token. The ID token contains information about the current user. See v2/api/userinfo response fields.

To include the email and email_verified fields in the ID token, an app must request and get the email scope.

When you use the authorization code flow (3-legged OAuth) to request an access token, you get an ID token automatically.

A resource server, which is an Indeed API, reads an access token, while a third-party app reads an ID token.

For example:

curl -X POST -H 'Content-Type: application/x-www-form-urlencoded' -H 'Accept: application/json' \
  -d 'code=qCwk_cdC4Ms' \
  -d 'client_id=a0c3b1092225d3e99f85d7aa3fe1e6001f9a0bb798717cbc2008e58fbda3ef16' \
  -d 'client_secret=1YFoyZOWmr83njlsIuyCL9QQq5jZkRCR4UtmHGp22MRzjIhe5RbynnAGmuYLFbYx' \
  -d 'redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Foauth%2Fcallback' \
  -d 'grant_type=authorization_code' \

See HTTP request headers.

The response is:

  "access_token": "eyJraWQiOiINiYzIwMDh9hrYboL9BMnU_Pfbv41Nqj3nUuMbprUtRgQ",
  "convid": "1erq23om7t5bu800",
  "scope": "email employer_access",
  "id_token": "eyJraWQiMWQ4NTciLCJ0eXAiOiJKV1LmNvbSzNjMwfQ.Jrb1fX-aPtkV9nQK9U1LFP0V61eEApo3pFFPaGjT9zA",
  "token_type": "Bearer",
  "expires_in": 3600

To decode the ID token, copy it into the form at

When the user grants your app both the email and employer_access scopes, the following response appears:

  "aud": "a0c3b1092225d3e99f85d7aa3fe1e6001f9a0bb798717cbc2008e58fbda3ef16",
  "sub": "d2d1962c0664d970",
  "employers": [{
      "id": "13ef9940a7c1f0500a7e411e74178c4e",
      "name": "Dharma Initiative"
      "id": "6d2f02224e30d401810b1726eb246d8d",
      "name": "Umbrella Corporation"
  "email_verified": true,
  "iss": "",
  "exp": 1610417960,
  "iat": 1610414360,
  "email": "[email protected]"

If a user does not grant these scopes, only the sub field is returned.

The ID token is a Base64-encoded JSON Web Token (JWT). Because the JSON Web Token is signed, use the public key to verify that the token is valid:

To verify that the ID token is valid, copy the key into the form at

The userinfo endpoint

To call the userinfo endpoint, you must get the access token by using the authorization code grant type.

This curl command shows how to call the userinfo endpoint, with a shortened token for readability:

curl -H 'Authorization:Bearer eyJraWQiOiI1OTdjYTgxNC03YTA2LTR0zh0Elm9A' \

The information that userinfo endpoint returns depends on the scopes that a user grants to your OAuth app. If the user does not grant your app any scopes, you receive the sub field, which represents a user's account ID:

  "sub": "d2d1962c0664d970"

If the user grants your app the email scope, you receive the email and email_verified fields:

  "sub": "a95064930d19bbc7",
  "email": "swalther[email protected]",
  "email_verified": true

Finally, if the user grants your app the employer_access scope, you receive a list of employers, or advertisers, for the user account:

  "sub": "bc8c847009a955c9",
  "email": "[email protected]",
  "email_verified": true,
  "employers": [{
      "id": "084a39249af95beedfb90cc5d2b8833c",
      "name": "Dharma Initiative"
      "id": "865e08b649774436ee1f410b611fad7c",
      "name": "Umbrella Corporation"
      "id": "4bc393648e880bc94dd6cef8efbc8486",
      "name": "US Robotics and Mechanical Men"

You can call the userinfo endpoint to request account information for the current user. This endpoint returns exactly the same information as the ID token. Like the ID token, you must request and receive the email scope to get the email and email_verified fields.

A use case for this resource is creation of Log in with Indeed buttons. See Log in with Indeed.

The URL path is:


This endpoint does not have any request body parameters.

In the request, pass the access token in an Authorization header with the Bearer authentication scheme:

Authorization: Bearer <token>

where <token> is the your access token.

For example:

GET /v2/api/userinfo HTTP/1.1
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

A successful request returns the HTTP status code 200 and a response payload:

HTTP/1.1 200 OK
Content-Type: application/json

  "sub": "248289761001",
  "email": "min[email protected]",
  "email_verified": true

See v2/api/userinfo response fields.

Get and manage scopes

When you request an access token, you request scopes that define your app's permissions.

Indeed authorization scopes

For a list of Indeed-supported scopes, see scopes.

When using the authorization code flow (3-legged OAuth), the authorizing user grants scopes to your app. They can grant no scopes, specific scopes, or all scopes to your app.

To determine which scopes your app has, read the scope field in a tokens response:

  "access_token": "eyJraWQiOiI1OTdjYTgxNC03YTA2LTRkZTMtO",
  "refresh_token": "ZenoK4KeQsg",
  "id_token": "eyJraWQiOiI1OTdjYTgxNC03YTA2LTE69SSJkjQQ",
  "scope": "offline_access employer_access email",
  "consented_scope": "offline_access employer_access email",
  "convid": "1er835qvtu54n800",
  "token_type": "Bearer",
  "expires_in": 3600

Use incremental authorization

Incremental authorization enables apps to request only the scopes that they need. Incremental authorization requires the offline_access, which maintains the permissions that your app has. For example, if your app requests the email scope, your app must also request the offline_access scope.

After a user grants your app a scope, they do not need to grant the scope again.

For example, if the user requests functionality from your app that requires the employer_access scope, the OAuth consent screen prompts the user to grant only the employer_access scope and not the email and offline_access scopes.

The Current permissions tab on the OAuth consent screen lists the app's scopes.

Revoke OAuth scopes

When using the authorization code flow (3-legged OAuth), a user can use the Authorized Applications page to revoke scopes from an app.



You cannot revoke scopes individually. You can revoke all scopes and, when using the app, grant scopes again.

See also

  • OAuth glossary for common OAuth term descriptions
  • Error Response in the RFC6749: IETF OAuth 2.0 Authorization Framework for a description of the error response
  • userinfo endpoint for an example of an API call
  • OAuth reference for request headers, request body parameters, response fields, Indeed-supported scopes, and the basic authentication scheme
  • Log in with Indeed button for Indeed-provided images to create Log In with Indeed buttons
  • Upgrade OAuth to upgrade your OAuth integration to the Indeed OAuth endpoint v2