I’m experiencing an issue with the client credentials OAuth flow for a custom app I created in the Dev Dashboard in my Partner Organization. The app installs successfully to a production merchant account (not in my Partner Organization) but the token request fails.
Setup Details:
- App created in: Dev Dashboard (not legacy custom app) under my Partner account
- App Client ID: ed91f893401483feac1f7814209cb7fa
- Target Store: redacted
- Distribution: Custom distribution (single store, “Allow multi-store install for Plus organization” unchecked)
- App Version: Released with read_orders scope
Steps I followed:
- Created a new app in Dev Dashboard in my Partner Organization
- Created an app version with read_orders access scope
- Released the app version
- Set up custom distribution for a single store
- Generated install link and successfully installed the app on the production merchant store (which is not in my Partner Organization)
- App appears as installed in the merchant’s admin (Settings → Apps)
The Problem:
When I make a token request using the client credentials grant, I receive this error:
POST https://redacted.myshopify.com/admin/oauth/access_token
Content-Type: application/x-www-form-urlencoded
grant_type=client_credentials
client_id=ed91f893401483feac1f7814209cb7fa
client_secret=redacted
Response: 400 Bad Request
Error: “shop_not_permitted”
Message: “Client credentials cannot be performed on this shop.”
What I’ve tried:
- Created a completely new app with fresh credentials
- Tried with and without the “Allow multi-store install for Plus organization” option
- Verified the request format matches the documentation exactly
- Confirmed the app is installed on the store
Questions:
- Is there a store-level setting that needs to be enabled for client credentials to work?
- Is there a known issue or additional step required for custom distribution apps from Partner Organization to use client credentials on merchant stores?
This is blocking our migration from legacy custom app tokens (which are being deprecated January 2026) to the new authentication flow.
Note that everything works as expected when I install my app into a dev store in my partner account. In this case, I am able to obtain access tokens using the client credentials oauth flow as expected.
Thank you for your help!
I just read this topic: Installing custom app - #8 by Tiwa_Ojo . If I understand correctly, client credentials authentication only works in these scenarios:
- A Partner Organization app authenticating to a dev store in that same Partner Organization.
- An merchant owned app authenticating to a production store owned by that same merchant.
And, specifically, this scenario is not supported for client credentials authentication:
- A Partner Organization app authenticating to a production store not owned by that partner.
Is this true? If so, what are the recommended authentication options? My application has no Shopify UI at all. All I want to do is just download some order data from a server that we manage that is written in java using the Shopify API.
Thanks again!
Hey @Symphiq_Integrations,
It looks like your understanding of the client credentials is correct. To my understanding, a Partner Org app authenticating to a prod store via client credentials can only be done if the user is a collaborator with a Developer role. See this comment for more info.
My approach uses the authentication grant flow(I used Typescript). Manual implementation Shopify.dev docs
- Setup your app with custom distribution(Looks like you have done this). I don’t think it’ll make a difference, but I had the
Allow multi-store install for Plus organization option selected
- Paste the generated URL in the browser. It’ll redirect you to
/ in your non embedded app.
- Verify the HMAC signature(recommended for security purposes)
- Once verified, redirect to your auth flow endpoint(e.g.
/auth).
- Use the shopify-api
auth.begin function to redirect the merchant to shopify. e.g.const callbackResponse = await api.auth.begin({
isOnline: false,
rawRequest: request,
// This should be the same as the "Redirect URL" in your app setup in the Partner Dashboard
callbackPath: '/auth/callback',
shop: shop,
});
return callbackResponse;
callbackResponse from the previous step will redirect to /auth/callback in my app. This is where we will generate the OfflineSession Token. e.g.const callback = await api.auth.callback<Headers>({ rawRequest: request });
- The offline session should resemble the following:
{
"session": {
"id": "offline_<my_store>.myshopify.com",
"shop": "<my_store>.myshopify.com",
"scope": "read_orders,read_inventory,read_products,...",
"state": "random generated state here",
"isOnline": false,
"accessToken": "shpca_<token>
}
}
- You will also need to setup a means of managing your tokens as they are not ephemeral. E.g. a database or datastore. Resinstalling your app will provide you the same token.
- Your mileage may vary due to the language and tools available to you.
This approach while not ideal worked for me. I would recommend you first verify your role in the prod store in other to use the client credentials, as that seems the simplest approach.
Hope this helps
.
Thanks. So, the shopify docs (Using the client credentials grant) say this:
Client credentials is only available for apps developed by your own organization and installed in stores that you own. Public or custom apps must use token exchange or authorization code.
This statement is confusing to me. The first sentence seems to confirm the idea that client credentials cannot be used by Partner custom apps authenticating to production merchant stores because these are not apps developed by the merchant organization.
However, my reading of the second sentence would seem to indicate that client credentials can’t be used for any app. Aren’t all apps either public or custom? If it’s true that all apps are either public or custom, then the second sentence would seem to mean that no app (even merchant owned custom apps) can use client credentials–they must use the authorization code flow instead. In other words, the second sentence seems to contradict the first sentence. I feel like the wording of the docs here needs to be fixed to make things clearer.
Let’s assume that client credentials cannot be used in the situation I’m trying to use them in with a Partner custom app authenticating to a production merchant store. I’d like to know why client credentials is not supported in this case but is supported for a merchant developed app authenticating to that merchant’s store. What’s the fundamental difference between these 2 cases that makes client credentials only possible for the merchant developed custom apps? After all, when a merchant installs an app, they are presented with a consent screen that clearly states the name of the app, the developer of the app, the scopes that are being requested, etc. The user can choose to not install the app at that point. Furthermore, the client id and client secret are unique for that custom app (even for Partner developed apps, each custom app the partner creates has a unique client id and client secret and can only be installed by one merchant). Therefore, it would seem that using the client id and client secret in the client credentials flow would be sufficient for authentication with both Partner and merchant developed apps. Why is it necessary for Partner custom apps to force the user through the authorization code flow which forces the user through a second consent screen that would seem to be essentially identical to the app install consent screen? I’m obviously talking about long-lived (offline) API access here (not short-lived online API access).
Since the docs are so unclear, I’d still like to get a definitive answer from someone from Shopify. Can Partner custom apps authenticate to a production merchant store using client credentials? That’s the critical answer that I’d like to receive quickly. As a follow on, if it turns out that Partner custom apps are not allowed to authenticate to a production merchant store using client credentials, then I’d like to know why not (at least for the long lived API access case).
Thanks!