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.
The Subtotal Connect Shopify app exposes a small set of JSON endpoints through Shopify’s app proxy. 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 or profile UI extension.
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.
Base path
All endpoints are mounted at /apps/subtotal/* on the merchant’s storefront domain. Fetch them with a relative path:
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
Example
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
{
"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
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
{
"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}.
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
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
{
"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:
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
Example
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 302s 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
<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} does the work in one step, so the button can be a plain <a href>. Only disconnect needs a JS click handler.
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:
<a href="/apps/subtotal/link/zklQOnlG?redirect_url=https://shop.example.com/account">
Link your Walmart account
</a>