POS UI extension direct API access returns 401 unauthorized

I’m trying to use direct API access from an iOS POS UI extension, but every request to shopify:admin/api/graphql.json comes back with 401 Unauthorized. I’ve tested various GraphQL queries using direct API access which have all failed. The queries have also been tested in GraphiQL (launched from the CLI) where they’ve run successfully, contrary to the requests made in the app.

For debugging purposes, a new extension only app scaffold was created using the Shopify CLI, where the only changes are:

  1. Addition of a file called Probe.jsx (alongside necessary changes to shopify.app.toml) which targets the pos.product-details.block.render surface and performs a simple request to the GraphQL API to get the shop’s name.
  2. Setting scopes = "read_content" in the main shopify.app.toml.

Environment

  • Device: iPhone 15 Pro
  • iOS version: 18.6.2
  • Shopify POS iOS app version: 10.9.1
  • Shopify CLI version: 3.84.0

shopify.extension.toml

api_version = "2025-07"

[[extensions]]
type = "ui_extension"
name = "pos-ui-post-purchase"

handle = "pos-ui-post-purchase"
description = "A react POS UI extension"

[[extensions.targeting]]
module = "./src/Action.jsx"
target = "pos.purchase.post.action.render"

[[extensions.targeting]]
module = "./src/Block.jsx"
target = "pos.purchase.post.block.render"

[[extensions.targeting]]
module = "./src/MenuItem.jsx"
target = "pos.purchase.post.action.menu-item.render"

[[extensions.targeting]]
module = "./src/Probe.jsx"
target = "pos.product-details.block.render"

shopify.app.toml

client_id = "[redacted]"
name = "my-app"
application_url = "https://shopify.dev/apps/default-app-home"
embedded = true
handle = "my-app"

[build]
include_config_on_deploy = true

[webhooks]
api_version = "2025-07"

[access_scopes]
scopes = "read_content"

[auth]
redirect_urls = [ "https://shopify.dev/apps/default-app-home/api/auth" ]

[pos]
embedded = false

Probe.jsx

import {
  extension,
  POSBlock,
  POSBlockRow,
  Text,
} from '@shopify/ui-extensions/point-of-sale';

async function queryShopName() {
  const requestBody = {
    query: `#graphql
      query {
        shop { name }
      }
    `,
  };

  try {
    const res = await fetch('shopify:admin/api/graphql.json', {
      method: 'POST',
      body: JSON.stringify(requestBody),
    });

    const text = await res.text();
    let json = null;
    try {
      json = JSON.parse(text);
    } catch {
      console.error('[Probe] JSON parse failed, raw body:', text);
      return;
    }

    if (!res.ok) {
      console.error('[Probe] Network Error:', res.status, res.statusText);
      console.error('[Probe] Body:', text);
      return;
    }

    if (json.errors) {
      console.error('[Probe] GraphQL Errors:', json.errors);
      return;
    }

    console.log('[Probe] Success:', json.data);
  } catch (err) {
    console.error('[Probe] Fetch exception:', err);
  }
}

export default extension('pos.product-details.block.render', (root) => {
  const resultText = root.createComponent(Text, { children: 'Running probe…' });
  const row = root.createComponent(POSBlockRow);
  row.append(resultText);

  const block = root.createComponent(POSBlock);
  block.append(row);
  root.append(block);

  queryShopName().then(() => {
    resultText.replaceChildren('Done (check console)');
  });
});

Repro steps

  1. shopify app deploy, install app in shop through browser.
  2. Open browser preview, press “View mobile” for “pos-ui-post-purchase” handle, scan QR and launch POS app on iOS.
  3. On smart grid tap “Other Extension Targets” to run API request in Probe.jsx.

Probe logs

22:44:38 │ pos-ui-post-purchase │ ERROR: [Probe] Fetch exception: {
22:44:38 │ pos-ui-post-purchase │   "name": "ApolloError",
22:44:38 │ pos-ui-post-purchase │   "message": "Response not successful: Received status code 401"
22:44:38 │ pos-ui-post-purchase │ }

I’ve tried reinstalling the POS app, logging out and back in on both iOS and the CLI, creating a new development store, amongst other things I’m probably forgetting. Would appreciate any help, thank you!

You need an access token to call the Shopify API. Otherwise, it will return Unauthorized.

1 Like

From the docs on direct api access:

Any fetch() calls from your extension to Shopify’s Admin GraphQL API are automatically authenticated by default. These calls are fast too, because Shopify handles requests directly.

Given this, can you elaborate on what needs changing in the sample code provided (or elsewhere)?

Direct API access will use an online token by default, so you’ll need to make sure that the staff account you’re logged in with has the correct permissions for the API call you’re making.

You can also change this by putting the following in your shopify.app.toml :

[access.admin]
direct_api_mode = “offline”

That would be the first thing I’d check :slight_smile:

1 Like

Hey folks, just agreeing with @bkspace here and sharing a bit more info in case it’s helpful. As mentioned, Direct API calls do use an Online Access token by default. These tokens are scoped not only to the app making the call but also to the user in the admin.

If a shop’s staff user who doesn’t have the right permission levels on the shop tries to access something via your app, it could also return a 403 error. We’d only return 401 errors if the token is expired though. There’s a bit more info in our docs here, hope this helps!