---
title: Authenticate an app for stores in your organization
description: >-
  Use the client credentials grant to get access tokens for a server-side app
  that acts on stores in your own Shopify organization.
source_url:
  html: >-
    https://shopify.dev/docs/apps/build/authentication-authorization/client-credentials-grant?lang=node
  md: >-
    https://shopify.dev/docs/apps/build/authentication-authorization/client-credentials-grant.md?lang=node
---

# Authenticate an app for stores in your organization

This tutorial shows how to use the [client credentials grant](https://shopify.dev/docs/apps/build/authentication-authorization/access-tokens#client-credentials-grant) to get access tokens for a server-side app acting on stores in your own Shopify organization. Of the three grants, it takes the least setup: your app exchanges its own client ID and secret for a token, with no redirect flow to implement.

With a client credentials grant, you won't see a token in the Shopify admin. Instead, you request tokens programmatically when you need them.

**Info:**

If you're building apps for other merchants, use [Shopify CLI](https://shopify.dev/docs/apps/build/cli-for-apps), which handles authentication automatically. To learn how authentication works for other common approaches, see [About app authentication](https://shopify.dev/docs/apps/build/authentication-authorization).

## What you'll learn

In this tutorial, you'll learn how to do the following tasks:

* Find your app credentials in the Dev Dashboard
* Exchange credentials for an access token programmatically
* Use the access token to call Shopify APIs

## Requirements

[Dev Dashboard app](https://shopify.dev/docs/apps/build/dev-dashboard/create-apps-using-dev-dashboard)

You've created an app in the Dev Dashboard.

[Access scopes](https://shopify.dev/docs/apps/build/dev-dashboard/create-apps-using-dev-dashboard#step-2-create-a-version)

You've selected the access scopes your app needs on your app's version in the Dev Dashboard.

[Installed app](https://shopify.dev/docs/apps/build/dev-dashboard/create-apps-using-dev-dashboard#step-3-install-your-app)

You've installed your app on your store.

## Project

[View on GitHub](https://github.com/Shopify/example-auth--client-credentials-grant)

## Get your credentials

Find your **Client ID** and **Client secret** in the Dev Dashboard. These credentials identify your app when requesting access tokens.

**Caution:**

Keep your Client secret secure. Set it as an environment variable rather than putting it in a file you might commit, and never commit secrets to version control. In production, read it from your platform's environment configuration or a secret manager.

### Locate your credentials

1. Open your app in the [Dev Dashboard](https://dev.shopify.com/dashboard/).
2. Go to **Settings**.
3. Copy your **Client ID** and **Client secret**.

![Dev Dashboard settings page showing the Client ID and Secret fields.](https://shopify.dev/assets/assets/images/apps/dev-dashboard/app-settings-BERWjF61.png)

[About the Dev Dashboard](https://shopify.dev/docs/apps/build/dev-dashboard)

## Set your credentials as environment variables

Keep your credentials out of your code so that you can't commit them and can use different values per environment.

### Add your credentials

The example code reads three variables from the environment. `SHOPIFY_SHOP` is your store's `myshopify.com` subdomain, without `.myshopify.com`:

```bash
export SHOPIFY_SHOP=your-store
export SHOPIFY_CLIENT_ID=your-client-id
export SHOPIFY_CLIENT_SECRET=your-client-secret
```

The examples also read a `.env` file when one is present, and environment variables take precedence over it. If you use a `.env` file, add it to your `.gitignore`.

## /node/index.js

```javascript
import { existsSync } from 'node:fs';
import { URLSearchParams } from 'node:url';


// Credentials come from the environment. Also read .env when one happens to be
// there, since the Shopify CLI writes credentials to that file. Real environment
// variables win over the file, so a platform's config always takes precedence.
// loadEnvFile needs Node.js 20.12 or later, and throws if the file is missing.
if (existsSync('.env')) {
  process.loadEnvFile('.env');
}


const SHOP = process.env.SHOPIFY_SHOP;
const CLIENT_ID = process.env.SHOPIFY_CLIENT_ID;
const CLIENT_SECRET = process.env.SHOPIFY_CLIENT_SECRET;


if (!SHOP || !CLIENT_ID || !CLIENT_SECRET) {
  throw new Error(
    'Set SHOPIFY_SHOP, SHOPIFY_CLIENT_ID, and SHOPIFY_CLIENT_SECRET in your environment.'
  );
}


let token = null;
let tokenExpiresAt = 0;


async function getToken() {
  if (token && Date.now() < tokenExpiresAt - 60_000) return token;


  const response = await fetch(
    `https://${SHOP}.myshopify.com/admin/oauth/access_token`,
    {
      method: 'POST',
      headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
      body: new URLSearchParams({
        grant_type: 'client_credentials',
        client_id: CLIENT_ID,
        client_secret: CLIENT_SECRET,
      }),
    }
  );


  if (!response.ok) throw new Error(`Token request failed: ${response.status}`);


  const { access_token, expires_in } = await response.json();
  token = access_token;
  tokenExpiresAt = Date.now() + expires_in * 1000;
  return token;
}


async function graphql(query, variables = {}) {
  const response = await fetch(
    `https://${SHOP}.myshopify.com/admin/api/2025-01/graphql.json`,
    {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'X-Shopify-Access-Token': await getToken(),
      },
      body: JSON.stringify({ query, variables }),
    }
  );


  if (!response.ok) {
    throw new Error(`GraphQL request failed: ${response.status}`);
  }


  const { data, errors } = await response.json();
  if (errors?.length) {
    throw new Error(`GraphQL errors: ${JSON.stringify(errors)}`);
  }
  return data;
}


async function main() {
  const query =
    '{ products(first: 3) { edges { node { id title handle } } } }';
  const data = await graphql(query);
  console.log('Products:', JSON.stringify(data, null, 2));
}


main().catch((error) => {
  console.error(error);
  process.exitCode = 1;
});
```

## Request an access token

Use your credentials to make a programmatic request to Shopify's token endpoint.

### Exchange credentials for a token

The code reads your credentials from the environment and exchanges them for an access token. Tokens expire after 24 hours, so the example caches the token and refreshes it before expiry rather than requesting a new one per call.

***

Token response format

```json
{
  "access_token": "f85632530bf277ec9ac6f649fc327f17",
  "scope": "read_products",
  "expires_in": 86399
}
```

* `access_token`: The token to include in API requests. Store this securely.
* `scope`: The [access scopes](https://shopify.dev/docs/api/usage/access-scopes) granted to your app. The token request doesn't ask for scopes, so this is a readback of what you selected on your app's version in the Dev Dashboard. If a scope you need is missing, [release a new version](https://shopify.dev/docs/apps/build/dev-dashboard/create-apps-using-dev-dashboard#step-2-create-a-version) with it and approve the change on the store.
* `expires_in`: Seconds until expiration. Always 86399 (24 hours).

***

##### `shop_not_permitted` error

**Problem:** You receive the error `Oauth error shop_not_permitted: Client credentials cannot be performed on this shop.`

**Solution:** The client credentials grant only works when the app and the store belong to the same Shopify organization. "Same organization" means both appear under the same org in the Dev Dashboard. Owning a store or having it installed doesn't automatically place it in your org.

To verify:

1. Open the [Dev Dashboard](https://dev.shopify.com/dashboard/) and click **Apps**. Confirm your app is listed.
2. Click **Dev stores** in the sidebar and confirm your target store appears in the list. If the store isn't listed, it's not in this organization.
3. Check that your `SHOPIFY_SHOP` value matches the store's `*.myshopify.com` subdomain exactly (without `.myshopify.com`).

Common causes:

* **Dev store created outside the Dev Dashboard:** If you created a dev store from the Shopify admin rather than from the Dev Dashboard, it won't be in your org. Create a new dev store from the **Dev stores** page in the Dev Dashboard instead.
* **Multiple organizations:** If you have access to more than one organization, the app and store might be in different ones. Check the organization ID in the URL (`dev.shopify.com/dashboard/<org-id>`) and verify both the app and store are under the same one.
* **Acting on another organization's stores:** Client credentials can't reach a store outside your organization, including a client's store. Distribute your app to that store with [custom distribution](https://shopify.dev/docs/apps/launch/distribution/select-distribution-method) so that a merchant installs it, then use [token exchange](https://shopify.dev/docs/apps/build/authentication-authorization/implement-token-exchange) if your app runs inside the Shopify admin, or the [authorization code grant](https://shopify.dev/docs/apps/build/authentication-authorization/authenticate-standalone-apps) if it runs outside. [Shopify CLI](https://shopify.dev/docs/apps/build/cli-for-apps) handles OAuth for you.

##### External tool asks you to "copy a token"

**Problem:** Some external tools ask you to copy a token or provide a "Shopify API key." These tools expect the older authentication flow.

**Solution:** Contact the tool vendor about updating their integration to use OAuth.

##### "Invalid API key or access token" error

**Problem:** You're sending your `client_id` or `client_secret` directly to the GraphQL Admin API.

**Solution:** First exchange your credentials for an `access_token` using the token endpoint, then use that token in your API requests.

## /node/index.js

```javascript
import { existsSync } from 'node:fs';
import { URLSearchParams } from 'node:url';


// Credentials come from the environment. Also read .env when one happens to be
// there, since the Shopify CLI writes credentials to that file. Real environment
// variables win over the file, so a platform's config always takes precedence.
// loadEnvFile needs Node.js 20.12 or later, and throws if the file is missing.
if (existsSync('.env')) {
  process.loadEnvFile('.env');
}


const SHOP = process.env.SHOPIFY_SHOP;
const CLIENT_ID = process.env.SHOPIFY_CLIENT_ID;
const CLIENT_SECRET = process.env.SHOPIFY_CLIENT_SECRET;


if (!SHOP || !CLIENT_ID || !CLIENT_SECRET) {
  throw new Error(
    'Set SHOPIFY_SHOP, SHOPIFY_CLIENT_ID, and SHOPIFY_CLIENT_SECRET in your environment.'
  );
}


let token = null;
let tokenExpiresAt = 0;


async function getToken() {
  if (token && Date.now() < tokenExpiresAt - 60_000) return token;


  const response = await fetch(
    `https://${SHOP}.myshopify.com/admin/oauth/access_token`,
    {
      method: 'POST',
      headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
      body: new URLSearchParams({
        grant_type: 'client_credentials',
        client_id: CLIENT_ID,
        client_secret: CLIENT_SECRET,
      }),
    }
  );


  if (!response.ok) throw new Error(`Token request failed: ${response.status}`);


  const { access_token, expires_in } = await response.json();
  token = access_token;
  tokenExpiresAt = Date.now() + expires_in * 1000;
  return token;
}


async function graphql(query, variables = {}) {
  const response = await fetch(
    `https://${SHOP}.myshopify.com/admin/api/2025-01/graphql.json`,
    {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'X-Shopify-Access-Token': await getToken(),
      },
      body: JSON.stringify({ query, variables }),
    }
  );


  if (!response.ok) {
    throw new Error(`GraphQL request failed: ${response.status}`);
  }


  const { data, errors } = await response.json();
  if (errors?.length) {
    throw new Error(`GraphQL errors: ${JSON.stringify(errors)}`);
  }
  return data;
}


async function main() {
  const query =
    '{ products(first: 3) { edges { node { id title handle } } } }';
  const data = await graphql(query);
  console.log('Products:', JSON.stringify(data, null, 2));
}


main().catch((error) => {
  console.error(error);
  process.exitCode = 1;
});
```

## Make API requests

Include the `access_token` in the `X-Shopify-Access-Token` header when calling Shopify APIs.

### Query the Graph​QL Admin API

Use the access token to authenticate requests to any Shopify API. This example queries products using the GraphQL Admin API.

[Graph​QL Admin API](https://shopify.dev/docs/api/admin-graphql) [Access scopes](https://shopify.dev/docs/api/usage/access-scopes)

## /node/index.js

```javascript
import { existsSync } from 'node:fs';
import { URLSearchParams } from 'node:url';


// Credentials come from the environment. Also read .env when one happens to be
// there, since the Shopify CLI writes credentials to that file. Real environment
// variables win over the file, so a platform's config always takes precedence.
// loadEnvFile needs Node.js 20.12 or later, and throws if the file is missing.
if (existsSync('.env')) {
  process.loadEnvFile('.env');
}


const SHOP = process.env.SHOPIFY_SHOP;
const CLIENT_ID = process.env.SHOPIFY_CLIENT_ID;
const CLIENT_SECRET = process.env.SHOPIFY_CLIENT_SECRET;


if (!SHOP || !CLIENT_ID || !CLIENT_SECRET) {
  throw new Error(
    'Set SHOPIFY_SHOP, SHOPIFY_CLIENT_ID, and SHOPIFY_CLIENT_SECRET in your environment.'
  );
}


let token = null;
let tokenExpiresAt = 0;


async function getToken() {
  if (token && Date.now() < tokenExpiresAt - 60_000) return token;


  const response = await fetch(
    `https://${SHOP}.myshopify.com/admin/oauth/access_token`,
    {
      method: 'POST',
      headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
      body: new URLSearchParams({
        grant_type: 'client_credentials',
        client_id: CLIENT_ID,
        client_secret: CLIENT_SECRET,
      }),
    }
  );


  if (!response.ok) throw new Error(`Token request failed: ${response.status}`);


  const { access_token, expires_in } = await response.json();
  token = access_token;
  tokenExpiresAt = Date.now() + expires_in * 1000;
  return token;
}


async function graphql(query, variables = {}) {
  const response = await fetch(
    `https://${SHOP}.myshopify.com/admin/api/2025-01/graphql.json`,
    {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'X-Shopify-Access-Token': await getToken(),
      },
      body: JSON.stringify({ query, variables }),
    }
  );


  if (!response.ok) {
    throw new Error(`GraphQL request failed: ${response.status}`);
  }


  const { data, errors } = await response.json();
  if (errors?.length) {
    throw new Error(`GraphQL errors: ${JSON.stringify(errors)}`);
  }
  return data;
}


async function main() {
  const query =
    '{ products(first: 3) { edges { node { id title handle } } } }';
  const data = await graphql(query);
  console.log('Products:', JSON.stringify(data, null, 2));
}


main().catch((error) => {
  console.error(error);
  process.exitCode = 1;
});
```

## /node/index.js

```javascript
import { existsSync } from 'node:fs';
import { URLSearchParams } from 'node:url';


// Credentials come from the environment. Also read .env when one happens to be
// there, since the Shopify CLI writes credentials to that file. Real environment
// variables win over the file, so a platform's config always takes precedence.
// loadEnvFile needs Node.js 20.12 or later, and throws if the file is missing.
if (existsSync('.env')) {
  process.loadEnvFile('.env');
}


const SHOP = process.env.SHOPIFY_SHOP;
const CLIENT_ID = process.env.SHOPIFY_CLIENT_ID;
const CLIENT_SECRET = process.env.SHOPIFY_CLIENT_SECRET;


if (!SHOP || !CLIENT_ID || !CLIENT_SECRET) {
  throw new Error(
    'Set SHOPIFY_SHOP, SHOPIFY_CLIENT_ID, and SHOPIFY_CLIENT_SECRET in your environment.'
  );
}


let token = null;
let tokenExpiresAt = 0;


async function getToken() {
  if (token && Date.now() < tokenExpiresAt - 60_000) return token;


  const response = await fetch(
    `https://${SHOP}.myshopify.com/admin/oauth/access_token`,
    {
      method: 'POST',
      headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
      body: new URLSearchParams({
        grant_type: 'client_credentials',
        client_id: CLIENT_ID,
        client_secret: CLIENT_SECRET,
      }),
    }
  );


  if (!response.ok) throw new Error(`Token request failed: ${response.status}`);


  const { access_token, expires_in } = await response.json();
  token = access_token;
  tokenExpiresAt = Date.now() + expires_in * 1000;
  return token;
}


async function graphql(query, variables = {}) {
  const response = await fetch(
    `https://${SHOP}.myshopify.com/admin/api/2025-01/graphql.json`,
    {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'X-Shopify-Access-Token': await getToken(),
      },
      body: JSON.stringify({ query, variables }),
    }
  );


  if (!response.ok) {
    throw new Error(`GraphQL request failed: ${response.status}`);
  }


  const { data, errors } = await response.json();
  if (errors?.length) {
    throw new Error(`GraphQL errors: ${JSON.stringify(errors)}`);
  }
  return data;
}


async function main() {
  const query =
    '{ products(first: 3) { edges { node { id title handle } } } }';
  const data = await graphql(query);
  console.log('Products:', JSON.stringify(data, null, 2));
}


main().catch((error) => {
  console.error(error);
  process.exitCode = 1;
});
```

## Tutorial complete!

You've successfully authenticated your Dev Dashboard app using the client credentials grant and made API requests.

### Next steps

[Manage your credentials\
\
](https://shopify.dev/docs/apps/build/authentication-authorization/manage-credentials)

[Find your client ID and secret, secure them, and rotate your client secret.](https://shopify.dev/docs/apps/build/authentication-authorization/manage-credentials)

[Manage access scopes\
\
](https://shopify.dev/docs/apps/build/authentication-authorization/manage-access-scopes)

[Find the scopes that common resources need, and where to declare them for a Dev Dashboard app.](https://shopify.dev/docs/apps/build/authentication-authorization/manage-access-scopes)

[GraphQL Admin API\
\
](https://shopify.dev/docs/api/admin-graphql)

[Start building with the GraphQL Admin API.](https://shopify.dev/docs/api/admin-graphql)

[Monitor app performance\
\
](https://shopify.dev/docs/apps/build/dev-dashboard/monitoring-and-logs)

[Access logs and metrics to understand and optimize your app's performance.](https://shopify.dev/docs/apps/build/dev-dashboard/monitoring-and-logs)
