Matrix Bundles

Connect an assistant to Matrix Bundles

Matrix Bundles has two ways in from outside the app: a connector an assistant can use, and a plain HTTP API. Claude and ChatGPT connect by signing in, and you approve them in your own Shopify admin. Anything else uses a key. This page is everything you need for either.

This is switched on.

The API and the assistant connector are switched on. Claude and ChatGPT connect by signing in, and the merchant approves them inside their own Shopify admin. Anything else uses a key the merchant makes on the app’s Access keys screen.

  • The addresses answer. Checked against the live app on 28 September 2026.
  • Keys can be made, by the merchant, on the Access keys screen in the app.
  • Claude and ChatGPT can sign in, with no key. Checked against the live app on 28 September 2026.

This page is only about the way in from outside. Installing the app, building a bundle and putting the builder on your storefront are in the manual.

What you can do through it

There are fifteen calls. Ten read, five change something. They reach one shop — the shop the key was made on, or the one you approved the assistant for — and nothing else.

Each call also has a plain title, in bold below, which an assistant can show you instead of its name.

They run the same code as the app’s own Save button. A change made this way is checked the same way, and a refusal comes back in the same words a merchant would see on the screen.

The ten that only read

  • Describe what this app can do, describe — What this app can do, including which kinds of saving this release can actually save.
  • List bundles, list_bundles — Every bundle on the shop, in the order checkout tries them.
  • Get one bundle, get_bundle — One bundle, with a plain-English sentence saying what it does, its place in the order bundles are tried in, and its page settings.
  • Check a bundle without saving it, validate_bundle — Check a bundle without saving it.
  • Try a basket at checkout, simulate_cart — Run a basket through the engine that decides what checkout does.
  • Check the store's setup, get_status — Whether bundling works at checkout on the shop, and what still needs doing: the Setup and status screen’s checks.
  • Find collections by name, find_collections — Find collections by name, with the id a step takes.
  • Find products for a free gift, find_variants — Find products for a free gift by name.
  • Get the builder's look and wording, get_style — How the builder looks.
  • Check wording without saving it, validate_strings — Check wording for the builder without saving it.

The five that change the shop

  • Save a bundle, save_bundle — Make a bundle or change one, its page settings included.
  • Set a bundle live or draft, set_bundle_status — Take a bundle live, or back to draft. On the connector only — over HTTP this is a change like any other.
  • Delete a bundle, delete_bundle — Delete a bundle.
  • Change the order bundles are tried in, reorder_bundles — Change the order bundles are tried in: send every bundle’s id once, first tried first. Move up and Move down on Your bundles do the same.
  • Change the builder's look and wording, save_style — Change how the builder looks.

These five need a key made with write access. A read key is refused, with: “This key can read but not change anything. Make a key with write access and use that instead.” A signed-in assistant needs “Also let it make changes” ticked when you connect it.

It reaches this app’s bundles and how they look, and reads the collections and products they are built from, and nothing else on your shop. The app does not read orders or customers and does not ask Shopify for permission to.

An example

Asked to put a bundle on its own page, show its steps one at a time and add a serum step, an assistant would:

  1. call get_status, and tell you in the app’s own words anything that stops bundling working at checkout;
  2. call get_bundle, and keep the config_version and page_version it gives back;
  3. call find_collections with “serum”, to get the collection’s id rather than guess it;
  4. call validate_bundle with the change — the steps, and page with page_handle “build-your-routine” and mode “guided” — to see what saving it would be refused for or warned about, without saving;
  5. call save_bundle with the same change and both stamps. Only this step needs changes allowed.

Connect Claude or ChatGPT

Claude and ChatGPT connect by signing in. You do not need a key. You give the assistant one address, and you approve it inside your own Shopify admin.

The address:

https://bundles.matrixhealthgroup.co.uk/mcp

Copy it exactly. The Access keys screen in the app shows the same address, under Assistants.

What happens

  1. In the assistant, add a custom connector with that address and press Connect. The steps for Claude and for ChatGPT are below.
  2. A Matrix Bundles page asks for your store. Type the name from your Shopify admin’s address — admin.shopify.com/store/your-store — and press Continue.
  3. Shopify opens the app’s Connect screen in your admin. Sign in to Shopify if it asks. Check the screen names the assistant and the store you meant, then press Connect.
  4. You are sent back to the assistant. Ask it to list your bundles.

Cancel on either page stops it, and nothing is connected.

What the assistant reads goes to the company that runs it — Anthropic for Claude, OpenAI for ChatGPT — under your own account with them. The privacy policy says what we keep about each connection.

Claude, and Claude Code

Claude connects through the connector, one address that speaks the Model Context Protocol. The fifteen calls arrive as fifteen tools. An assistant that may only read is offered the ten that read; the five that change the shop are not put in front of it.

In claude.ai, or the Claude desktop app:

  1. Open Customize, then Connectors, and choose Add custom connector.
  2. Name it Matrix Bundles and paste the address above. Leave any OAuth client ID and secret fields empty.
  3. Add it, press Connect, and follow the store and Connect steps above.
  4. In a new chat, ask Claude to list your bundles.

Tested in claude.ai on 28 September 2026: Claude found the sign-in, the Connect screen opened in the Shopify admin, and Claude listed the six read tools and answered from them.

Claude Code

Add it with one command. Then type /mcp inside Claude Code and choose matrix-bundles to sign in:

claude mcp add --transport http matrix-bundles \
  https://bundles.matrixhealthgroup.co.uk/mcp

The Connect screen then says: “This goes back to a program on your own computer. Only connect if you started it yourself.” Press Connect only if you ran the command yourself.

Signing in from Claude Code has not yet been tried against the live app. Using a key, below, has.

With a key instead

Paste this into your Claude Code configuration, with your own key in place of the example:

{
  "mcpServers": {
    "matrix-bundles": {
      "type": "http",
      "url": "https://bundles.matrixhealthgroup.co.uk/mcp",
      "headers": {
        "Authorization": "Bearer mxb_live_your-shop_YOUR-KEY"
      }
    }
  }
}

Or add it in one command:

claude mcp add --transport http matrix-bundles \
  https://bundles.matrixhealthgroup.co.uk/mcp \
  --header "Authorization: Bearer mxb_live_your-shop_YOUR-KEY"

The key travels as a header on every call. Use it if you would rather not sign in, or for a tool that has nowhere to sign in. A key and a sign-in reach the same fifteen calls.

Once connected, ask it to describe the app before anything else. The connector says so itself when it starts: call describe first, send back the stamp you read before changing anything, and show the merchant the app’s own words when something is refused rather than rewriting them.

Tested with Claude Code: it connected first time, with every tool offered to a read-and-change key. If something here is wrong for your client, tell us and it will be corrected.

ChatGPT

ChatGPT connects the same way, by signing in. Adding your own connector needs developer mode, which depends on your ChatGPT plan and, at work, on your workspace’s settings.

  1. In ChatGPT, open Settings, then Security and login, and turn on Developer mode.
  2. Open Plugins and press the plus button. Give it a name, such as Matrix Bundles, and a short description.
  3. Under Connection, paste the address above. Choose OAuth if it asks how to sign in. Create it.
  4. Connect it, and follow the store and Connect steps above.
  5. Start a new chat, add Matrix Bundles from the tools menu, and ask it to list your bundles.

Not yet tried from a ChatGPT account. The sign-in is built for the way ChatGPT signs in, but nobody has connected ChatGPT to the live app yet. If it does not connect, email us the message you saw.

With a key instead

A custom GPT with an action still works: set the action to call the addresses below and to send your key as an API key in the Authorization header, in the form “Bearer ” followed by the key. Anything else that lets you set a header works the same way.

The app describes itself, without a key, at https://bundles.matrixhealthgroup.co.uk/api/v1/schema.json. That is a plain description of what the app does and what it will accept, and it lists every call with its method, its address and the fields it takes. It is not an OpenAPI file, so an action’s schema still has to be written by hand, from that list or the one on this page.

What you approve in Shopify

The Connect screen is part of the app, inside your own Shopify admin. Nobody can connect an assistant to your store without signing in to your admin.

  • Who is asking. The screen names the assistant, the website it came from and your store — for example, “Claude (claude.ai) wants to connect to your-store.myshopify.com.” If any of that is not what you expect, press Cancel.
  • What it can see. “It can see your bundles, and how the bundle builder looks and what it says.” That is all it can reach. It cannot see orders or customers.
  • Changes are a separate box. “Also let it make changes” is never ticked for you. Leave it empty and the assistant can only read. The box appears only if the assistant asked to make changes.
  • A program on your own computer. If the sign-in goes back to a program on your computer, such as Claude Code, the screen warns you first.
  • Who can press Connect. Anyone on your staff who can open Matrix Bundles in your admin — the same people who can make a key.

Connect sends you back to the assistant. Cancel sends you back too, and the assistant is told you said no.

Read only, or changes too

Read only is the default. An assistant connected to read only is offered the ten calls that read, and not the five that change your shop.

Ask it to change something and it cannot. If it tries anyway, the app refuses with: ‘This assistant was connected to read only. To let it make changes, connect it again and tick “Also let it make changes”.’

To let it make changes, connect it again from the assistant and tick “Also let it make changes” on the Connect screen. It can then create, edit, publish and delete bundles, change the order bundles are tried in, and change how the builder looks. Every change runs the same checks as the Save buttons in the app.

Each Connect makes a new connection, and the Access keys screen lists every one that is working. Disconnect the ones you no longer use.

Disconnect an assistant

Open Matrix Bundles in your Shopify admin and choose Access keys. Under Assistants, each connected assistant is listed with the website it came from, whether it can read only or read and change, the day it was connected and the day it was last used. Press Disconnect beside it.

It stops working on its very next call. To use it again, connect it again from the assistant.

Removing the connector inside Claude or ChatGPT might not end it at this end. To be sure an assistant can no longer reach your store, disconnect it here.

A connection also ends by itself 90 days after you approved it, and the assistant asks you to connect again. Uninstalling Matrix Bundles ends every connection and deletes them.

How many calls it can make

There is a limit on how fast calls can come in, so a runaway assistant or script cannot slow your store down. It covers keys and signed-in assistants alike.

  • Each assistant or key: 60 calls in quick succession, then one a second.
  • Each store: 120 calls in quick succession, then two a second, shared by all its assistants and keys.

A normal job, even a long one, stays inside that. Past it, a call is refused with a message saying how long to wait, like “Too many requests in a short time. Wait 5 seconds, then try again.” It works again after that wait.

Make a key

You need a key only for something that cannot sign in: the HTTP API, Perplexity, your own program, or Claude Code if you would rather not sign in. Claude and ChatGPT do not need one.

Open Matrix Bundles in your Shopify admin and choose Access keys, the item above Help in the app’s menu. Under Make a key, give it a Name if you want one, choose Permissions — Read only, or Read and change — and press Make a key. The key is shown once, in a box headed “Copy this key now”. Nobody else can make one for you and nobody here can read yours.

  • You choose what it can do. Read only, or read and change. A read key cannot change anything, whatever asks it to. If you only want an assistant to look, make a read key — that is the safe one.
  • You can name it. Up to 40 characters, for you, so you know which is which later. It is optional.
  • You see the whole key once. Copy it there and then. It is not stored anywhere and it cannot be shown again. If you lose it, revoke it and make another.
  • Ten at a time. A shop can hold ten live keys. At ten, revoke one you no longer use before making another.
  • Revoking is how you take one back. Press Revoke beside it on the Access keys screen and it stops working. A revoked key is turned away with exactly the same answer as a key that never existed. Revoked keys stay on the screen, under Revoked, with the day each one was turned off. The app keeps thirty keys in all, live and revoked together; past thirty the oldest revoked ones drop off.
  • Last used is a date, not a time. It is written at most once a day, so it tells you a key is in use, not when the last call was.

What a key looks like

mxb_live_your-shop_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Your shop’s address is part of the key, in plain sight and on purpose: it means a request carrying nothing but the key already says which shop it is for.

Treat it like a password. Anyone holding a key with write access can change your bundles, and your bundles set prices. Never put a key in a web page, in a theme, or in anything a shopper’s browser loads. It belongs on a machine you control.

Nobody at this end can read your key. What the app keeps is a one-way scramble of it, so even a copy of the app’s own records cannot be turned back into a working key.

Perplexity, and anything else

The HTTP API. One header, ordinary JSON, nothing unusual. Here is a read:

curl https://bundles.matrixhealthgroup.co.uk/api/v1/bundles \
  -H "Authorization: Bearer mxb_live_your-shop_YOUR-KEY"

That answers with the shop’s bundles and a stamp called config_version. Keep the stamp: every change has to carry the one you last read, which is what stops two people undoing each other’s work.

And here is a change — taking one bundle to 15% off:

curl -X PATCH https://bundles.matrixhealthgroup.co.uk/api/v1/bundles/cream-trio \
  -H "Authorization: Bearer mxb_live_your-shop_YOUR-KEY" \
  -H "Content-Type: application/json" \
  -d '{
        "pricing": { "type": "percentage", "value": 15 },
        "config_version": "THE-STAMP-YOU-JUST-READ"
      }'

Anything you leave out stays as it is. You do not have to send a whole bundle back to change one thing, and a program that does not know about a setting cannot wipe it.

Your own program

The same HTTP API. There is no library to install and nothing to sign. Read first, keep the stamp, send it back with your change.

const res = await fetch(
  "https://bundles.matrixhealthgroup.co.uk/api/v1/bundles",
  { headers: { Authorization: "Bearer " + key } }
);
const { bundles, config_version } = await res.json();

If you would rather speak the connector protocol directly, it is one address and one message per request:

curl -X POST https://bundles.matrixhealthgroup.co.uk/mcp \
  -H "Authorization: Bearer mxb_live_your-shop_YOUR-KEY" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'

It speaks three revisions of the protocol: 2025-06-18 and 2025-11-25, which open with initialize, and 2026-07-28, which does not. Ask initialize for any other revision and it answers 2025-11-25. Send one message per request — a list of them is refused.

The addresses

Everything hangs off https://bundles.matrixhealthgroup.co.uk.

GET /api/v1/schema
What this app can do, and the list of calls it takes. Ask for this first.
GET /api/v1/bundles
Every bundle on the shop, in the order checkout tries them, with the stamp you need to send back when you change one.
POST /api/v1/bundles
Make a new bundle. This address only ever makes one — it can never change an existing bundle. Leave the id out: a body that names a bundle already on the shop is refused with 409 and told to use PATCH, and any other id with 422. It answers 201, and a location header carrying the new bundle’s address.
POST /api/v1/bundles/validate
What saving this bundle would be refused for, and warned about, without saving it.
POST /api/v1/bundles/simulate
Put a basket through the same engine that decides what happens at checkout.
POST /api/v1/bundles/order
Change the order bundles are tried in: every bundle’s id once, first tried first, with the stamp you read. Move up and Move down on Your bundles do the same.
GET /api/v1/bundles/{id}
One bundle, with its page settings and the page_version a change to them must carry.
PATCH /api/v1/bundles/{id}
Change that bundle, its page settings included. Sending {"status": "live"} is how it goes live.
DELETE /api/v1/bundles/{id}
Delete that bundle.
GET /api/v1/style
How the builder looks on the shop.
PATCH /api/v1/style
Change how it looks, and what it says. Wording gets the same checks as the What customers read screen.
POST /api/v1/style/validate
What saving this wording would be refused for, and warned about, without saving it.
GET /api/v1/status
The checks the app’s Setup and status screen makes: whether bundling works at checkout, how full the rules storage is, live bundles pointing at a collection that is gone, and live bundles with no page. It changes nothing.
GET /api/v1/collections?query=
Find collections by name, with the id a step or a left-out line takes. limit is optional: 20 unless you send one, at most 40.
GET /api/v1/variants?query=
Find products for a free gift by name, as the app’s gift picker does: no draft or archived products. limit is optional and counts products.
POST /mcp
The connector, for an assistant. One address for all fifteen calls. A GET here answers 405.

The method matters as much as the path. POST /api/v1/bundles only makes a bundle; PATCH /api/v1/bundles/{id} changes one. There is no address for taking a bundle live over HTTP — send {"status": "live"} to the bundle itself.

Every address is written out with a worked example, the answers and the refusals on the page for developers.

Three addresses need no key and are meant to be read by anything: /llms.txt, a short description of the app in plain text; /llms-full.txt, the long one, with every styling control and every limit in it; and /api/v1/schema.json, the same description as JSON. All three are about the app. None of them reaches a shop.

The sign-in has addresses of its own, which Claude and ChatGPT find by themselves: /.well-known/oauth-protected-resource and /.well-known/oauth-authorization-server, which describe it, and /oauth/authorize, /oauth/token and /oauth/revoke. It takes PKCE with S256, identifies Claude, Claude Code and ChatGPT by their published client documents, and has no open registration. You never need to call these yourself.

The manual’s words and the API’s names

The manual and the app’s screens use the words a merchant sees. The API uses shorter names for the same things:

Bundle name
title — The name on the single cart line.
Status
status — draft or live.
Items in all
min_items — How many products make the finished bundle.
The steps
slots — A list, one entry per step. The next three are inside each entry.
At least, on a step
min — The fewest the shopper must take from that step. 0 makes it optional.
The words above a step
title — Inside a step, title is the step’s heading, not the bundle’s name.
What a step matches
match — mode is collection or tag, and value is the collection’s id or the tag.
How they save
pricing — A percentage, or tiers that grow as the bundle grows.
Leave some products out
exclude — A list of collections and tags that never count towards the bundle.
Free gift
gifts — A list, each with variant_id and qty.
Order tried, and Move up and Move down, on Your bundles
priority — A bundle’s place in the order bundles are tried in: 1 is tried first, and a draft keeps its place but is skipped. Every bundle read carries it, and a bundle change cannot move it. reorder_bundles sets the whole order.
Steps on the bundle page, Bundle page, Appearance
page — The bundle’s page settings, new builder only: its page (page_handle), how the steps are shown (mode), each step’s options (stages), its own look, and its wording in every language. A change to them carries page_version as well as config_version.
The hidden product
parent_variant_id — The app makes it and sets this. You can read it; a request cannot change it.
How it looks
/api/v1/style — The style, read and changed on its own address, or with get_style and save_style. A change can also carry exact_money: true for exact prices, the default, or false for estimates. That changes the live shop straight away.

Some things can be changed only on the app’s own screens, not through the API. The main ones are a bundle’s cart-line picture and its description; discount rules, and the most off any one item; applying a look to every bundle, saved presets, restoring the previous style, and switching between the old and new bundle builder; and access keys and connected assistants. The rest are every setting of the old bundle builder, changing the plan, turning discount rules back on after a plan change, the list of other discounts that also apply to a bundle, and re-registering the checkout and refreshing what the storefront reads, which opening the app does on its own. The app lists all nine under admin_only — in describe on the connector, and in /api/v1/schema.json and /llms-full.txt, which need no key — each with where it is in the app. A bundle’s page, its steps on the page, its wording and its own look are no longer on that list: they are page, above.

Every field, with its limits and the JSON for it, is on Configure bundles.

Rules that apply to every call

  • The key, or the sign-in, goes in a header. Authorization: Bearer followed by the key — or, for a signed-in assistant, by the pass it was given when you pressed Connect, which it sends by itself. There is no other way in — not in the web address, not in a cookie, not in a header of your own naming.
  • There is a speed limit. 60 calls in quick succession per key or assistant, then one a second; 120 per store, then two a second. See How many calls it can make, above.
  • Read before you write. A change to a bundle carries the config_version you last read, and a change to its page settings carries that bundle’s page_version too. A change to how things look carries based_on, which works the same way. Without one the request is refused rather than guessed at. Over HTTP all three may travel in the web address instead of the body, which is how a delete carries one.
  • What you leave out is kept. One exception worth knowing: sending slots (the steps), exclude (the things left out) or gifts replaces that whole list rather than adding to it. Sending gifts as an empty list removes them.
  • A bundle called “new” is a trap. The app reads the id “new” as “make a new bundle”. A merchant who named a bundle New has one with that id, and it can be read through the API but only changed on the screen.
  • Which savings can be saved is decided by the app, not by you. Pricing is either a percentage — {"type": "percentage", "value": 10} — or tiers that grow as the bundle grows. Other kinds exist in the code and are not offered to anyone; sending one is refused by name, with the list of what can be saved. Ask describe rather than assuming.
  • 64 KB per request. Both ways in. Bigger than that is refused before it is read.
  • Not for a web page. The API deliberately sends nothing that would let a browser page call it from another site, because a key does not belong in a page a shopper can load.
  • Nothing about your shop is cached. Every answer carrying your shop’s own data says not to store it. The two keyless addresses are the opposite — they are about the app, and they are cacheable for an hour.
  • A key that is not accepted takes a moment. A request whose key is turned away is held for a quarter of a second before it answers, so nobody can learn from the timing which shops have the app or which keys are nearly right. Every other refusal answers as soon as it is decided.

When a call is refused

A refusal comes back in the app’s own words. Nothing on the way out is allowed to rewrite or tidy them, so what you read is what the merchant would have read on the screen. Show them that sentence rather than your own summary of it.

A refused call answers with the same shape every time:

{
  "error": {
    "code": "refused",
    "message": "every sentence, joined into one line for a log",
    "messages": [
      "the first thing wrong",
      "the second thing wrong"
    ]
  }
}

Read messages, not message. The joined line is for a log. The list is what to put in front of a person, one sentence at a time.

400
The request could not be read. For example: “Send a JSON object.” or “The body of this request is not JSON. Send a JSON object.”
401
“This request needs a working API key. Send it as `Authorization: Bearer <key>`, using a key made in the app that has not been revoked.” No key, a made-up key, a key for a shop the app cannot reach and a key revoked this morning all get exactly this, on purpose — an outsider is not told which of those it was. A signed-in assistant that was disconnected, or whose connection has run out, gets it too, and asks you to connect again.
403
“This key can read but not change anything. Make a key with write access and use that instead.”
403
‘This assistant was connected to read only. To let it make changes, connect it again and tick “Also let it make changes”.’ The same refusal, for a signed-in assistant. It is sent the way the protocol asks, so the assistant can offer to connect again with more.
404
“There is no bundle with that id on this store.” — or, for an address that does not exist, “There is nothing at that address.” followed by every method and path that does.
405
“That address does not answer POST. It answers GET.” — the address is right and the method is not.
409
“Somebody else changed this store’s bundles after you read them, so nothing was saved. Read them again and make your change again.” For a bundle’s page settings: “Somebody else changed this bundle’s page settings after you read them, so nothing was saved. Read the bundle again and make your change again.” Nothing was written. Read, redo your change, send it again.
413
“That request is too big. The most this API takes in one request is 64 KB.”
422
The request was understood and the app would not do it. This is where the app’s own refusals come back — the same sentences its Save button gives, such as “This app cannot save "fixed_price" pricing. It can save: percentage, tiered_percentage.”
429
“Too many requests in a short time. Wait 5 seconds, then try again.” — with the real number of seconds, which is also in the Retry-After header. Nothing was done. Wait, then send it again.
503
“We couldn’t read this store just now, so nothing was changed. Try again in a moment.”

The connector reports the same thing twice over: its own error number for what was asked, and the same status number the API would have given, so a program that knows one way in can read the other.

Support

Email support@matrixhealthgroup.co.uk and a person will answer. Tell us your shop address, what you called, and the exact sentence you got back.

Never send us a key. We cannot read yours and we do not want a copy. If you think one has got out, revoke it in the app and make another.

The support page and the manual cover the app itself.