API Fetch error: 401 / inconsistent

Hi all,

I made a Shopify UI Extension for our retail operation that needs to discount from an MSRP rather than the selling price that Shopify typically discounts from.

I got it working in testing, then deployed to the smart grid. We have a number of locations throughout California, and it seems to work on some but not others.

I put some debugging into the extension so that it would give an actual error code, and I’m getting a 401 Fetch error.

What’s confusing to me is that this works on my personal iPad, and at a couple of the showroom locations iPads. But other ones are getting the 401 fetch error.

Something I can’t do: run webview debugging. All of our devices are iPads but we only have Windows PCs/Laptops.

The only scope this extension has is read_products, which everyone has or they wouldnt be able to look at products at all.

Any suggestions are appreciated!

Best,
Olek

This is my modal.jsx

import {render} from ‘preact’;
import {useState, useEffect} from ‘preact/hooks’;

export default async () => {
render(, document.body);
};

function Extension() {
const [isLoading, setIsLoading] = useState(true);
const [isApplying, setIsApplying] = useState(false);
const [statusMessage, setStatusMessage] = useState(‘’);
const [eligibleItems, setEligibleItems] = useState();
const [selectedUuids, setSelectedUuids] = useState();

useEffect(() => {
async function fetchMsrpData() {
const lineItems = shopify.cart.current.value.lineItems;

  const catalogItems = lineItems.filter(
    (item) => item.productId !== undefined && item.productId !== null,
  );

  if (catalogItems.length === 0) {
    setStatusMessage('No eligible products found in cart.');
    setIsLoading(false);
    return;
  }

  const uniqueProductIds = [
    ...new Set(catalogItems.map((item) => item.productId)),
  ];

  const productAliases = uniqueProductIds
    .map(
      (id, index) => `
      product${index}: product(id: "gid://shopify/Product/${id}") {
        id
        metafield(namespace: "custom", key: "msrp") {
          value
        }
      }
    `,
    )
    .join('\n');

  const query = `query { ${productAliases} }`;

  try {
    const response = await fetch('shopify:admin/api/graphql.json', {
      method: 'POST',
      headers: {'Content-Type': 'application/json'},
      body: JSON.stringify({query}),
    });

    const data = await response.json();

  if (data.errors) {

setStatusMessage(GraphQL error: ${data.errors[0].message});
setIsLoading(false);
return;
}

    const msrpByProductId = {};
    uniqueProductIds.forEach((id, index) => {
      const productData = data.data[`product${index}`];
      if (productData && productData.metafield) {
        msrpByProductId[id] = parseFloat(productData.metafield.value);
      }
    });

    const items = [];
    for (const item of catalogItems) {
      const msrp = msrpByProductId[item.productId];
      if (msrp === undefined || isNaN(msrp)) continue;

      const discountedPrice = msrp * 0.6;
      const discountAmount = item.price - discountedPrice;
      if (discountAmount <= 0) continue;

      items.push({
        uuid: item.uuid,
        title: item.title || 'Unknown product',
        currentPrice: item.price,
        discountedPrice: discountedPrice,
        discountAmount: discountAmount,
      });
    }

    if (items.length === 0) {
      setStatusMessage(
        'No discounts to apply. Items may already be at or below the 40% off MSRP price.',
      );
      setIsLoading(false);
      return;
    }

    setEligibleItems(items);
    setSelectedUuids(items.map((item) => item.uuid));
} catch (error) {

console.error(‘Failed to fetch MSRP metafields:’, error);
setStatusMessage(Fetch error: ${error.message});
}

  setIsLoading(false);
}

fetchMsrpData();

}, );

const applyDiscounts = async () => {
if (selectedUuids.length === 0) {
setStatusMessage(‘Please select at least one item.’);
return;
}
setIsApplying(true);

const discountInputs = eligibleItems
  .filter((item) => selectedUuids.includes(item.uuid))
  .map((item) => ({
    lineItemUuid: item.uuid,
    lineItemDiscount: {
      type: 'FixedAmount',
      title: '40% Off MSRP',
      amount: item.discountAmount.toFixed(2),
    },
  }));

try {
  await shopify.cart.bulkSetLineItemDiscounts(discountInputs);
  shopify.toast.show('MSRP discounts applied');
} catch (error) {
  console.error('Failed to apply discounts:', error);
  setStatusMessage('Failed to apply discounts. Please try again.');
}

setIsApplying(false);

};

return (


{isLoading ? (

Loading products…

) : statusMessage ? (

{statusMessage}

) : (
<>

Select the items you want to discount:

<s-choice-list
multiple
values={selectedUuids}
onChange={(event) => setSelectedUuids([…event.currentTarget.values])}
>
{eligibleItems.map((item) => (

{item.title}

${item.currentPrice.toFixed(2)} → ${item.discountedPrice.toFixed(2)} (save ${item.discountAmount.toFixed(2)})


))}


<s-button
onClick={applyDiscounts}
disabled={isApplying || selectedUuids.length === 0}
>
{isApplying
? ‘Applying…’
: Apply discount to ${selectedUuids.length} item${selectedUuids.length !== 1 ? 's' : ''}}


</>
)}


);
}

Here’s my tile.jsx

import ‘@shopify/ui-extensions/preact’;
import {render} from ‘preact’;
import {useState, useEffect} from ‘preact/hooks’;

export default async () => {
render(, document.body);
};

function Extension() {
const [disabled, setDisabled] = useState(
shopify.cart.current.value.lineItems.length === 0,
);

useEffect(() => {
const unsubscribe = shopify.cart.current.subscribe((cart) => {
setDisabled(cart.lineItems.length === 0);
});
return unsubscribe;
}, );

return (
<s-tile
heading=“Trade Discount”
subheading=“Apply MSRP discount to cart”
onClick={() => shopify.action.presentModal()}
disabled={disabled}
/>
);
}

Hey @Olek_Pacheco-Dul - thanks for reaching out. Taking a quick look at your code there, I don’t see anything that immediately jumps out as incorrect. For the users who are affected, I know you mentioned that they should see products, but I’m wondering if it’s possible that somehow they didn’t have read_products access enabled on their staff/POS accounts? That could account for the intermittent-ness of the error.

If you’ve confirmed that though, no worries, just wanted to narrow things down. If that doesn’t resolve things, can you use these steps to send some logs our way from an affected device?

Open the Shopify POS app

  1. Tap

    (More)

  2. Tap

    Support

  3. Tap

    Report a bug

  4. Enter a description of what’s happening and include my username here in the description

  5. Tap

    Send

I can take a look on our end here once we have the logs to investigate further if needed - just ping me here if you do send those our way.