How to get Shopify Access token - sent to Dev Dashboard?

Hi all,

I am trying to create a Shopify Access Token and watched many YouTube videos to learn how to do it.

However, unlike in the videos I only have the option to build apps in the Dev Dashboard.

And when I click it I get into this screen, where I could build an app. But it is unlike what I can find tutorials for.

Can someone help what to do to create an Access Token?

Any help is much appreciated.

Thank.

Olaf

Hi @Olaf2

You’ve run into an outdated tutorial. Shopify has replaced old “private apps” with the new “custom app” flow, which is what you’re seeing. You are in the right place.

Here is the quick, modern way to get your token:

  1. In your Shopify admin, go to Apps > Apps and sales channels > Develop apps**.
  2. Click “Allow custom app development” (you only need to do this once).
  3. Click “Create an app” and give it a name.
  4. Once created, click the “Configure Admin API scopes” tab. Select all the permissions you need (like read_products) and click Save.
  5. Go to the “API credentials” tab, click “Install app,” and confirm.
  6. The page will refresh. Now you can click “Reveal token once” to get your Admin API access token.

This is the new, correct process, and that token (starting with shp_) is what you’re looking for.

Hope this helps

Hi @PieLab,

Thank you for coming back to me. You lost me at step 4/5.

When I click create an app, I get this screen:

And then I choose the right option and gave it a name, selected create.

In the next screen I selected a bunch of Scopes. But I don’t see “API credentials”.

Thanks.

Hello! @Olaf2

Correct, facing same, Have you figured out how to have access token?

Anyone got the solution? I am not able to get the access token with the new UI.

Please advise

Make a POST request to your shop url like so

https://{shop_subdomain}.myshopify.com/admin/oauth/access_token

and body should be JSON with

{
“client_id”: “YOUR_CLIENT_ID”,
“client_secret”: “YOUR_CLIENT_SECRET”,
“grant_type”: “client_credentials”
}

then you should get a response like so

{

"access_token": "shpca_xxxxxxxxx",
 "scope": "write_draft_orders,write_products",
 "expires_in": 86399

}

In your app you should make a request every 24h to get a new access token

I’ve been looking for how to get this for 2 days now. Thank you for posting it.

I tried to generate the access_token using the OAuth flow, but I’m getting the following error:

Oauth error shop_not_permitted: Client credentials cannot be performed on this shop.

Do you know what could be causing this or if there’s any additional configuration required?

I was also struggling with this error bout found a solution. If you want to use custom app on paid store, not dev store, you need to implement token exchange (for embedded app) or Authorization code grant (for BE apps) as described here: About token acquisition

Simply Using the client credentials grant works only for dev stores - If you try getting access token this way from paid store you will get error:

Oauth error shop_not_permitted: Client credentials cannot be performed on this shop.

This topic is also discussed here: https://community.shopify.dev/t/custom-app-credentials/27460/9

I talked to support about this and they basically gave me the same solution that Sergiu222 posted above. The problem is these tokens only last 24 hours so you have to redo this process every single day. Which of course is ridiculous and not a viable solution.

Really irresponsible of Shopify to suddenly break API integration like this.

How is this possible ? We can’t create a private app with an access_token that last longer than 24 hours ???

You need an offline access token. No need top set expires param in request.

I’ve built a simple token obtainer tool for that.

This video has the solution.

Props to Jan. [email removed]Coding-with-Jan

Hi. Here is my solution fabricated with the aid of Claude and it worked!

How to Set Up a Properly Scoped Shopify API Token for CLI Scripts

Background

I run a series of Python CLI scripts to manage my Shopify store — bulk product updates, pricing, metafields, draft orders etc. I needed a robust way to get a properly scoped API token that could be refreshed automatically every 24 hours.

This guide covers the full setup including the gotchas I ran into along the way. Hopefully it saves someone else the same pain.


The Problem

Shopify custom app tokens expire every 24 hours. There are two ways to get a new one:

  1. Client credentials flow — simple POST request, no browser needed, easy to automate
  2. OAuth authorization code flow — requires browser interaction, but gives a properly scoped token

The catch: the client credentials flow only works correctly if your app is not using the legacy install flow. If you’re getting a token back with empty scopes, that’s why.


Step 1 — Create a Custom App in the Partners Dashboard

  1. Go to https://partners.shopify.com
  2. Apps → Create appStart from Dev Dashboard
  3. Enter an app name and click Create

Configure the app

In the Configuration page:

App URL:

http://localhost:3456

Scopes — paste into the Scopes field (adjust to your needs):

read_all_orders,write_checkouts,read_checkouts,read_customers,write_customers,
read_price_rules,write_price_rules,write_draft_orders,read_draft_orders,
read_fulfillments,write_fulfillments,write_inventory,read_inventory,
write_locations,read_locations,read_metaobjects,write_metaobjects,
read_orders,write_orders,read_product_listings,write_product_listings,
read_products,write_products,read_shipping,write_shipping,read_themes

Redirect URLs:

http://localhost:3456/callback

Important settings:

  • :cross_mark: Use legacy install flow — leave unchecked
  • :cross_mark: Embed app in Shopify admin — leave unchecked

Click Release.

Get your credentials

Go to the Settings tab and note down:

  • Client ID
  • Client Secret (click the eye icon to reveal)

Step 2 — Run the OAuth Catch Server

Since the redirect goes to localhost, you need a small local server running to catch the OAuth callback and exchange it for a token.

Save this as shopify_oauth_catch.py and update the credentials:

#!/usr/bin/env python3
import http.server
import urllib.parse
import requests
import os
import secrets

env = {}
with open(os.path.expanduser("~/.env")) as f:
    for line in f:
        line = line.strip()
        if line and not line.startswith("#"):
            key, _, value = line.partition("=")
            env[key.strip()] = value.strip()

SHOP = env["SHOPIFY_SHOP"]
CLIENT_ID = "YOUR_CLIENT_ID"
CLIENT_SECRET = "YOUR_CLIENT_SECRET"
PORT = 3456

class OAuthHandler(http.server.BaseHTTPRequestHandler):
    def do_GET(self):
        parsed = urllib.parse.urlparse(self.path)
        params = urllib.parse.parse_qs(parsed.query)

        if "code" in params:
            code = params["code"][0]
            print(f"\n✓ Got authorization code: {code[:20]}...")
            url = f"https://{SHOP}/admin/oauth/access_token"
            payload = {
                "client_id": CLIENT_ID,
                "client_secret": CLIENT_SECRET,
                "code": code,
            }
            r = requests.post(url, json=payload)
            data = r.json()
            if "access_token" in data:
                token = data["access_token"]
                print(f"✓ Token: {token[:20]}...")
                print(f"  Scopes: {data.get('scope', '')}")
                env_path = os.path.expanduser("~/.env")
                with open(env_path, "r") as f:
                    lines = f.readlines()
                with open(env_path, "w") as f:
                    for line in lines:
                        if line.startswith("SHOPIFY_TOKEN="):
                            f.write(f"SHOPIFY_TOKEN={token}\n")
                        else:
                            f.write(line)
                print(f"✓ ~/.env updated!")
                self.send_response(200)
                self.end_headers()
                self.wfile.write(b"<h1>Token captured! You can close this window.</h1>")
                import threading
                threading.Thread(target=self.server.shutdown).start()
            else:
                print(f"✗ Failed: {data}")
                self.send_response(400)
                self.end_headers()
                self.wfile.write(b"Failed to get token")

        elif "shop" in params:
            shop = params["shop"][0]
            state = secrets.token_hex(16)
            scopes = "read_products,write_products,read_orders,write_draft_orders,read_draft_orders,read_customers,write_customers,read_inventory,write_inventory,read_locations"
            redirect_uri = f"http://localhost:{PORT}/callback"
            auth_url = (f"https://{shop}/admin/oauth/authorize"
                       f"?client_id={CLIENT_ID}"
                       f"&scope={scopes}"
                       f"&redirect_uri={urllib.parse.quote(redirect_uri)}"
                       f"&state={state}")
            print(f"\n→ Redirecting to OAuth authorization...")
            self.send_response(302)
            self.send_header("Location", auth_url)
            self.end_headers()
        else:
            self.send_response(200)
            self.end_headers()
            self.wfile.write(b"Waiting for OAuth callback...")

    def log_message(self, format, *args):
        pass

print(f"Starting OAuth server on http://localhost:{PORT} — waiting for callback...")
server = http.server.HTTPServer(("localhost", PORT), OAuthHandler)
server.serve_forever()

Run it:

python3 shopify_oauth_catch.py

Then go to your Partners dashboard → your app → Overview → click Install app.

You’ll see a permissions screen — click Install. The browser will redirect to localhost, the script catches the callback, exchanges the code for a token, and saves it to ~/.env automatically.


Step 3 — Daily Token Refresh

Once the app is installed, you can refresh the token automatically using the client credentials flow — no browser needed:

#!/usr/bin/env python3
# shopify_get_token.py
import requests, os, sys

env = {}
with open(os.path.expanduser("~/.env")) as f:
    for line in f:
        line = line.strip()
        if line and not line.startswith("#"):
            key, _, value = line.partition("=")
            env[key.strip()] = value.strip()

SHOP = env["SHOPIFY_SHOP"]
args = sys.argv[1:]
client_id = args[args.index("--client-id") + 1] if "--client-id" in args else None
client_secret = args[args.index("--client-secret") + 1] if "--client-secret" in args else None

url = f"https://{SHOP}/admin/oauth/access_token"
payload = {
    "grant_type": "client_credentials",
    "client_id": client_id,
    "client_secret": client_secret,
}
r = requests.post(url, data=payload)
data = r.json()

if "access_token" in data:
    token = data["access_token"]
    print(f"✓ Token: {token[:15]}... Scopes: {data.get('scope','')}")
    env_path = os.path.expanduser("~/.env")
    with open(env_path, "r") as f:
        lines = f.readlines()
    with open(env_path, "w") as f:
        for line in lines:
            f.write(f"SHOPIFY_TOKEN={token}\n" if line.startswith("SHOPIFY_TOKEN=") else line)
    print("✓ ~/.env updated")
else:
    print(f"✗ Failed: {data}")

Run manually:

python3 shopify_get_token.py --client-id YOUR_CLIENT_ID --client-secret YOUR_CLIENT_SECRET

Automate with cron (refreshes daily at 6am):

0 6 * * * /path/to/refresh_shopify_token.sh

Where refresh_shopify_token.sh contains:

#!/bin/bash
python3 /path/to/shopify_get_token.py \
  --client-id YOUR_CLIENT_ID \
  --client-secret YOUR_CLIENT_SECRET \
  >> /path/to/results/token_refresh.log 2>&1

Gotchas

Empty scopes from client credentials flow
If you get a token back but scope is empty, your app is using the legacy install flow. Create a new app with legacy install flow unchecked.

status=any returns 0 products
On some stores status=any doesn’t work. Fetch active and draft separately and merge.

metafield_definitions require GraphQL
The REST endpoint for metafield definitions returns HTTP 406. Use the GraphQL Admin API instead.

Blank metafield values rejected
Shopify rejects empty string "" as a metafield value. Use "-" as a placeholder.

“Embed app in Shopify admin” causes redirect issues
If this is checked, the OAuth redirect goes to the Shopify admin instead of your localhost callback. Uncheck it for CLI-only apps.

“localhost sent an invalid response”
The browser blocked HTTP localhost. The OAuth catch script handles this by intercepting the initial app launch request and redirecting to the proper OAuth URL itself.


~/.env Structure

SHOPIFY_SHOP=yourstore.myshopify.com
SHOPIFY_TOKEN=shpat_xxxxxxxxxxxxxxxxxxxx

The token is updated in place by both scripts so all your CLI scripts that read from ~/.env automatically get the new token.