Skip to main content

Exchange a session token for an access token

Tip

You can use Shopify CLI to generate a starter app with boilerplate code that handles authentication and authorization. The starter app includes code for an embedded app that uses session tokens and token exchange.


  • You've created an embedded app that doesn't use a Shopify app template.
  • You have your app's client credentials.
  • You're familiar with session tokens in Shopify.

Anchor to Step 1: Ensure you have a valid session tokenStep 1: Ensure you have a valid session token

Your app's frontend must acquire a session token from App Bridge. In the current version of App Bridge, this is handled automatically using authenticatedFetch. You must include the token in the AUTHORIZATION header for all requests to the app's backend.

Your app's backend is responsible for authenticating all incoming requests using the session token.


Anchor to Step 2: Get an access tokenStep 2: Get an access token

If your app doesn't have a valid access token, then it can exchange its session token for an access token using token exchange.

POST https://{shop}.myshopify.com/admin/oauth/access_token
Parameters for the token exchange API and their descriptions
ParameterDescription
client_idrequiredThe API key for the app.
client_secretrequiredThe client secret for the app.
grant_typerequiredThe value urn:ietf:params:oauth:grant-type:token-exchange indicates that token exchange is to be performed.
subject_tokenrequiredAn ID token that represents the identity and active browser session of a merchant using the app.
subject_token_typerequiredThe value urn:ietf:params:oauth:token-type:id_token indicates that the subject token type is an ID token.
requested_token_type
  • urn:shopify:params:oauth:token-type:offline-access-token (default) for requesting offline access tokens
  • urn:shopify:params:oauth:token-type:online-access-token for requesting online access tokens
expiringOnly applicable if requested_token_type is set to urn:shopify:params:oauth:token-type:offline-access-token. Learn more about expiring vs non-expiring offline tokens
  • 0 (default) for requesting a non-expiring offline token
  • 1 for requesting an expiring offline token

The following shows an example of a token exchange request and response for both an online and an offline access token.

Request

curl -X POST \
https://{shop}.myshopify.com/admin/oauth/access_token \
-H 'Content-Type: application/x-www-form-urlencoded' \
-H 'Accept: application/json' \
-d 'client_id={client_id}' \
-d 'client_secret={client_secret}' \
-d 'grant_type=urn:ietf:params:oauth:grant-type:token-exchange' \
-d 'subject_token={session_token}' \
-d 'subject_token_type=urn:ietf:params:oauth:token-type:id_token' \
-d 'requested_token_type=urn:shopify:params:oauth:token-type:online-access-token'
curl -X POST \
https://{shop}.myshopify.com/admin/oauth/access_token \
-H 'Content-Type: application/x-www-form-urlencoded' \
-H 'Accept: application/json' \
-d 'client_id={client_id}' \
-d 'client_secret={client_secret}' \
-d 'grant_type=urn:ietf:params:oauth:grant-type:token-exchange' \
-d 'subject_token={session_token}' \
-d 'subject_token_type=urn:ietf:params:oauth:token-type:id_token' \
-d 'requested_token_type=urn:shopify:params:oauth:token-type:offline-access-token' \
-d 'expiring=1'
curl -X POST \
https://{shop}.myshopify.com/admin/oauth/access_token \
-H 'Content-Type: application/x-www-form-urlencoded' \
-H 'Accept: application/json' \
-d 'client_id={client_id}' \
-d 'client_secret={client_secret}' \
-d 'grant_type=urn:ietf:params:oauth:grant-type:token-exchange' \
-d 'subject_token={session_token}' \
-d 'subject_token_type=urn:ietf:params:oauth:token-type:id_token' \
-d 'requested_token_type=urn:shopify:params:oauth:token-type:offline-access-token'

Response

{
"access_token": "f85632530bf277ec9ac6f649fc327f17",
"scope": "write_orders,read_customers",
"expires_in": 86399,
"associated_user_scope": "write_orders",
"associated_user": {
"id": 902541635,
"first_name": "John",
"last_name": "Smith",
"email": "john@example.com",
"email_verified": true,
"account_owner": true,
"locale": "en",
"collaborator": false
}
}
{
"access_token": "f85632530bf277ec9ac6f649fc327f17",
"scope": "write_orders,read_customers",
"expires_in": 3600,
"refresh_token": "shprt_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"refresh_token_expires_in": 7776000
}
{
"access_token": "f85632530bf277ec9ac6f649fc327f17",
"scope": "write_orders,read_customers"
}

If your session token is expired or otherwise invalid, then the token exchange request fails with an HTTP status code of 400 Bad Request.

Anchor to Online access token response valuesOnline access token response values

ValueDescription
access_tokenAn API access token that can be used to access the shop’s data. Your app should store the token somewhere to make authenticated requests for a shop’s data. An online access token can be used for as long as the app is installed or for the next 24 hours, whichever comes first. After 24 hours, you need to refresh the access token.
scopeThe list of access scopes that were granted to your app and are associated with the access token.
expires_inThe number of seconds until the access token expires.
associated_user_scopeThe list of access scopes that were granted to the app and are available for this access token, given the user’s permissions.
associated_userInformation about the user who completed the authorization. The email field in this response appears regardless of the email verification status. If you’re using emails as an identification source, then make sure that the email_verified field is also true. You can use the id field to uniquely identify a single user.

Anchor to Offline access token response valuesOffline access token response values

Response values and their descriptions
ValueDescription
access_tokenAn API access token that can be used to access the shop's data. Your app should store the token somewhere to make authenticated requests for a shop's data. Learn more about offline access tokens and online access tokens.
scopeThe list of access scopes that were granted to your app and are associated with the access token.
expires_in*The number of seconds until the access token expires.
refresh_token*The refresh token that can be used to obtain a new access token when the current one expires.
refresh_token_expires_in*The number of seconds until the refresh token expires.

* Only included when expiring=1 is specified in the request.


Anchor to Step 3: Make authenticated requestsStep 3: Make authenticated requests

After your app has obtained an API access token, it can make authenticated requests to the GraphQL Admin API and fulfill incoming requests from the app frontend.

The following example shows how to retrieve a list of products using the GraphQL Admin API.

Terminal

curl -X POST \
https://{shop}.myshopify.com/admin/api/2025-10/graphql.json \
-H 'Content-Type: application/json' \
-H 'X-Shopify-Access-Token: {access_token}' \
-d '{
"query": "{
products(first: 5) {
edges {
node {
id
handle
}
}
pageInfo {
hasNextPage
}
}
}"
}'

Was this page helpful?