Organization DepotDevelopers
All pages

Troubleshooting

The problems people run into most, and what to do. Your browser's developer tools (the Console and Network tabs) show nearly all of them.

TOKEN_INVALID (401)

The Storefront API refused the token: it's missing, mistyped, or revoked.

  • Copy the token again from Tokens on your shop's Headless page. It's odpk_ (public) or odpv_ (preview) followed by 32 letters and digits, with no spaces.
  • Check it hasn't been revoked. A revoked token stops working within 60 seconds.
  • Check you're calling the right API: a token from one Organization Depot environment (a local or test backend) isn't known to another. apiUrl / api-url should be https://api.organizationdepot.com/api for your live shop.
  • With your own fetch (the fetch option, or a proxy), make sure it passes the X-OD-Storefront-Token header through.

The web components explain a refused token in the console: "The Storefront API refused the token on <od-store> (TOKEN_INVALID)…". The elements show "This shop isn't available right now."

CORS errors

The Storefront API accepts requests from any origin, so a CORS error in the console almost always means the request didn't reach it, or carried something it doesn't accept:

  • The wrong apiUrl. It's https://api.organizationdepot.com/api, with /api at the end. Another address answers without the Storefront API's CORS headers, and the browser reports that as a CORS error.
  • Credentials. A custom fetch with credentials: "include" fails: the API never allows credentials. The script sends none.
  • Extra headers. Only Content-Type and X-OD-Storefront-Token are allowed; anything else your code (or a proxy) adds fails the preflight.
  • Something in between. An ad blocker, privacy extension, corporate proxy or captive portal can block or replace the response. Try a private window without extensions.
  • The scripts themselves. Their URLs answer with Access-Control-Allow-Origin: *. A CORS error loading components.js usually means a mistyped URL: a missing file is a real 404, without the CORS header.

In the Network tab, look at the failed request's status and response headers to see which it is.

429 RATE_LIMITED

Too many requests from one buyer (one token and client address): about 120 reads or 60 writes a minute. The error's retryAfter (the Retry-After header) says how many seconds to wait.

  • Look for a loop: code that re-reads the cart or products on a timer, on every scroll, or in a render loop. Listen to cart:change instead of polling the cart.
  • A page that loads products one by one (products.get() per card) should use one products.list() instead.
  • A page that reads every product to build its tag links should call tags.list() once instead.
  • On a server, all your visitors share the server's address: cache the catalog.
  • The web components show "Too many requests right now. Try again in 20 seconds." and recover by themselves.

The client doesn't retry a 429 for you.

Nothing renders

Web components

  • Load components.js with <script type="module" src="…">. Without type="module" nothing happens.
  • Open the page through a web server. Browsers don't run module scripts from file://.
  • Add <od-store token="odpk_…"> (or your odpv_… preview token while you develop) to every page that uses the elements. Without one, each element waits; after 3 seconds the console says "found no <od-store token="…"> on the page".
  • A Content-Security-Policy may be blocking the script, the API or the photos: the console names the directive. Add the three sources.
  • An unknown handle shows "This product isn't available." Check handle against the product's slug.
  • Browsers older than 2023 (Safari before 16.4, say) don't support what the elements use.

storefront.js

  • OrgDepot is not defined: load storefront.js with a plain <script src> (not type="module"), before the code that uses it. Code in a module script after it is fine.
  • await is only valid in async functions: top-level await needs a module script (<script type="module">) or an async function.
  • A blocked request shows as NETWORK_ERROR (status 0) with the browser's reason in cause: often a Content-Security-Policy connect-src missing https://api.organizationdepot.com.

The Storefront

Problems that come from the shop itself: its sale, its carts and its checkout.

NOT_FOUND on everything

Every call answering 404 NOT_FOUND (the console says "isn't published" for the web components) means the storefront isn't published. Publish it in Organization Depot. A single product being null from products.get() means that handle isn't a product for sale: check the slug, and that the product is active.

Photos don't load

Product photos come from https://api.organizationdepot.com. A Content-Security-Policy needs it in img-src.

The cart is empty after checkout

That's by design. Once the buyer pays, the cart is CONVERTED: it's an order now. The next cart.get() returns null, and the next addLine() starts a new cart. The web components do the same, including when the buyer comes back with the Back button.

If your page keeps the cart in its own state, update it from cart:change, which fires with null when the client finds the cart is over.

CART_CONVERTED

An edit (updateLine, removeLine, a discount code) hit a cart that was paid for, perhaps in another tab, or that expired. The client has already forgotten it. Show the empty cart; the buyer's next addLine() starts a new one. addLine(), setAnswers() and setContact() don't throw this: they start the new cart themselves.

CART_LOCKED

The cart is mid-checkout. The client releases it and retries once by itself, so you only see this error when the cart is locked again straight away. Ask the buyer to try again.

Checkout opens the wrong address

  • The platform address instead of your domain: your checkout domain isn't connected or isn't live yet. Check the domain wizard on the Storefront page. shop.checkoutDomain shows the host in use, and shop.url the full address checkout starts from.
  • "Continue shopping" or the confirmation goes somewhere else: set Your website on the Headless page. The checkout only ever links to that address.
  • Our storefront shows your products as well as your site: choose Checkout only on the Headless page.

INVALID (400)

  • field is code, quantity, lines[n].quantity, email or name: the buyer typed something the API refuses, and error.message tells them what to fix. Show it.
  • Any other field (first, after, lines, lines[n].variantId), or none: the page sent a request the API doesn't take, and error.message says what, for you. Check the call against the HTTP reference and show the buyer your own words.

CUSTOMIZATION_INVALID

A personalization answer doesn't fit the product's form. error.field is the field's key and error.message says what's wrong, in words for the buyer: show it on that field. The web components do this for you.

SALE_CLOSED

The shop's sale isn't open (shop.sale.availableForSale is false): browsing works, starting a cart doesn't. shop.sale.reason says why, and error.message is a sentence fit for the buyer:

reason Message What to check in Organization Depot
NOT_OPEN_YET "This shop isn't taking orders yet." The shop is still a draft, or the sale opens later
PAUSED "This shop isn't taking orders right now." The shop is paused
ENDED "This sale has ended." The sale's closing time has passed

Enable Add to cart only when shop.sale.availableForSale || shop.preview, so buyers don't hit this, and show "Sold out" only from a variant's availableForSale, which is about stock.

Building your site while the shop is closed? Use a preview token in your local settings: shop.preview is then true, and its carts work whatever the sale says and pay only in test mode. The web components and starters let Add to cart through for it.

Preview carts pay only with test cards

That's what they're for. A cart made with a preview token (odpv_…) has preview: true; the hosted checkout shows its real totals and takes only Stripe's test cards, places no order and moves no money. If buyers see "Preview cart" or "Test mode" on your live site, it's using a preview token: put a public token (odpk_…) in its settings instead. The stored cart is kept per token, so buyers then start on a new, real cart.