I completely get the frustration - you do need a hosted endpoint to handle the OAuth callback. It’s a one-time setup during installation, but it does require a server.
Authorization code grant gives you a permanent, non-expiring offline token (not a 24-hour token like client credentials). The OAuth flow runs once during app installation, exchanges the authorization code for a permanent token, and then you’re done - that token works forever for your background scripts until the app is uninstalled.
Client credentials won’t work for your case because it only works when the app and store are owned by the same organization. Partner apps can’t use it on merchant stores (even your own merchant store), which is why you got the shop_not_permitted error.
The minimal setup you need:
-
Host a simple callback endpoint (can be serverless like Cloudflare Workers)
-
Merchant clicks the install link from Dev Dashboard
-
Shopify redirects to your callback with an authorization code
-
Your endpoint makes one POST request to exchange the code for a permanent token
-
Store that token somewhere secure
-
Use it for all future API calls - no expiration, no refresh needed
When you exchange the authorization code for the token, don’t include expiring=1 in the POST request. The default (expiring=0) gives you a non-expiring token. The response will just have access_token and scope - no expires_in field because it doesn’t expire. The authorization code grant docs show the full flow.
I know it’s more work than the legacy flow. The endpoint can be minimal - literally just receive the code, POST to get the token, save it, and redirect. After that one-time setup, your backend scripts work exactly like before with a permanent token.
I do appreciate you raising this - you’re not alone in finding this frustrating, and all this feedback is taken onboard by the team