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!

We are experiencing the same issue in prod (401 error in extension). However, only sometimes, which suggests it’s the error you are mentioning regarding expired tokens.

I don’t see anything in the documentation how to refresh an access token inside a POS UI extension, so what would be best practice here?

Apologies for the delayed follow up – I’ve switched to using direct_api_mode = "offline" and I still get the exact same 401 error. I’m logged in as the shop admin on the POS app. Is there any way to get more information from the error?

Hey @pos_ui_testing - thanks for following up! You should be able to get a bit more info on the error itself, following these steps:

If you’re still not able to narrow down what’s causing the issue though, no worries. If you follow the steps below to send some logs our way after replicating the error, we can take a closer look into this with you for sure:

Open the Shopify POS app on your device.

  1. Tap the menu icon (three horizontal lines or dots) on the left side of the screen.

  2. Select Support from the menu.

  3. Tap Report a bug

  4. In the message box, please include your name and any relevant details about the issue you’re experiencing. Also include my username there: Alan_G.

  5. Tap Send to submit the report.

Once we have this from you I can take a look on my end and dig into this further - hope this helps!

Hey @pos_ui_testing , just following up here to see if we can help out further, just let me know if you send those logs our way and I can look into this.

Hey @pos_ui_testing - wanted to follow up here to close the loop on this thread. If I can still help with anything further though, please ping me here and I can take a look.

Hi @Alan_G - just hijacking this thread as I’m experiencing the same issue. I’ve tried all the suggested steps in this thread without success. My setup is as follows:

shopify.app.toml

client_id = "[redacted]"
name = "my-app"
application_url = "https://example.com"
embedded = true

[webhooks]
api_version = "2026-01"

[access_scopes]
scopes = "read_content"

[auth]
redirect_urls = [ "https://example.com/api/auth" ]

[build]
automatically_update_urls_on_dev = true

[access.admin]
direct_api_mode = "offline"

shopify.extension.toml

api_version = "2025-07"

[[extensions]]
uid = "[redacted]"
type = "ui_extension"
name = "pos-ui"
handle = "pos-ui"
description = "POS UI"

[[extensions.targeting]]
module = "./src/LineItemMenuItem.jsx"
target = "pos.cart.line-item-details.action.menu-item.render"

[[extensions.targeting]]
module = "./src/LineItemActionModal.jsx"
target = "pos.cart.line-item-details.action.render"

LineItemActionModal.jsx

import {
    Navigator,
    Screen,
    ScrollView,
    Text,
    reactExtension,
    useApi,
} from '@shopify/ui-extensions-react/point-of-sale';
import { useEffect } from 'react';

const Modal = () => {
  const api = useApi();

    useEffect(() => {
      async function fetchShopName() {
        try {
          const res = await fetch('shopify:admin/api/graphql.json', {
            method: 'POST',
            body: JSON.stringify({
              query: `#graphql
                query {
                  shop {
                    name
                  }
                }
              `,
            }),
          });

          const data = await res.json();
          console.log(data);
        } catch (err) {
          console.error(err);
        }
      }

    fetchShopName();
  }, []);

  return (
    <Navigator>
      <Screen name='test' title="Test">
        <ScrollView>
          <Text>Test Render</Text>
        </ScrollView>
      </Screen>
    </Navigator>
  );
};

export default reactExtension('pos.cart.line-item-details.action.render', () => (
  <Modal />
));

This same query returns a response when running in GraphiQL under the same access scope. I’m testing this purely using the shopify app dev command, installing to a development store where I am the sole user and owner, using the QR scanner in the UI DevConsole to open on my phone.

I tried extensively to get the chrome debugging working, however I was unable to get the webview to appear at all.

CLI version: 3.85.4
Mobile device: Samsung Galaxy s24, Android 16
POS app version: 10.11.1

I see you mentioned online access tokens but I’m unsure what my responsibility is with these as far as making this request inside my extension goes?

Hey @jshortall, thanks for reaching out and for waiting on my reply here, appreciate you sending all of that info as well. Would you be open to share your myshopify.com URL and the App ID for your POS extension app? I can try to do some further troubleshooting on my end here as well to see if I’m able to replicate the issue.

If you’re open to sharing some logs in the same way as above, I can find those on my end as well if you haven’t already and take a look.

Let me know if you’d rather share that info over a DM and I can set one up in the community here with you (just to keep things private if you’d prefer that). Hope to hear from you soon!

Hey @jshortall - wanted to check in to see if I can help out with this still, just ping me here!

Hey @Alan_G, apologies for the delay. The issue was simply that the extension needed enabling within the POS sales channel under POS apps in the Shopify admin.

Also sorry for the delayed response. Could you explain exactly how you enabled the app in the POS channel through the admin page? Wondering if that’s my issue but it’s unclear where I should be navigating to.

Of course. I also had the same confusion and didn’t realise it needed enabling. From your Shopify Admin, if you go to the POS sales channel > settings > general > POS apps and you should see your extension there.

Thanks @jshortall for reaching back out, glad to hear that this is solved for you! @pos_ui_testing - if you need more help, just ping me here and I can take a look.