> ## Documentation Index
> Fetch the complete documentation index at: https://docs.subtotal.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Shopify Storefront API

> App-proxy endpoints for building custom Subtotal Link entrypoints from a Shopify storefront.

The Subtotal Connect Shopify app exposes a small set of JSON endpoints through Shopify's [app proxy](https://shopify.dev/docs/apps/build/online-store/display-dynamic-data). You can call them from any storefront page to build custom UI — for example, a "Link your Walmart account" button on a marketing page, a CTA inside a loyalty widget, or an integration with a third-party storefront framework — without using the [theme app block](/docs/subtotal-connect/shopify#theme-app-block) or [profile UI extension](/docs/subtotal-connect/shopify#profile-ui-extension).

<Note>
  If the theme app block or profile UI extension meets your needs, use those instead — they handle UI, state, and styling for you. The Storefront API is for cases where you need a custom entrypoint or a different layout.
</Note>

## Base path

All endpoints are mounted at `/apps/subtotal/*` on the merchant's storefront domain. Fetch them with a relative path:

```js theme={null}
fetch('/apps/subtotal/retailers')
```

## Authentication

You do **not** send an API key. Shopify's app proxy signs every request server-side, so calls only work when they originate from a page on the merchant's storefront.

When a customer is signed in, Shopify automatically appends `logged_in_customer_id` as a query parameter when proxying the request to the Subtotal Connect backend. Endpoints that operate on a specific customer (`/retailers`, `/connections`, `/disconnect-connection`, `/visibility`, `/link/{linkId}`) require the customer to be signed in. If they aren't, those endpoints return `400` or redirect to the Shopify customer login.

## Endpoints

| Method | Path                                   | Purpose                                                      |
| ------ | -------------------------------------- | ------------------------------------------------------------ |
| `GET`  | `/apps/subtotal/configuration`         | Check whether the shop has Subtotal Connect configured       |
| `GET`  | `/apps/subtotal/visibility`            | Check visibility rules for the current customer              |
| `GET`  | `/apps/subtotal/retailers`             | List retailers with the current customer's connection status |
| `POST` | `/apps/subtotal/connections`           | Create or reuse a connection, return a Subtotal Link URL     |
| `POST` | `/apps/subtotal/disconnect-connection` | Disconnect an existing connection                            |
| `GET`  | `/apps/subtotal/link/{linkId}`         | One-shot redirect into Subtotal Link for a single retailer   |

***

### `GET /apps/subtotal/configuration`

Returns `200` if the shop has configured Subtotal Connect (i.e. a Subtotal API key is stored for the shop). Returns `404` otherwise. Use this to feature-detect before rendering your UI.

**Response**

```json theme={null}
{ "status": "success" }
```

**Example**

```js theme={null}
async function isSubtotalConnectConfigured() {
  const response = await fetch('/apps/subtotal/configuration');
  return response.ok;
}
```

***

### `GET /apps/subtotal/visibility`

Returns the visibility decision for the current customer based on the rules the merchant has configured in the Shopify admin (global allow-list of emails/domains, plus a per-retailer allow-list). Use this if you want your custom entrypoint to respect the same gating as the theme app block.

**Response**

```json theme={null}
{
  "blockVisible": true,
  "restrictedRetailerIds": []
}
```

* `blockVisible` — `false` when the merchant's global visibility rules hide Subtotal Connect for this customer.
* `restrictedRetailerIds` — IDs the merchant has hidden from this specific customer. Filter these out of any retailer list you render.

**Example**

```js theme={null}
async function checkVisibility() {
  const response = await fetch('/apps/subtotal/visibility');
  if (!response.ok) return { blockVisible: true, restrictedRetailerIds: [] };
  return response.json();
}
```

***

### `GET /apps/subtotal/retailers`

Returns the list of retailers supported for the shop, with the current customer's connection (if any) attached to each retailer.

**Response**

```json theme={null}
{
  "retailers": [
    {
      "retailer_id": "walmart",
      "name": "Walmart",
      "link_url": "https://link.subtotal.com/zklQOnlG",
      "images": {
        "logo": { "light": "...", "dark": "..." },
        "icon": { "light": "...", "dark": "..." }
      },
      "connection": {
        "connection_id": "01K...",
        "retailer_id": "walmart",
        "status": "active"
      }
    }
  ]
}
```

`retailer_id` is a lowercase slug (e.g. `walmart`, `amazon`, `kroger`). The `link_url` path suffix is a short mixed-case alphanumeric `linkId` (e.g. `zklQOnlG`) used by [`/apps/subtotal/link/{linkId}`](#get-apps-subtotal-link-linkid).

`connection` is omitted when the customer has never linked the retailer. `status` is one of:

| Status              | Meaning                                                       | Suggested button text                                                                                   |
| ------------------- | ------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- |
| *(no `connection`)* | The customer has never linked this retailer                   | "Link Walmart"                                                                                          |
| `initialized`       | Connection created but not yet authenticated                  | "Link Walmart"                                                                                          |
| `disconnected`      | Customer disconnected the account                             | "Link Walmart"                                                                                          |
| `revoked`           | Retailer revoked access                                       | "Link Walmart"                                                                                          |
| `unauthenticated`   | Previously authenticated, needs the customer to sign in again | "Login to sync new purchases"                                                                           |
| `active`            | Authenticated and collecting purchases                        | "Disconnect", "Purchase on Walmart" (link to your brand's products on the retailer), or hide the button |

Treat `initialized`, `disconnected`, and `revoked` the same as "no connection" — show a fresh **Link** action that starts a new linking flow.

**Example**

```js theme={null}
async function getRetailers() {
  const response = await fetch('/apps/subtotal/retailers');
  if (!response.ok) throw new Error(`retailers fetch failed: ${response.status}`);
  const data = await response.json();
  return data.retailers;
}
```

***

### `POST /apps/subtotal/connections`

Creates (or reuses) a Subtotal connection for the current customer and returns a URL that opens Subtotal Link with the connection pre-bound. Redirect the customer to `link_url` to start the linking flow.

**Request body**

| Field             | Type   | Required | Description                                                                                        |
| ----------------- | ------ | -------- | -------------------------------------------------------------------------------------------------- |
| `retailerId`      | string | yes      | `retailer_id` from `/apps/subtotal/retailers`                                                      |
| `retailerLinkUrl` | string | yes      | `link_url` from the same retailer object                                                           |
| `redirectUrl`     | string | yes      | URL Subtotal Link should send the customer back to after they finish (e.g. `window.location.href`) |
| `connectionId`    | string | no       | Existing connection ID — pass this when reauthenticating an `unauthenticated` connection           |

**Response**

```json theme={null}
{
  "link_url": "https://link.subtotal.com/zklQOnlG?connection_id=01K...&redirect_url=https%3A%2F%2Fshop.example.com%2Faccount"
}
```

**Example** — a Connect button that opens Subtotal Link for a single retailer:

```js theme={null}
async function startLinkFlow(retailer) {
  const body = {
    retailerId: retailer.retailer_id,
    retailerLinkUrl: retailer.link_url,
    redirectUrl: window.location.href,
  };
  if (retailer.connection) {
    // Reauthenticate an existing connection
    body.connectionId = retailer.connection.connection_id;
  }

  const response = await fetch('/apps/subtotal/connections', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(body),
  });
  if (!response.ok) throw new Error(`failed to create connection: ${response.statusText}`);

  const { link_url } = await response.json();
  window.location.href = link_url;
}
```

***

### `POST /apps/subtotal/disconnect-connection`

Disconnects an existing connection. After this returns, the connection's status moves to `disconnected` and Subtotal stops collecting purchases for it.

**Request body**

| Field          | Type   | Required | Description                                     |
| -------------- | ------ | -------- | ----------------------------------------------- |
| `connectionId` | string | yes      | `connection_id` from `/apps/subtotal/retailers` |

**Response**

```json theme={null}
{ "success": true }
```

**Example**

```js theme={null}
async function disconnect(connectionId) {
  const response = await fetch('/apps/subtotal/disconnect-connection', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ connectionId }),
  });
  if (!response.ok) throw new Error(`failed to disconnect: ${response.statusText}`);
}
```

***

### `GET /apps/subtotal/link/{linkId}`

One-shot redirect endpoint. Resolves the retailer with the given `linkId` (the last path segment of a retailer's `link_url`), creates or finds the customer's connection, and `302`s into Subtotal Link. If the customer isn't signed in, they're redirected to Shopify's customer login first and bounced back here on success.

Useful for plain `<a href>` entrypoints where you don't want to run any client-side JavaScript.

**Query parameters**

| Param          | Required | Description                                                                    |
| -------------- | -------- | ------------------------------------------------------------------------------ |
| `redirect_url` | no       | Where to send the customer after they finish (defaults to the shop's homepage) |

**Example**

```html theme={null}
<a href="/apps/subtotal/link/zklQOnlG?redirect_url=https://shop.example.com/account">
  Link your Walmart account
</a>
```

## Putting it together

A minimal custom Connect button — feature-detect, fetch the customer's Walmart connection state, and render the right entrypoint for the current status. For linking and reauthenticating, [`/apps/subtotal/link/{linkId}`](#get-apps-subtotal-link-linkid) does the work in one step, so the button can be a plain `<a href>`. Only disconnect needs a JS click handler.

```js theme={null}
const configured = await fetch('/apps/subtotal/configuration').then(r => r.ok);
if (!configured) return;

const { retailers } = await fetch('/apps/subtotal/retailers').then(r => r.json());
const walmart = retailers.find(r => r.retailer_id === 'walmart');

const status = walmart.connection?.status;
const linkId = walmart.link_url.split('/').pop();
const redirectUrl = encodeURIComponent(window.location.href);
const slot = document.querySelector('#subtotal-entrypoint');

if (status === 'active') {
  // Already linked — render a Disconnect button
  const btn = document.createElement('button');
  btn.textContent = 'Disconnect Walmart';
  btn.addEventListener('click', async () => {
    await fetch('/apps/subtotal/disconnect-connection', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ connectionId: walmart.connection.connection_id }),
    });
    window.location.reload();
  });
  slot.appendChild(btn);
} else {
  // Not linked (or needs reauth) — a plain link to the convenience redirect
  // covers both cases: it creates a connection if needed, or reuses the
  // existing one for unauthenticated reauth.
  const a = document.createElement('a');
  a.href = `/apps/subtotal/link/${linkId}?redirect_url=${redirectUrl}`;
  a.textContent = status === 'unauthenticated'
    ? 'Login to sync new purchases'
    : 'Link Walmart';
  slot.appendChild(a);
}
```

If you don't need to inspect the customer's current connection status (for example, on a marketing page where you just want a "Link your Walmart account" CTA), skip the `/retailers` call entirely and hard-code the `linkId`:

```html theme={null}
<a href="/apps/subtotal/link/zklQOnlG?redirect_url=https://shop.example.com/account">
  Link your Walmart account
</a>
```
