Skip to content

Powered by Grav CMS + Helios Theme

Run the store from an AI assistant

Connect an AI assistant to your store so it can answer questions and run the admin for you.

Note

This page is the admin side: an assistant working for you, with an API key and your permissions. To let shoppers' AI agents find your products and buy them, see Agentic commerce and the public Storefront MCP endpoint, which needs no key.

The Model Context Protocol (MCP) is how an AI client calls tools on an outside system. grav-mcp is the MCP server for Grav. It connects an assistant to your site over the REST API and picks up KahunaCart's tools automatically.

Before you begin

  • Grav 2.0 or later.
  • The Grav API plugin 1.0.22 or later, installed and enabled. That release added GET /mcp/tools.
  • grav-mcp 0.1.2 or later.
  • Node.js 18 or later on the machine running the assistant.
  • An API key on an account holding api.access plus the kahunacart.* permissions the assistant needs. See REST API to generate one and Permissions and safety to choose which.

How it works

grav-mcp fetches GET /mcp/tools at startup and registers one MCP tool per entry, named kahunacart_<name>: kahunacart_list_products, kahunacart_refund_order. Calling one calls the matching API route, under the same permissions and rules as clicking the button in the admin.

Piece Where it lives What it does
mcp.yaml KahunaCart's plugin root Describes each route as a tool: name, description, method, path, permission, input schema
GET /mcp/tools The API plugin Serves every enabled plugin's manifest, filtered to what the calling key may see
grav-mcp The machine running the assistant Registers one MCP tool per entry and calls the API on the assistant's behalf

Note

Every KahunaCart tool comes from the manifest, so an update that adds a route adds a tool. grav-mcp needs no change.

Connect Claude Code

  1. Open your Claude Code MCP config.
  2. Add a grav server:
JSON
{
  "mcpServers": {
    "grav": {
      "command": "npx",
      "args": ["-y", "grav-mcp"],
      "env": {
        "GRAV_API_URL": "https://mysite.com/api",
        "GRAV_API_KEY": "grav_your_api_key_here"
      }
    }
  }
}
  1. Restart Claude Code.

The assistant now has the core Grav tools and every KahunaCart tool your key can call.

GRAV_API_URL is your site plus /api, without the version prefix. GRAV_API_KEY is the key you generated, starting with grav_.

Warning

The key carries its owner's permissions. Do not commit it or paste it into a shared config file. grav-mcp reads it from the environment and never logs or returns it.

Other MCP clients

Any MCP client that launches a stdio server takes the same command and the same two variables: npx -y grav-mcp, with GRAV_API_URL and GRAV_API_KEY set. Claude Desktop, Cursor and VS Code each keep their MCP configuration somewhere different, so check that client's docs.

To test outside a client, run grav-mcp standalone:

BASH
GRAV_API_URL=https://mysite.com/api GRAV_API_KEY=grav_abc123 npx grav-mcp

The same settings are available as CLI flags: --url, --key, --environment, --plugin-tools, --transport and --port. For a remote deployment, use HTTP instead of stdio:

BASH
npx grav-mcp --url https://mysite.com/api --key grav_abc123 --transport http --port 3100

Check the connection

  1. Ask the assistant to run discover_plugins.
  2. Read the mcp_tools block in the answer. It lists the loaded tools by plugin, and kahunacart should be one of them.
  3. If your client can show its tool list, look there for names starting with kahunacart_ instead.

When kahunacart is missing, read grav-mcp's stderr. A plugin-tools problem never stops it: grav-mcp prints a warning naming the cause and starts with the core Grav tools only. Troubleshooting lists the causes and fixes.

Fix the cause, then ask the assistant to run refresh_plugin_tools. No restart is needed.

What the assistant can do

KahunaCart publishes 379 tools over 415 admin routes, behind 11 permissions. An assistant calls each one by its name below with kahunacart_ in front of it.

These 36 routes are not published as tools: GET /kahunacart/admin/sections/{id}.js, GET /kahunacart/admin/widget.js, POST /kahunacart/categories/{id}/image, GET /kahunacart/config/export, GET /kahunacart/orders/{id}/receipt.html, GET /kahunacart/address-lookup, GET /kahunacart/import/runs/{id}/errors.csv, POST /kahunacart/import/uploads, POST /kahunacart/notification-channels, DELETE /kahunacart/notification-channels/{id}, PATCH /kahunacart/notification-channels/{id}, POST /kahunacart/notification-channels/{id}/test, POST /kahunacart/object-templates/preview, GET /kahunacart/previews, GET /kahunacart/previews/email/{kind}, GET /kahunacart/previews/pdf/{document}, POST /kahunacart/orders/{id}/downloads, GET /kahunacart/badge, POST /kahunacart/orders/bulk/pdf, GET /kahunacart/orders/{id}/credit-notes/{cnid}.pdf, GET /kahunacart/orders/{id}/packing-slip.pdf, GET /kahunacart/orders/{id}/refund-request/label.pdf, GET /kahunacart/orders/{id}/shipments/{sid}/packing-slip.pdf, POST /kahunacart/products/{id}/files, POST /kahunacart/products/{id}/media, POST /kahunacart/products/{id}/releases/publish, GET /kahunacart/reports/export, GET /kahunacart/reports/export.pdf, GET /kahunacart/export/catalog.yaml, GET /kahunacart/export/customers.csv, GET /kahunacart/export/products.csv, GET /kahunacart/exports/{id}/download, POST /kahunacart/restores/uploads, GET /kahunacart/tax/rates/export, POST /kahunacart/webhook-subscriptions/{id}/ping, GET /kahunacart/zones/widgets/{id}.js.

Your key's permissions decide which of these exist in a session. The routes with no tool are the multipart uploads, the CSV and PDF exports, the two admin script routes and the sidebar badge the admin shell polls for itself. Manifest version 1 does not cover them.

The Arguments column names the properties of each tool's input schema.

Tool Route Permission Arguments What it does
get_config GET /kahunacart/config kahunacart.orders.view none Read the store-level facts every other call is interpreted against: the currency and how many decimal places it has, the storefront route, the site languages, the running plugin version, the registered payment providers and what each declares it can do, background job health, and whether the enabled payment methods can actually sell the published catalog
get_store_status GET /kahunacart/store/status kahunacart.orders.view none Read whether the store is open, coming soon or paused, and the message shoppers see while it is not open
set_store_status POST /kahunacart/store/status kahunacart.settings status required; message optional Set the store to open, coming_soon or paused
list_admin_sections GET /kahunacart/admin/sections kahunacart.orders.view none List the extra KahunaCart admin screens contributed by other plugins, each with its id, label, icon and sort position
list_products GET /kahunacart/products kahunacart.products.manage q, sort, dir, featured, missing_price, cf, page, per_page List catalog products, newest added first, with their variants, option axes, category ids, tags, attribute values, primary image and per-language translation overrides
get_product GET /kahunacart/products/{id} kahunacart.products.manage id, required One product with everything on it: variants and their option combinations, option axes, category ids, tags, the attribute value map, the full image gallery, translation overrides, plus its ten most recent completed orders and the total count of them
create_product POST /kahunacart/products kahunacart.products.manage title required; slug, type_code, type, type_fields, kit_items, sell_as_set, lock_parts, option_prices, variant_attributes, min_qty, max_qty, qty_step, combine_variants, sold_individually, exclude_order_rules, exclude_category_rules, pay_what_you_want, pwyw_suggested_minor, pwyw_max_minor, currency_amounts, buy_now, button_label, badge_text, badge_tone, release_at, brand, item_condition, final_sale, agent_checkout, google_category, manufacturer_id, safety_info, status, visibility, summary, description, tax_class, shipping_class, position, featured, variant, variants, category_ids, customer_group_ids, tags, attributes, translations optional Create a product and at least one variant, and return the created product in the same form get_product does
update_product PATCH /kahunacart/products/{id} kahunacart.products.manage id required; title, slug, type_code, type, type_fields, kit_items, sell_as_set, lock_parts, option_prices, variant_attributes, min_qty, max_qty, qty_step, combine_variants, sold_individually, exclude_order_rules, exclude_category_rules, pay_what_you_want, pwyw_suggested_minor, pwyw_max_minor, currency_amounts, buy_now, button_label, badge_text, badge_tone, release_at, brand, item_condition, final_sale, agent_checkout, google_category, manufacturer_id, safety_info, status, visibility, summary, description, tax_class, shipping_class, position, featured, variants, category_ids, customer_group_ids, tags, attributes, translations optional Change one or more fields of a product and return the updated product
reorder_products POST /kahunacart/products/reorder kahunacart.products.manage ids, required Put the catalog in the order the ids are given, and answer with how many rows moved
delete_product DELETE /kahunacart/products/{id} kahunacart.products.manage id, required Delete a product, its variants, its option axes, its attribute values, its releases and file rows, and its images including the bytes on disk
update_variant PATCH /kahunacart/products/{id}/variants/{variantId} kahunacart.products.manage id, variantId required; price_minor, price, currency_amounts, sku, position, is_default, min_qty, max_qty, qty_step, bundle_of, gtin, mpn, backorder_note, is_digital optional Change one variant of a product and return it with the rest of the product's variants beside it
search_variants GET /kahunacart/variants/search kahunacart.products.manage q, limit, ids Find variants by product title, variant title or SKU, across every product whatever its status, for choosing the parts of a bundle
delete_variant DELETE /kahunacart/products/{id}/variants/{variantId} kahunacart.products.manage id, variantId, all required Remove one variant of a product and return the variants that remain
delete_variants POST /kahunacart/products/{id}/variants/delete kahunacart.products.manage id, ids, all required Remove several variants of one product in one call, under the same rules as delete_variant, and return the variants that remain
list_product_options GET /kahunacart/products/{id}/options kahunacart.products.manage id, required The product's option axes with their values, its variants with the option-value combination each of them answers, and the widget names an axis can be rendered with
create_product_option POST /kahunacart/products/{id}/options kahunacart.products.manage id, name required; kind, required, widget, values, translations optional Add an option axis to a product, for example Size or Colour, optionally with its values in the same call, and return the product's axes and variants
update_product_option PATCH /kahunacart/products/{id}/options/{optionId} kahunacart.products.manage id, optionId required; name, widget, position, translations optional Rename an axis, change how it is rendered, move it among the product's other axes, or replace its translation overrides
delete_product_option DELETE /kahunacart/products/{id}/options/{optionId} kahunacart.products.manage id, optionId, all required Remove an axis from a product and return the axes and variants that remain
create_option_value POST /kahunacart/products/{id}/options/{optionId}/values kahunacart.products.manage id, optionId, label required; color, image_id, translations optional Add one value to an option axis, for example Large on Size, and return the product's axes and variants
update_option_value PATCH /kahunacart/products/{id}/options/{optionId}/values/{valueId} kahunacart.products.manage id, optionId, valueId required; label, color, image_id, position, translations optional Relabel a value, change its swatch colour, move it among the axis's other values, or replace its translation overrides
delete_option_value DELETE /kahunacart/products/{id}/options/{optionId}/values/{valueId} kahunacart.products.manage id, optionId, valueId, all required Remove one value from an axis and return the product's axes and variants
generate_variants POST /kahunacart/products/{id}/options/generate kahunacart.products.manage id required; apply optional Work out which combinations of the product's option axes have no variant yet and report the plan: how many combinations there are in total, how many already exist, how many are missing and their labels, plus the orphan variants whose combination the axes no longer describe and the unassigned ones that answer no combination at all
assign_variant_options POST /kahunacart/products/{id}/options/assign/{variantId} kahunacart.products.manage id, variantId, option_value_ids, all required Point one existing variant at one combination of option values, which is how a variant that predates the axes is adopted into the matrix instead of being deleted and retyped
list_product_files GET /kahunacart/products/{id}/files kahunacart.products.manage id, required The downloadable files attached to a product, with their file name, the title and description customers read (null when unset, and the file name shows instead), size, download limit, expiry, position and the release each belongs to, and variant_id when it goes only to one variant's buyers
update_product_file PATCH /kahunacart/products/{id}/files/{fileId} kahunacart.products.manage id, fileId required; name, title, description, download_limit, expiry_days, position, variant_id optional Edit a downloadable's metadata: what the customer's browser saves it as, the title and description customers read for it, how many times it may be downloaded, how long the link lives, where it sits in the list, and which variant's buyers get it (variant_id; null gives it to every buyer of the product, a physical product's included)
delete_product_file DELETE /kahunacart/products/{id}/files/{fileId} kahunacart.products.manage id, fileId, all required Detach a downloadable from its product and delete the bytes on disk
list_product_media GET /kahunacart/products/{id}/media kahunacart.products.manage id required; role, kind optional Everything attached to a product, gallery first and then the samples
list_product_images GET /kahunacart/products/{id}/images kahunacart.products.manage id, required The compatibility view of the above: a product's gallery pictures alone, each as id, stored path, site-relative url, alt text and position
create_product_embed POST /kahunacart/products/{id}/media/embeds kahunacart.products.manage id, embed_url required; alt, role, poster_media_id optional Attach a third-party video by URL
update_product_media PATCH /kahunacart/products/{id}/media/{mediaId} kahunacart.products.manage id, mediaId required; alt, position, duration_seconds, poster_media_id, embed_url optional Change a row's alt text, its place in its list, its recorded duration, its poster, or an embed's URL
reorder_product_media POST /kahunacart/products/{id}/media/reorder kahunacart.products.manage id, ids required; role optional Put a product's gallery, or its samples, in the order of the ids given, in one transaction
delete_product_media DELETE /kahunacart/products/{id}/media/{mediaId} kahunacart.products.manage id, mediaId, all required Detach a row from its product and delete the bytes on disk, where there are any
list_product_labels GET /kahunacart/products/{id}/labels kahunacart.products.manage id, required A product's labels in display order: EU energy labels and image labels such as a warranty badge
create_product_label POST /kahunacart/products/{id}/labels kahunacart.products.manage id, kind, image_media_id required; energy_class, energy_scale, title, variant_id, position, thumb_media_id, document_media_id optional Add an EU energy label or an image label (a warranty badge, a certification)
update_product_label PATCH /kahunacart/products/{id}/labels/{labelId} kahunacart.products.manage id, labelId required; energy_class, energy_scale, title, variant_id, position, image_media_id, thumb_media_id, document_media_id optional Change a label's energy class and scale, title, variant, position or the files it names
delete_product_label DELETE /kahunacart/products/{id}/labels/{labelId} kahunacart.products.manage id, labelId, all required Remove a label from its product, and delete its files from disk unless another label uses them
list_releases GET /kahunacart/products/{id}/releases kahunacart.products.manage id, required A product's versioned releases, newest first, each with its version, release date, Markdown changelog, published flag and the files attached to it, plus the evergreen files that belong to no release and are therefore offered to every buyer
create_release POST /kahunacart/products/{id}/releases kahunacart.products.manage id, version required; released_at, changelog, published optional Cut a new version of a product
update_release PATCH /kahunacart/products/{id}/releases/{releaseId} kahunacart.products.manage id, releaseId required; version, released_at, changelog, published optional Edit a release's version, date, notes or published flag
delete_release DELETE /kahunacart/products/{id}/releases/{releaseId} kahunacart.products.manage id, releaseId, all required Delete a release and the rows of the files on it
attach_release_file POST /kahunacart/products/{id}/releases/{releaseId}/files/{fileId} kahunacart.products.manage id, releaseId, fileId, all required Move a file already attached to the product into this release, which is how a build uploaded before the version was cut gets joined to it
detach_release_file DELETE /kahunacart/products/{id}/releases/{releaseId}/files/{fileId} kahunacart.products.manage id, releaseId, fileId, all required Take a file back out of its release, making it evergreen
list_categories GET /kahunacart/categories kahunacart.settings none The whole catalog tree as a flat list, each row carrying its parent_id, position, display mode, picture, translation overrides, the number of products filed directly under it, and the number a shopper finds when they click it including everything in its subtree
create_category POST /kahunacart/categories kahunacart.settings title required; slug, description, parent_id, position, display, image_alt, min_qty, max_qty, qty_step, translations optional Add a category to the tree
update_category PATCH /kahunacart/categories/{id} kahunacart.settings id required; title, slug, description, parent_id, position, display, image_alt, min_qty, max_qty, qty_step, translations optional Change a category's fields
delete_category DELETE /kahunacart/categories/{id} kahunacart.settings id, required Delete a category and its picture
move_category POST /kahunacart/categories/{id}/move kahunacart.settings id required; parent_id, position optional Give a category a new parent, a new place among its siblings, or both
reorder_category POST /kahunacart/categories/{id}/reorder kahunacart.settings id required; direction optional Swap a category with the sibling above or below it
delete_category_image DELETE /kahunacart/categories/{id}/image kahunacart.settings id, required Remove a category's picture, the row and the bytes, and return the category
list_tags GET /kahunacart/tags kahunacart.settings none Every catalog tag with its label, slug, translation overrides and how many products wear it
create_tag POST /kahunacart/tags kahunacart.settings label required; slug, translations optional Create a tag
update_tag PATCH /kahunacart/tags/{id} kahunacart.settings id required; label, slug, translations optional Rename a tag, change its slug, or replace its translation overrides
delete_tag DELETE /kahunacart/tags/{id} kahunacart.settings id, required Delete a tag and every assignment of it, and report how many products were carrying it
list_manufacturers GET /kahunacart/manufacturers kahunacart.products.manage none Every manufacturer with its name, trade name, address, email, website, EU responsible person, default safety text with its translations, and how many products name it as their own, plus default_manufacturer_id, the store default a product without its own falls back to
create_manufacturer POST /kahunacart/manufacturers kahunacart.products.manage name required; trade_name, address, email, url, rp_name, rp_address, rp_email, rp_url, safety_info, translations optional Create a manufacturer
update_manufacturer PATCH /kahunacart/manufacturers/{id} kahunacart.products.manage id required; name, trade_name, address, email, url, rp_name, rp_address, rp_email, rp_url, safety_info, translations optional Change one or more fields of a manufacturer
delete_manufacturer DELETE /kahunacart/manufacturers/{id} kahunacart.products.manage id, required Delete a manufacturer and take it off every product that named it, and report how many there were (products_affected) and whether it was the store default (was_default)
list_attributes GET /kahunacart/attributes kahunacart.products.manage none Every attribute definition this store has, in display order, with its slug, label, type, option list, unit, group heading, filterable, filter_panel and show_on_product flags, translation overrides and how many products answer it, plus the vocabulary lists a create form needs: every type, the types that may be filtered on, and the two types that carry options
get_attribute GET /kahunacart/attributes/{id} kahunacart.products.manage id, required One attribute definition with its option list, translation overrides and product_count
create_attribute POST /kahunacart/attributes kahunacart.products.manage label, type required; slug, options, unit, group_label, position, filterable, filter_panel, show_on_product, translations optional Define a store-wide attribute products can answer
update_attribute PATCH /kahunacart/attributes/{id} kahunacart.products.manage id required; label, slug, type, options, unit, group_label, position, filterable, filter_panel, show_on_product, translations optional Change an attribute definition
reorder_attributes POST /kahunacart/attributes/reorder kahunacart.products.manage ids, required Put the whole attribute list in the order the ids are given, and answer with how many moved and the reordered list
delete_attribute DELETE /kahunacart/attributes/{id} kahunacart.products.manage id required; force optional Delete an attribute definition, every value of it and its translations, and report how many products were carrying a value
list_tax_zones GET /kahunacart/tax/zones kahunacart.settings none Every tax zone with its country, region and postcode criteria decoded, its priority, and the rates hanging off it
create_tax_zone POST /kahunacart/tax/zones kahunacart.settings name required; countries, regions, postcodes, priority, rates optional Create a tax zone and its rates in one call
update_tax_zone PATCH /kahunacart/tax/zones/{id} kahunacart.settings id required; name, countries, regions, postcodes, priority, rates optional Change a zone's fields
delete_tax_zone DELETE /kahunacart/tax/zones/{id} kahunacart.settings id, required Delete a tax zone and its rates
import_eu_vat_presets POST /kahunacart/tax/presets/eu-vat kahunacart.settings none Create a tax zone and rate for each EU member state at its standard VAT rate, and report how many were actually added
list_tax_drivers GET /kahunacart/tax/engines kahunacart.settings none Every tax engine an add-on registered, the store-level default engine code, whether the built-in zone and rate editors still govern anything, and the registered tax-id validators
bind_tax_driver PUT /kahunacart/tax/engines/bind kahunacart.settings zone_id required; engine optional Attach a registered tax engine to one tax zone, so a store can quote a connector for one jurisdiction and its own rates for another from the same install
list_price_preferences GET /kahunacart/tax/price-preferences kahunacart.settings none Whether prices contain tax, per region and per currency, plus the published resolution order and the store-wide tax.included at the bottom of it
create_price_preference POST /kahunacart/tax/price-preferences kahunacart.settings attribute, value, is_tax_inclusive required; currency optional Say that one region or one currency is priced inclusive or exclusive of tax
update_price_preference PUT /kahunacart/tax/price-preferences/{id} kahunacart.settings id required; attribute, value, is_tax_inclusive, currency optional Change a preference's scope or its answer
delete_price_preference DELETE /kahunacart/tax/price-preferences/{id} kahunacart.settings id, required Remove a preference
import_tax_rates_csv POST /kahunacart/tax/rates/import kahunacart.settings csv, required Import the 10-column rate table — country_code, state_code, postcode, city, rate, tax_rate_name, priority, compound, shipping, tax_class — as a csv string
list_shipping_zones GET /kahunacart/shipping/zones kahunacart.settings none Every shipping zone in the merchant's own order, with its country, region and postcode criteria decoded and the delivery methods it offers
create_shipping_zone POST /kahunacart/shipping/zones kahunacart.settings name required; countries, regions, postcodes, position, methods optional Create a shipping zone and the methods it offers
update_shipping_zone PATCH /kahunacart/shipping/zones/{id} kahunacart.settings id required; name, countries, regions, postcodes, position, methods optional Change a zone's fields
delete_shipping_zone DELETE /kahunacart/shipping/zones/{id} kahunacart.settings id, required Delete a shipping zone and the methods it offered
get_shipping_rate_rules GET /kahunacart/shipping/methods/{id}/rates kahunacart.settings id, required Every rate type on one method — base, per item, per kilogram, percentage, per shipping class — plus the minimum and maximum clamps, the cost expression, the three-valued shipping-class policy and the carrier the method is bound to
set_shipping_rate_rules PUT /kahunacart/shipping/methods/{id}/rates kahunacart.settings id required; per_item, per_kg, percent_rate, min_total_cost, max_total_cost, cost_expression, class_policy, class_policy_classes, class_costs_mode, carrier_code, carrier_service_code, currency_amounts optional Replace the rate rules on one method
validate_shipping_expression POST /kahunacart/shipping/expressions/validate kahunacart.settings expression, required Parse a cost expression without saving it and answer whether it is valid, with the offending token named when it is not
list_shipping_profiles GET /kahunacart/shipping/profiles kahunacart.settings none The profiles a store packs by, in assembly order, with how many products point at each
create_shipping_profile POST /kahunacart/shipping/profiles kahunacart.settings code required; name, position, ships optional Add a profile for a part of the catalogue that travels separately
update_shipping_profile PUT /kahunacart/shipping/profiles/{id} kahunacart.settings id required; code, name, position, ships optional Change a profile's code, name, position or whether it ships
delete_shipping_profile DELETE /kahunacart/shipping/profiles/{id} kahunacart.settings id, required Refused while any product still points at the profile, and refused outright for the built-in default and digital profiles, which are what an unassigned product falls back to
list_shipping_packages GET /kahunacart/orders/{id}/packages kahunacart.orders.view id, required The packages one order was priced as: which profile assembled each, which method priced it, what it cost, what it weighed, which lines went in it and any carrier metadata for the method that actually won
list_carrier_drivers GET /kahunacart/shipping/carriers kahunacart.settings none The live carrier rate drivers installed in this store, the shipping methods bound to them and whether each binding resolves
clear_carrier_rate_cache POST /kahunacart/shipping/carriers/cache/clear kahunacart.settings none Drop every stored carrier quote so the next checkout asks the carriers again
pickup_locations_list GET /kahunacart/pickup-locations kahunacart.products.manage none Every collection point in the merchant's own order, enabled or not, with its full address, opening hours, optional collection charge as a decimal string in the store currency, and the language key holding its collection instruction
pickup_location_create POST /kahunacart/pickup-locations kahunacart.products.manage code, name, country required; address_1, address_2, city, state, postcode, phone, hours, instructions_key, price, currency_amounts, tax_class, enabled, position optional Add a collection point
pickup_location_update PUT /kahunacart/pickup-locations/{id} kahunacart.products.manage id required; code, name, address_1, address_2, city, state, postcode, country, phone, hours, instructions_key, price, currency_amounts, tax_class, enabled, position optional Change a collection point's fields
pickup_location_delete DELETE /kahunacart/pickup-locations/{id} kahunacart.products.manage id, required Delete a collection point
list_coupons GET /kahunacart/coupons kahunacart.settings page, per_page, group_id, id, sort, dir, q Discount codes with their type, percentage or amount, minimum and maximum cart totals, schedule, usage limits, how many times each has been redeemed, and the restriction matrix: what each is scoped to, whether it pays the postage, whether it skips items already on sale, how many units it covers, which addresses it is reserved for, and its conditions as a nested conditions document (an empty all when it has none)
create_coupon POST /kahunacart/coupons kahunacart.settings code required; type, percent, amount, min_total, max_total, currency_amounts, starts_at, expires_at, usage_limit, usage_limit_per_user, enabled, individual_use, basis, product_ids, scopes, free_shipping, exclude_sale_items, limit_to_items, allowed_emails, conditions, grants_customer_group_id, utm_campaign optional Create a discount code
update_coupon PATCH /kahunacart/coupons/{id} kahunacart.settings id required; code, type, percent, amount, min_total, max_total, currency_amounts, starts_at, expires_at, usage_limit, usage_limit_per_user, enabled, individual_use, basis, product_ids, scopes, free_shipping, exclude_sale_items, limit_to_items, allowed_emails, conditions, grants_customer_group_id, utm_campaign optional Change a coupon's fields
delete_coupon DELETE /kahunacart/coupons/{id} kahunacart.settings id, required Delete a discount code
list_coupon_groups GET /kahunacart/coupon-groups kahunacart.settings page, per_page, sort, dir, q Campaigns of coupon codes
get_coupon_group GET /kahunacart/coupon-groups/{id} kahunacart.settings id required; page, per_page optional One campaign with its totals and a page of the codes under it
create_coupon_group POST /kahunacart/coupon-groups kahunacart.settings name required; prefix, pattern, enabled, usage_limit, usage_limit_per_user, total_usage_limit, used_codes_limit, template optional Create the header a batch of codes will be generated into
update_coupon_group PATCH /kahunacart/coupon-groups/{id} kahunacart.settings id required; name, prefix, pattern, enabled, usage_limit, usage_limit_per_user, total_usage_limit, used_codes_limit, template optional Change a campaign's fields
delete_coupon_group DELETE /kahunacart/coupon-groups/{id} kahunacart.settings id, required Delete the campaign header
generate_coupon_codes POST /kahunacart/coupons/generate kahunacart.settings group_id, count, all required Mint a batch of unique codes into a campaign
export_coupon_group GET /kahunacart/coupon-groups/{id}/export kahunacart.settings id, required Every code in a campaign as a CSV — code, whether it is enabled, how many times it has been redeemed, its own limit, and the day it was minted
list_rule_attributes GET /kahunacart/rules/vocabulary kahunacart.sales.manage none The published vocabulary a sale's conditions are written against: every attribute, its type, the operators it offers, whether it is a fact about the cart, one line, the customer or the order, and which picker the admin draws for it
list_rule_operations GET /kahunacart/rules/operations kahunacart.sales.manage none Every operation a sale rule can price through, with the arguments each takes
get_catalog_index GET /kahunacart/catalog/index kahunacart.products.manage none What the denormalised catalog index holds and whether the storefront is actually reading it
rebuild_catalog_index POST /kahunacart/catalog/index/rebuild kahunacart.products.manage full, max Rebuild the denormalised index every listing, facet count, sort and search reads
list_search_synonyms GET /kahunacart/search/synonyms kahunacart.products.manage none The merchant's own search vocabulary: each row is a word shoppers type and the words it should also find
set_search_synonyms PUT /kahunacart/search/synonyms kahunacart.products.manage synonyms, required Replace the whole synonym table with the rows you send
list_product_types GET /kahunacart/product-types none (public route) none Every kind of thing this store can sell: the four core types (physical, digital, grouped, external) plus any an installed add-on registers
list_product_relations GET /kahunacart/products/{id}/relations kahunacart.products.manage id, required List the products a merchant hand-picked to sit beside this one, in the order they picked them, split into the three kinds: related ("you might also like"), cross_sell ("goes well with this") and up_sell ("consider this instead")
set_product_relations PUT /kahunacart/products/{id}/relations kahunacart.products.manage id required; related, cross_sell, up_sell optional Replace one product's curated lists
rebuild_product_relations POST /kahunacart/products/relations/rebuild kahunacart.products.manage none Orphan every cached relation block in the store and delete relation rows pointing at products that no longer exist
list_bulk_fields GET /kahunacart/products/bulk/fields kahunacart.products.manage none List what a bulk edit can change, which modes and units each field accepts, the values the enumerated fields allow, and the store's admin.bulk.max_rows limit
list_price_lists GET /kahunacart/price-lists kahunacart.sales.manage none Every price list with its header and its rules
get_price_list GET /kahunacart/price-lists/{id} kahunacart.sales.manage id, required One price list with its rules in order and every price row it holds, variant by variant and rung by rung
create_price_list POST /kahunacart/price-lists kahunacart.sales.manage name required; currency, kind, priority, starts_at, expires_at, active, show_in_catalog, catalog_label, rules, prices optional Create a named set of prices
update_price_list PATCH /kahunacart/price-lists/{id} kahunacart.sales.manage id required; name, currency, kind, priority, starts_at, expires_at, active, show_in_catalog, catalog_label, rules, prices optional Change a price list's header, its rules, its prices, or any combination
delete_price_list DELETE /kahunacart/price-lists/{id} kahunacart.sales.manage id, required Delete a price list, its rules and every price row it holds
list_price_tiers GET /kahunacart/price-tiers kahunacart.sales.manage variant_id, sku, currency One variant's own quantity ladder — the "buy ten and save" breaks that belong to no price list and apply to every shopper
set_price_tiers PUT /kahunacart/price-tiers kahunacart.sales.manage tiers required; variant_id, sku, currency optional Replace one variant's quantity ladder
preview_bulk_update POST /kahunacart/products/bulk/preview kahunacart.products.manage product_ids, operations, all required Say what a bulk edit would do without doing any of it — how many products and variants it would touch, and the before and after of the first ten
bulk_update_products POST /kahunacart/products/bulk kahunacart.products.manage product_ids, operations, all required Change price, compare price, stock quantity, status, visibility, featured or the quantity limits across many products in one transaction, using Change to / Increase by / Decrease by with a fixed amount or a percentage
get_product_history GET /kahunacart/products/{id}/history kahunacart.products.manage id required; per_page optional Read what has happened to one product — every bulk edit and every import that changed it, newest first, each carrying the fields that changed with their before and after values in minor units and the person who made the change
list_import_fields GET /kahunacart/import/fields kahunacart.products.manage none List what the CSV importer can import — the five entities (categories, products, variants, customers, orders) in the order a whole-store migration has to run them, the fields each accepts with their types and the column headings they are usually exported under, which keys rows can be matched on, and which fields are required
list_import_mappings GET /kahunacart/import/mappings kahunacart.products.manage entity List the saved column mappings, each with the entity it describes, the key rows are matched on, what it does with rows that already exist, and the column-to-field map itself
create_import_mapping POST /kahunacart/import/mappings kahunacart.products.manage name, entity, match_key, columns required; on_existing optional Save a reusable column mapping so the same export can be imported again next month without describing the file twice
update_import_mapping PATCH /kahunacart/import/mappings/{id} kahunacart.products.manage id required; name, entity, match_key, on_existing, columns optional Change a saved mapping
delete_import_mapping DELETE /kahunacart/import/mappings/{id} kahunacart.products.manage id, required Delete a saved column mapping
dry_run_import POST /kahunacart/import/dry-run kahunacart.products.manage upload required; mapping_id, mapping, limit optional Rehearse an import of an already-uploaded file: the same writers, the same values, inside a transaction that is always rolled back
start_import POST /kahunacart/import/runs kahunacart.products.manage upload required; mapping_id, mapping, filename optional Queue an import of an already-uploaded file
list_import_runs GET /kahunacart/import/runs kahunacart.products.manage per_page List recent import runs, newest first, with their entity, status, progress and counts of rows created, updated, skipped and failed
get_import_run GET /kahunacart/import/runs/{id} kahunacart.products.manage id, required Read one import run's progress and its first fifty failed rows, each with the line number of the merchant's own file and the reason it failed
list_export_sets GET /kahunacart/export/sets kahunacart.export none List the parts of the store a bundle can carry (settings, customers, catalog, pictures, digital files, promotions, reviews, orders, store credit, and each add-on's own), grouped as the Export screen shows them, with how many rows each holds, the sets each depends on, the permission each needs, and whether this caller holds it
start_store_export POST /kahunacart/exports kahunacart.export everything, sets, media, files, passwords, secrets, passphrase, orders_from, orders_to Queue an export of the store into one bundle (a zip) on the server
get_store_export GET /kahunacart/exports/{id} kahunacart.export id, required Read one export's progress, and once it has finished, its size, its sets with their row and file counts, whether secrets were sealed in it, what was not included, and when the bundle will be deleted from the server
delete_store_export DELETE /kahunacart/exports/{id} kahunacart.export id, required Delete an export's bundle from the server
list_store_bundles GET /kahunacart/restores/bundles kahunacart.restore none List the bundles in user/data/kahunacart/exports, newest first: this store's exports, uploaded bundles, and bundles copied into the folder by hand
inspect_store_bundle GET /kahunacart/restores/bundles/{name} kahunacart.restore name, required Read what a bundle on the server holds before restoring it: where it came from, the KahunaCart and add-on versions that wrote it, each set with its counts and whether it can be restored here (and which add-on to install when not), whether it carries sealed secrets, and whether a full restore is allowed into this store and why not
delete_store_bundle DELETE /kahunacart/restores/bundles/{name} kahunacart.restore name, required Delete a bundle from user/data/kahunacart/exports by its file name
dry_run_store_restore POST /kahunacart/restores/dry-run kahunacart.restore bundle, mode required; sets, passphrase optional Queue a rehearsal of restoring a bundle: the whole restore inside a transaction that is always rolled back, so it writes nothing
start_store_restore POST /kahunacart/restores kahunacart.restore bundle, mode required; sets, passphrase optional Queue the restore of a bundle into this store
get_store_restore GET /kahunacart/restores/{id} kahunacart.restore id, required Read one restore or dry run: its progress, and once finished its report
list_store_transfers GET /kahunacart/transfers kahunacart.export kind, per_page The history of store exports and restores, newest first: who ran each, when, which sets, the mode, the bundle's size and how it went
list_order_statuses GET /kahunacart/order-statuses kahunacart.orders.view none Every order status this store has, grouped by the three axes an order carries — status (the cart lifecycle), payment_status and fulfillment_status — with a live count of how many orders stand in each
create_order_status POST /kahunacart/order-statuses kahunacart.orders.manage axis, code, flag required; label, colour, position, enabled, counts_as_paid, reduces_stock, restores_stock, is_final optional Add a status to one axis
update_order_status PATCH /kahunacart/order-statuses/{id} kahunacart.orders.manage id required; label, colour, position, flag, enabled, counts_as_paid, reduces_stock, restores_stock, is_final optional Rename, recolour, reposition, reflag or switch off one status
delete_order_status DELETE /kahunacart/order-statuses/{id} kahunacart.orders.manage id, required Remove a status a merchant added
reorder_order_statuses POST /kahunacart/order-statuses/reorder kahunacart.orders.manage axis, ids, all required Put one axis in an order, posted as the whole list of ids in the order you want
list_order_changes GET /kahunacart/orders/{id}/changes kahunacart.orders.view id, required Every change ever staged against an order, newest first, each with its typed actions, its state (staged, confirmed or discarded) and the versions it moved the order between
create_order_change POST /kahunacart/orders/{id}/changes kahunacart.orders.manage id required; change_type, reason, idempotency_key optional Open a change against a placed order
add_order_change_action POST /kahunacart/orders/{id}/changes/{cid}/actions kahunacart.orders.manage id, cid, action required; item_id, adjustment_id, variant_id, qty, received_qty, damaged_qty, unit_price_minor, amount_minor, title, label, type, basis optional Add one typed verb to a staged change and get back the previewed totals
confirm_order_change POST /kahunacart/orders/{id}/changes/{cid}/confirm kahunacart.orders.manage id, cid, all required Apply the change to a new version of the order, atomically
cancel_order_change POST /kahunacart/orders/{id}/changes/{cid}/cancel kahunacart.orders.manage id, cid, all required Abandon a staged change
get_order_version GET /kahunacart/orders/{id}/versions/{v} kahunacart.orders.view id, v, all required The lines and adjustments an order carried at a given version, exactly as they were written
unlock_order POST /kahunacart/orders/{id}/unlock kahunacart.orders.manage id required; reason optional Open an order whose status is marked final — cancelled, archived, or anything a merchant flagged that way — so a change can be staged against it
mark_test_order POST /kahunacart/orders/{id}/test kahunacart.orders.manage id required; test, reason optional Mark an order the merchant placed to try the store (a rehearsal, a comp, a card run through a live provider) as a test order, or with test false take the mark off
set_order_dispute POST /kahunacart/orders/{id}/dispute kahunacart.orders.manage id, outcome required; note optional Set an order's dispute to won, lost, or back to open, by hand
list_features GET /kahunacart/features kahunacart.orders.view none Core features that change how orders are stored, with whether each is on, which installed add-ons have declared they understand it, and which have not
set_feature POST /kahunacart/features/{code}/enable kahunacart.orders.manage code required; enabled optional Switch a feature on, or off with {"enabled": false}
list_credit_accounts GET /kahunacart/credit/accounts kahunacart.orders.view customer_id, sort, dir, q, page Every store-credit account with its balance, which is summed from the account's transactions on every read and stored in no column anywhere
get_credit_account GET /kahunacart/credit/accounts/{id} kahunacart.orders.view id, required One account with its balance, the customer it belongs to, and how many movements it carries
list_credit_transactions GET /kahunacart/credit/accounts/{id}/transactions kahunacart.orders.view id required; sort, dir, q, page optional One account's movements, newest first, each with the provenance the ledger required of it — what produced the row (gift_card, refund, order or adjustment), which one, the order it belongs to, and a source snapshot of what the producing rule saw
adjust_store_credit POST /kahunacart/credit/accounts/{id}/adjust kahunacart.orders.refund id, note required; amount, amount_minor optional Add credit to an account or take some back, by hand
refund_to_credit POST /kahunacart/credit/refunds kahunacart.orders.refund order_id required; amount, amount_minor, items, shipping_minor, restock, reason, notify, return_id optional Give a refund back as store credit instead of to the card that paid for the order
list_credit_notes GET /kahunacart/orders/{id}/credit-notes kahunacart.orders.view id, required The numbered documents an order's refunds have earned, oldest first
list_gift_cards GET /kahunacart/gift-cards kahunacart.orders.view status, sort, dir, q, page Every gift card with its value, its state and the last four characters of its code
create_gift_card POST /kahunacart/gift-cards kahunacart.orders.refund amount, amount_minor, currency, note Mint a card by hand — a goodwill gesture, a promotion, a card sold over the counter
redeem_gift_card POST /kahunacart/gift-cards/redeem kahunacart.orders.refund code, customer_id required; order_id optional Put a card's value into a customer's store credit on their behalf — the support-desk case, for somebody who cannot type the code in themselves
void_gift_card POST /kahunacart/gift-cards/{id}/void kahunacart.orders.refund id required; reason optional Cancel a card that has not been redeemed and take its value back off the ledger
list_customer_groups GET /kahunacart/customer-groups kahunacart.settings none List the store's customer groups with their codes, labels, flat discount percentages and how many customers are in each
create_customer_group POST /kahunacart/customer-groups kahunacart.settings label required; code, description, position, is_default, discount_percent, show_on_account optional Create a customer group
update_customer_group PATCH /kahunacart/customer-groups/{id} kahunacart.settings id required; code, label, description, position, is_default, discount_percent, show_on_account optional Change a customer group
delete_customer_group DELETE /kahunacart/customer-groups/{id} kahunacart.settings id, required Delete a customer group
set_customer_group POST /kahunacart/customers/{id}/group kahunacart.customers.manage id required; group_ids, group_id optional Set the groups one customer is in
list_customer_wishlists GET /kahunacart/customers/{id}/wishlists kahunacart.customers.view id, required List the saved lists one customer keeps, each with its name, its sharing setting and the products on it
list_product_stock_alerts GET /kahunacart/products/{id}/stock-alerts kahunacart.products.manage id required; status optional List the back-in-stock alerts on one product, newest first: each with the variant it is for, the shopper's email, whether it is pending (a guest who has not confirmed yet), active, sent, cancelled or expired, and when it was confirmed and sent
list_customer_stock_alerts GET /kahunacart/customers/{id}/stock-alerts kahunacart.customers.view id required; all optional List the back-in-stock alerts one customer holds, found by their account and by their email address, so an alert they left as a guest before signing up is included
cancel_stock_alert POST /kahunacart/stock-alerts/{id}/cancel kahunacart.customers.manage id, required Stop one back-in-stock alert, pending or active, so no email is sent for it
get_erasure_blockers GET /kahunacart/customers/{id}/erasure-blockers kahunacart.customers.manage id, required Say whether this customer can be erased and, when they cannot, name every reason — an order still awaiting payment or awaiting despatch, a download they can still fetch, and whatever add-ons report holding on their behalf such as an active subscription or an issued licence
erase_customer POST /kahunacart/customers/{id}/erase kahunacart.customers.manage id, required Anonymise a customer and everything that points at them: the email, the names and addresses frozen onto their orders, their saved address book, their login, their wishlists, and the identity behind their reviews
reassign_orders POST /kahunacart/customers/{id}/reassign-orders kahunacart.customers.manage id, to_customer_id required; dry_run optional Move every order from one customer to another, with its history, its notes, its refunds and its download grants
merge_customer POST /kahunacart/customers/{id}/merge kahunacart.customers.manage id, to_customer_id required; dry_run optional Merge a customer into another one, for somebody who bought under two addresses
get_retention_policy GET /kahunacart/retention kahunacart.customers.view none Read the store's data-retention windows — how long placed-but-unpaid orders are kept, how long failed ones are, how long before a completed order is anonymised, how long before an inactive customer is erased — and what each rule last did
run_retention POST /kahunacart/retention/run kahunacart.customers.manage dry_run, limit Run the retention sweep immediately instead of waiting for the daily worker tick
list_reviews GET /kahunacart/reviews kahunacart.products.manage state, product_id, page, per_page, sort, dir, q List product reviews for moderation, oldest first, optionally narrowed to one state or one product
approve_review POST /kahunacart/reviews/{id}/approve kahunacart.products.manage id, required Publish a review on its product page and count it in that product's star rating
reject_review POST /kahunacart/reviews/{id}/reject kahunacart.products.manage id, required Turn a review down
delete_review DELETE /kahunacart/reviews/{id} kahunacart.products.manage id, required Remove a review outright, leaving no record of it
rebuild_review_ratings POST /kahunacart/reviews/rebuild kahunacart.products.manage none Throw away every cached star aggregate in the store so the next request recomputes it
list_payment_methods GET /kahunacart/payment-methods kahunacart.settings none Every merchandised payment method this store offers, with its fee, its scoping conditions and the gateway it runs over
create_payment_method POST /kahunacart/payment-methods kahunacart.settings provider, code required; label_key, description_key, logo, instructions, enabled, position, fee_fixed_minor, currencies, currency_amounts, fee_percent, fee_tax_class, zero_total_only, config, conditions optional Add a checkout choice over an existing gateway
update_payment_method PATCH /kahunacart/payment-methods/{id} kahunacart.settings id required; provider, code, label_key, description_key, logo, instructions, enabled, position, fee_fixed_minor, currencies, currency_amounts, fee_percent, fee_tax_class, zero_total_only, config, conditions optional Change a method's merchandising, its fee, or its conditions
delete_payment_method DELETE /kahunacart/payment-methods/{id} kahunacart.settings id, required Remove a checkout choice and the conditions that scoped it
reorder_payment_methods POST /kahunacart/payment-methods/reorder kahunacart.settings ids, required Put the checkout list in the order given
bulk_order_status POST /kahunacart/orders/bulk/status kahunacart.orders.manage order_ids, status required; message, notify optional Move a selection of orders onto one status, as a single background job
get_bulk_run GET /kahunacart/orders/bulk/{jobId} kahunacart.orders.view jobId, required What a bulk status change did, order by order: which changed, which were already in that status, and which failed and why
export_orders POST /kahunacart/orders/bulk/export kahunacart.orders.view scope, format, column_set_id, columns, from, to, statuses, order_ids, filter Orders out in the columns you choose, as CSV text or as JSON rows
list_export_column_sets GET /kahunacart/export-column-sets kahunacart.orders.view none The saved column choices, plus the whole column vocabulary for both scopes and the defaults a merchant gets when they have saved none
create_export_column_set POST /kahunacart/export-column-sets kahunacart.orders.manage name, columns required; scope optional Save a named column choice so the same export comes out the same way next month
update_export_column_set PATCH /kahunacart/export-column-sets/{id} kahunacart.orders.manage id required; name, scope, columns optional Rename a set, move it to the other scope, or replace its columns
delete_export_column_set DELETE /kahunacart/export-column-sets/{id} kahunacart.orders.manage id, required Remove a saved column choice
quote_order POST /kahunacart/orders/{id}/quote kahunacart.orders.manage id required; expires_at, days, send optional Turn a placed order into an offer with a deadline, and optionally email it
accept_quote POST /kahunacart/orders/{id}/quote/accept kahunacart.orders.manage id, required Record that the customer accepted, for an answer that arrived by phone or by email — the customer's own button is on their order page
decline_quote POST /kahunacart/orders/{id}/quote/decline kahunacart.orders.manage id, required Record that the customer turned the offer down
list_custom_fields GET /kahunacart/custom-fields kahunacart.settings entity Every custom field defined on this store, optionally for one entity — product, variant, order, order_item, customer, address, or an entity an add-on registered — with its type, visibility (public, readonly or internal), whether the admin list can sort and filter on it, its validation rules, its default and the blueprint fragment the admin renders it from
create_custom_field POST /kahunacart/custom-fields kahunacart.settings entity, name required; type, label_key, help_key, visibility, sortable, filterable, required, default, validate, position optional Define a custom field on an entity
update_custom_field PATCH /kahunacart/custom-fields/{id} kahunacart.settings id required; label_key, help_key, visibility, sortable, filterable, required, default, validate, position optional Change a custom field's label, help, visibility, sortable and filterable flags, required flag, default, validation or position
delete_custom_field DELETE /kahunacart/custom-fields/{id} kahunacart.settings id, required Delete a custom field definition and every value stored under it, on every row of its entity
read_custom_field_values GET /kahunacart/custom-fields/values/{entity}/{entityId} kahunacart.orders.view entity, entityId, all required Every custom field value on one row of an entity — a product, a variant, an order, an order line, a customer or an address — with the field definitions beside them
write_custom_field_values PUT /kahunacart/custom-fields/values/{entity}/{entityId} kahunacart.orders.view entity, entityId, values, all required Set custom field values on one row of an entity
read_settings_store GET /kahunacart/settings-store/{namespace} kahunacart.orders.view namespace, required Every key in one namespace of the add-on settings store with its current value, or its default when nothing has been written, plus the key's scope and permission
write_settings_store PUT /kahunacart/settings-store/{namespace} kahunacart.orders.view namespace, values, all required Write one or more keys in a namespace of the add-on settings store
list_zones GET /kahunacart/zones kahunacart.orders.view none The zone catalogue: every place an add-on can mount a widget in the admin or on the storefront, by its stable id — order.details.side, product.details, cart.express and the rest — with the widgets currently mounted in each, already in the merchant's order, the saved layouts, and whether the caller has dev mode switched on
list_schema_extensions GET /kahunacart/schema-extensions kahunacart.settings none What every installed add-on has declared it adds to KahunaCart's own responses, and where
list_hook_points GET /kahunacart/hook-points kahunacart.settings none The five points in an order's life where an add-on may do something that has to be undone if a later step fails — order_completed, payment_captured, fulfillment_created, order_canceled and order_refunded — with every handler attached to each, the plugin that owns it, and whether it declared an undo
list_product_revisions GET /kahunacart/products/{id}/revisions kahunacart.products.manage id, required Every version of one product, newest first, with what wrote it (a save, a restore or a scheduled change), the note and the admin who made it, and the product's publish window
restore_product_revision POST /kahunacart/products/{id}/revisions/{revision}/restore kahunacart.products.manage id, revision, all required Put a product back exactly as one of its revisions found it — variants, options, media, categories, tags, attributes, kit children, relations, quantity tiers, translations and custom-field values, ids included
duplicate_product POST /kahunacart/products/{id}/duplicate kahunacart.products.manage id, required Copy a product into a new draft and answer with the copy, in the same form create_product answers
set_scheduled_publish PUT /kahunacart/products/{id}/schedule kahunacart.products.manage id required; publish_at, unpublish_at optional Give a product a time to go live and a time to come down, as UTC epoch seconds
list_product_prerequisites GET /kahunacart/products/{id}/prerequisites kahunacart.products.manage id, required What a customer has to have bought before they may buy this product
set_product_prerequisites PUT /kahunacart/products/{id}/prerequisites kahunacart.products.manage id, prerequisites required; mode optional Replace the list wholesale
list_payment_collections GET /kahunacart/payment-collections kahunacart.orders.view status, page, per_page, sort, dir, q The orders that have been part paid, with what each owes, what has been authorised, captured and refunded, and what is still outstanding
create_deposit POST /kahunacart/orders/{id}/deposit kahunacart.orders.manage id required; amount, percent, provider optional Open a payment collection on one order and record the deposit session against it
collect_order_balance POST /kahunacart/orders/{id}/collect-balance kahunacart.orders.manage id, required Send the balance request email to a part-paid order's customer and record the send on the order
capture_payment POST /kahunacart/transactions/{id}/capture kahunacart.orders.manage id required; amount_minor, amount optional Take some or all of the money a provider is holding against an order
void_payment POST /kahunacart/transactions/{id}/void kahunacart.orders.manage id required; reason optional Release the money a provider is holding against an order without taking any of it, and cancel the order — the goods go back on the shelf, the coupon is released and download grants are revoked, exactly as Cancel does, and the history says why
list_payment_tokens GET /kahunacart/customers/{id}/payment-tokens kahunacart.customers.view id, required The payment methods a provider keeps on file for one customer, as this store knows them: the provider, a display label, the brand, the last four digits and the expiry the provider reports, and which one is the default
delete_payment_token DELETE /kahunacart/payment-tokens/{id} kahunacart.customers.manage id, required Ask the provider to forget a stored payment method, then retire the store's own record of it
list_webhook_subscriptions GET /kahunacart/webhook-subscriptions kahunacart.settings none Every outbound webhook subscription — a topic and a URL another system is told about — with its status (active, paused, disabled), failure streak and last delivered and failed times, plus the counts per status, every topic a subscription may name, the disable threshold and the delivery-log retention
create_webhook_subscription POST /kahunacart/webhook-subscriptions kahunacart.settings name, topic, target_url required; ping optional Create a subscription and answer with its signing secret, once
update_webhook_subscription PATCH /kahunacart/webhook-subscriptions/{id} kahunacart.settings id required; name, topic, target_url, status, rotate_secret optional Rename, retarget or change the topic; pause; activate, which pings the target first and refuses with the reason when the ping fails; or rotate the secret, which answers with the new one once and stops the old one working at that moment
delete_webhook_subscription DELETE /kahunacart/webhook-subscriptions/{id} kahunacart.settings id, required Delete a subscription
list_outbound_webhook_deliveries GET /kahunacart/webhook-deliveries kahunacart.settings subscription_id, order_id, event_id, sort, dir, q, page, per_page The outbound delivery log, one row per attempt, newest first: the subscription, the topic, the event id, the order it concerned, the attempt number, the response status, a 2 KB excerpt of the response, how long it took and any error
redeliver_webhook POST /kahunacart/webhook-deliveries/{id}/redeliver kahunacart.settings id, required Queue one attempt's occurrence again, to that one subscription, with the same event id so the receiver can deduplicate
list_jobs GET /kahunacart/jobs kahunacart.settings state, type, sort, dir, page, per_page A page of the job queue, newest first: every job with its state — pending, running, complete, failed or cancelled — its arguments, when it was queued and started, how long it took, how many attempts of how many, the worker that held it and the last error, plus the counts per state
run_job POST /kahunacart/jobs/{id}/run kahunacart.settings id, required Release a job for the next worker tick, whatever it was waiting for; a failed job is granted one more attempt
cancel_job POST /kahunacart/jobs/{id}/cancel kahunacart.settings id, required Cancel a pending job outright, or ask a running one to stop: the flag is read at a transaction boundary, so a bulk change stops between orders and nothing half-written is left
run_check GET /kahunacart/check kahunacart.settings offline Run every going-live check — database, migrations, scheduler, mail transport, download roots, trusted proxies, provider credentials, webhook reachability, template drift, the report rollup — and answer each finding with its status, detail, suggested fix and docs anchor, the overall verdict, and the redacted support report as text
template_drift_report GET /kahunacart/template-drift kahunacart.settings none Every template the plugin ships with the state of its theme override: current, behind (the override's @version is lower than the shipped one), ahead, unstamped, or none
list_notifications GET /kahunacart/notifications-registry kahunacart.settings none Every email the store can send — the base plugin's own and any an add-on registers — with whether it is switched on, the template pair it renders from, the shipped pair it can be restored to, who it goes to and what triggers it, plus the template pairs a notification may be pointed at
update_notification PATCH /kahunacart/notifications-registry kahunacart.settings code required; enabled, template, restore, channels, priority, use_default optional Change one notification by code
list_notification_channels GET /kahunacart/notification-channels kahunacart.settings none Every channel the store can reach its owner on — email, Slack, Discord, Telegram, Pushover, ntfy, Gotify, a text message through Textbelt or Twilio or a webhook of your own — with its id, its kind and what it is called, plus the fields each kind asks for
set_default_notification_channels PUT /kahunacart/notification-channels/default kahunacart.settings channels, required Set the channels every store-owner message goes to until its own channels are saved — "New messages go to" on the Channels page
get_object_template GET /kahunacart/object-templates kahunacart.settings slot The operator-editable formats — SKU, product and variant line titles, the order description a provider is handed, the order reference, the invoice file name and every order email's subject — each with its shipped default, the template it currently holds, the variables it may read and the sample its preview renders against
set_object_template PUT /kahunacart/object-templates kahunacart.settings slot, template, all required Save the Twig template one slot renders
list_sales GET /kahunacart/sales kahunacart.sales.manage none Every sale with its header, its rules and its quantity ladder
get_sale GET /kahunacart/sales/{id} kahunacart.sales.manage id, required One sale with its rules in order, its quantity ladder lowest rung first, and the translation overrides on its shopper-facing label
create_sale POST /kahunacart/sales kahunacart.sales.manage name, label required; starts_at, expires_at, priority, enabled, stack_with_coupons, rules, conditions, tier_basis, tiers, translations optional Create an automatic discount
update_sale PATCH /kahunacart/sales/{id} kahunacart.sales.manage id required; name, label, starts_at, expires_at, priority, enabled, stack_with_coupons, rules, conditions, tier_basis, tiers, translations optional Change a sale's header, its rules, or both
delete_sale DELETE /kahunacart/sales/{id} kahunacart.sales.manage id, required Delete a sale, its rules and the translations of its label
report_summary GET /kahunacart/reports/summary kahunacart.reports currency_table, currency_picker, currency, from, to Headline figures for a date range: revenue, orders, average order value, items sold, refunds, top products, a breakdown by payment method, and a per-day series carrying revenue, orders, units and refunds
report_products GET /kahunacart/reports/products kahunacart.reports currency_picker, currency, from, to, page, sort, dir, q What sold in a date range, by product and by variant, sorted by revenue, with units, revenue and refunds on each
report_customers GET /kahunacart/reports/customers kahunacart.reports currency_picker, currency, from, to, page, sort, dir, q Who bought in a date range, sorted by revenue, with each customer's orders, revenue, average order value, first and last order and whether they were new in the range, plus new against returning splits for the whole range
report_taxes GET /kahunacart/reports/taxes kahunacart.reports currency_picker, currency, from, to Tax collected in a date range, per label and rate, with the taxable base and the tax beside it
report_refunds GET /kahunacart/reports/refunds kahunacart.reports currency_picker, currency, from, to, page, sort, dir, q Every refund transaction raised in a date range, newest first, whatever became of it, with the order and customer it belongs to
report_low_stock GET /kahunacart/reports/low-stock kahunacart.reports threshold Stock-managed variants at or below a threshold, right now rather than over a range
list_orders GET /kahunacart/orders kahunacart.orders.view q, currency, sort, dir, cf, product, variant, from, to, customer, status, payment, fulfillment, channel, location, refund_request, page, per_page Every order that got past the cart, newest first, canceled ones included, with its number, email, totals, payment status and fulfillment status
get_order GET /kahunacart/orders/{id} kahunacart.orders.view id, required One order with its lines, adjustments, payment transactions, its stored history, its notes thread and which of its emails were last queued and sent, plus the invoice and invoice PDF URLs, a pay link while there is still something to pay, and the refunded and pending-refund totals
create_order POST /kahunacart/orders kahunacart.orders.manage items required; email, channel, location_id, register_ref, name, note, currency, billing, shipping, shipping_method_id, save_addresses, billing_address_id, shipping_address_id, tax_exempt, vat_id optional Record an order taken somewhere other than the storefront, over the phone, at a stall or by email
list_order_tenders GET /kahunacart/orders/{id}/tenders kahunacart.orders.view id, required Every payment on an order, oldest first, as a till reads them: type (cash, card, store_credit, gift_card, pay_link or online), the amount, for cash what was handed over (tendered_minor) and the change, any cash rounding, the provider and its reference, who took it (taken_by), the till (register_ref), a card's reader, and what has been refunded against it
add_order_tender POST /kahunacart/orders/{id}/tenders kahunacart.orders.manage id, type required; amount_minor, tendered_minor, code, register_ref, idempotency_key optional Record one payment on an order that is waiting for money, so a sale can be paid several ways
counter_return POST /kahunacart/orders/{id}/counter-return kahunacart.orders.refund id, lines required; reason, note, refund optional The customer brought goods back to the shop: open an in-store return, approve it and receive it in one step, then refund it
open_counter_cart POST /kahunacart/counter-carts kahunacart.orders.manage location_id required; register_ref, currency optional Start a sale at a till as a cart, to be priced as items are added and then completed into a counter-sale order with complete_counter_cart
get_counter_cart GET /kahunacart/counter-carts/{id} kahunacart.orders.manage id, required A counter cart priced, or the counter-sale order it became once it was completed (open is then false)
discard_counter_cart DELETE /kahunacart/counter-carts/{id} kahunacart.orders.manage id, required Void a sale that was never charged, or a parked sale nobody came back for: the cart, its lines and its adjustments are deleted
add_counter_cart_line POST /kahunacart/counter-carts/{id}/lines kahunacart.orders.manage id, variant_id required; qty, line_data, parts optional Add a variant to the sale, as a buy button would: the price, the title and the stock check are the catalog's, and a variant already on the cart with the same line data gains the quantity
set_counter_cart_line_qty PATCH /kahunacart/counter-carts/{id}/lines/{item} kahunacart.orders.manage id, item, qty, all required Set a line's quantity; 0 takes the line off, with its staff discount
remove_counter_cart_line DELETE /kahunacart/counter-carts/{id}/lines/{item} kahunacart.orders.manage id, item, all required Take a line off the sale, with its staff discount
apply_counter_cart_coupon POST /kahunacart/counter-carts/{id}/coupons kahunacart.orders.manage id, code, all required Apply a discount code to the sale through the cart's own coupon path, so it is worked out before tax exactly as at an online checkout
remove_counter_cart_coupon DELETE /kahunacart/counter-carts/{id}/coupons/{code} kahunacart.orders.manage id, code, all required Take a discount code off the sale
set_counter_cart_discount POST /kahunacart/counter-carts/{id}/discount kahunacart.orders.manage id, kind, value, reason required; item_id optional A discount given by hand at the till, on one line (item_id) or on the whole sale (no item_id)
remove_counter_cart_discount DELETE /kahunacart/counter-carts/{id}/discount kahunacart.orders.manage id, required Take the staff discount on the whole sale off
remove_counter_cart_line_discount DELETE /kahunacart/counter-carts/{id}/lines/{item}/discount kahunacart.orders.manage id, item, all required Take the staff discount on one line off
set_counter_cart_customer POST /kahunacart/counter-carts/{id}/customer kahunacart.orders.manage id, customer_id, all required Attach a customer to the sale, which is repriced at once for their customer group's price list and group sales, or send customer_id null to make it a walk-in sale again
complete_counter_cart POST /kahunacart/counter-carts/{id}/complete kahunacart.orders.manage id, required Turn the sale into a counter-sale order, payment pending, exactly as create_order with channel pos would have built it for the same lines, customer and location; the signed-in user is its staff_user
refund_order POST /kahunacart/orders/{id}/refund kahunacart.orders.refund id required; items, shipping_minor, amount_minor, amount, reason, restock, notify, against_reference, return_id optional Refund an order through the provider that took the payment, either by naming the lines that came back or as a bare amount
record_refund POST /kahunacart/orders/{id}/refunds/record kahunacart.orders.refund id, reference required; amount_minor, amount, reason, notify, return_id optional Write down a refund that was issued somewhere KahunaCart cannot see — in the provider's own dashboard, by bank transfer, or in cash
get_refund_request GET /kahunacart/orders/{id}/refund-request kahunacart.orders.view id, required What a customer asked for on this order and what came of it: the reason they picked off the store's fixed list, anything they wrote, when they asked, and the decision with the sentence it was declined with
approve_refund_request POST /kahunacart/orders/{id}/refund-request/approve kahunacart.orders.refund id required; items, shipping_minor, amount_minor, amount, reason, restock, notify, method, note, label_url, label_pdf, tracking_number, carrier optional Agree to a customer's refund request: the order is refunded through the provider exactly as refund_order does it, and the request is then marked approved — only if the refund answered, so a request never claims a refund the provider refused
decline_refund_request POST /kahunacart/orders/{id}/refund-request/decline kahunacart.orders.manage id, note, all required Say no to a customer's refund request, and tell them why
list_returns GET /kahunacart/returns kahunacart.orders.view status, kind, order_id, page, per_page Every refund request and return across the store, newest first, each with its lines and the order's order_number
receive_return POST /kahunacart/orders/{id}/refund-request/receive kahunacart.orders.refund id required; lines optional Check in the parcel of the order's approved return: per line, how many arrived and how many of those are damaged
cancel_return POST /kahunacart/orders/{id}/refund-request/cancel kahunacart.orders.refund id required; note optional Call off the order's open or approved return: you stopped waiting for the parcel, or the customer withdrew it
mark_return_refunded POST /kahunacart/orders/{id}/refund-request/mark-refunded kahunacart.orders.refund id required; transaction_id optional Close the order's received (or approved) return as refunded without moving any money, for a return you settled some other way
attach_return_label POST /kahunacart/orders/{id}/refund-request/label kahunacart.orders.refund id required; label_url, label_pdf, tracking_number, carrier, notify optional Give the order's open or approved return a label: a PDF (base64), a URL, a tracking number and a carrier
buy_return_label POST /kahunacart/orders/{id}/refund-request/label/buy kahunacart.orders.refund id, provider required; notify optional Buy a label for the order's open or approved return from an add-on that sells them (get_refund_request lists them as label_providers) and attach it
mark_order_paid POST /kahunacart/orders/{id}/mark-paid kahunacart.orders.manage id required; notify optional Settle an order whose payment arrived outside KahunaCart, a bank transfer most often
fulfill_order POST /kahunacart/orders/{id}/fulfill kahunacart.orders.manage id required; notify optional Record one shipment covering every unit on the order that has not gone out yet, with no carrier named, and queue the shipped email to the customer
list_shipments GET /kahunacart/orders/{id}/shipments kahunacart.orders.view id, required Every parcel recorded against an order, oldest first, each with its carrier, tracking number, the tracking link the customer sees and the lines it carried
create_shipment POST /kahunacart/orders/{id}/shipments kahunacart.orders.manage id, items required; carrier, tracking_number, tracking_url, note, notify optional Record that these quantities of these lines went out, optionally with a carrier and a tracking number
cancel_order POST /kahunacart/orders/{id}/cancel kahunacart.orders.manage id required; notify optional Call off an unpaid sale and give back everything completion took: stock, the coupon redemption and any live download links
resend_order_confirmation POST /kahunacart/orders/{id}/resend-confirmation kahunacart.orders.manage id, required Queue the order confirmation email again
get_order_history GET /kahunacart/orders/{id}/history kahunacart.orders.view id, required Everything recorded against an order, oldest first: when it was placed, every payment and refund at each stage of its lifecycle, when it shipped, when it was cancelled, every email queued and every email sent, and every note written
order_notices_list GET /kahunacart/orders/{id}/notices kahunacart.orders.view id, required What still has to be said about one order, in two lists
order_notice_resolve POST /kahunacart/orders/{id}/notices/{noticeId}/resolve kahunacart.orders.manage id, noticeId, all required Mark one queue item on an order dealt with, recording who did it and when
order_notice_reopen POST /kahunacart/orders/{id}/notices/{noticeId}/reopen kahunacart.orders.manage id, noticeId, all required Put a resolved queue item back, for the case where somebody pressed the button on the wrong row
admin_notices_list GET /kahunacart/notices kahunacart.orders.view resolved, type, sort, dir, q, page, per_page Orders that need a human, across the whole store, unresolved first and newest first within each half
list_order_notes GET /kahunacart/orders/{id}/notes kahunacart.orders.view id, required The notes thread on an order — internal notes and notes written to the customer in one list, oldest first, each with its author and time
add_order_note POST /kahunacart/orders/{id}/notes kahunacart.orders.manage id, body required; to_customer optional File a note against an order
delete_order_note DELETE /kahunacart/orders/{id}/notes/{entryId} kahunacart.orders.manage id, entryId, all required Remove an internal note from an order
resend_order_email POST /kahunacart/orders/{id}/emails/{kind}/resend kahunacart.orders.manage id, kind, all required Queue one of an order's emails again — confirmation, payment-received, shipped, refund, pay-link, customer-note or download-added
issue_order_pay_link POST /kahunacart/orders/{id}/pay-link kahunacart.orders.manage id required; send optional Mint a new signed pay link for an order and move its deadline forward by orders.pay_link_days
list_order_downloads GET /kahunacart/orders/{id}/downloads kahunacart.orders.view id, required Every download grant issued against an order, with the file it points at, how many times it has been used, when it expires and whether it has been revoked
revoke_download POST /kahunacart/orders/{id}/downloads/{grantId}/revoke kahunacart.orders.manage id, grantId, all required Kill one download grant, which is the answer to a chargeback or a link that turned up where it should not have
list_customers GET /kahunacart/customers kahunacart.customers.view currency_picker, currency, q, group, account, sort, dir, cf, page, per_page Everyone who has bought from this store, with their order count, lifetime revenue and, on a store that keeps customer accounts in its own database, the state of each account
get_customer GET /kahunacart/customers/{id} kahunacart.customers.view id required; currency_picker, currency optional One customer with their lifetime stats (in one currency, with other_currencies beside them for anything they ordered in other currencies), their orders including canceled and refunded ones, their saved address book, the state of their account, and any panels contributed by add-on plugins such as a subscription or a membership
invite_customer POST /kahunacart/customers/{id}/invite kahunacart.customers.manage id, required Open a store account for this customer if there is none yet and queue the set-password email, the same message and the same token machinery the checkout opt-in uses
customer_magic_link_send POST /kahunacart/customers/{id}/magic-link kahunacart.customers.manage id, required Queue a single-use, short-lived link that signs this customer in, to the address on their record and never to one supplied here
clear_customer_2fa POST /kahunacart/customers/{id}/clear-2fa kahunacart.customers.manage id, required Destroy the authenticator secret on a customer's account, for the case where somebody lost their phone
set_customer_state POST /kahunacart/customers/{id}/state kahunacart.customers.manage id, state, all required Switch a customer's ability to sign in on or off
list_customer_addresses GET /kahunacart/customers/{id}/addresses kahunacart.customers.view id, required A customer's address book, default billing and shipping addresses first
create_customer_address POST /kahunacart/customers/{id}/addresses kahunacart.customers.manage id required; name, company, address1, address2, city, region, postcode, country, phone, label, is_default_billing, is_default_shipping optional Save an address for a customer, for example one taken over the phone
update_customer_address PATCH /kahunacart/customers/{id}/addresses/{addressId} kahunacart.customers.manage id, addressId required; name, company, address1, address2, city, region, postcode, country, phone, label, is_default_billing, is_default_shipping optional Change one address in a customer's book
delete_customer_address DELETE /kahunacart/customers/{id}/addresses/{addressId} kahunacart.customers.manage id, addressId, all required Remove an address from a customer's book
set_default_customer_address POST /kahunacart/customers/{id}/addresses/{addressId}/default kahunacart.customers.manage id, addressId, kind, all required Make one saved address the customer's default billing or shipping address, the one checkout and the admin's New order form start on
list_countries GET /kahunacart/countries kahunacart.orders.manage none Every country an address may name, as ISO 3166-1 alpha-2 code and English name, and under regions the states of the countries that publish a list of them, keyed by country code
webhook_summary GET /kahunacart/webhooks/summary kahunacart.settings none Per-provider delivery counts and the current run of consecutive failures, plus how many days of log are kept
list_webhook_deliveries GET /kahunacart/webhooks kahunacart.settings provider, page, per_page Recent webhook deliveries, newest first, each with the provider, the time it arrived, the status code, whether it succeeded, the action it was read as, the order it touched, any error and an excerpt of the provider's raw body
abandoned_carts_list GET /kahunacart/carts/abandoned kahunacart.orders.view sort, dir, q, hours, page, per_page Carts that have sat untouched past the store's abandoned window and still have an email address and something in them, biggest first by default
abandoned_cart_send_recovery POST /kahunacart/carts/{id}/recovery-email kahunacart.orders.manage id, required Queue the recovery message for one abandoned cart now instead of waiting for the sweep, and stamp the cart so the sweep does not mail it again
cart_link_create POST /kahunacart/carts/{id}/link kahunacart.orders.manage id required; purpose, email optional Mint a signed link that opens one cart in a customer's own browser — the way a phone or social sale is handed over
email_log_list GET /kahunacart/emails kahunacart.orders.view kind, recipient, status, order_id, sort, dir, page, per_page A page of the email log, newest first: every message the store queued for a customer with its kind, recipient, subject, order, how many attempts it took, whether it was sent, failed or is still pending, and the transport's own words on a failure
report_recovery GET /kahunacart/reports/recovery kahunacart.orders.view currency_picker, currency, days How well cart recovery is working over a window: carts mailed, carts reminded, carts recovered, the recovery rate, and what the recovered carts were worth once they became orders
list_providers GET /kahunacart/providers kahunacart.settings none Every registered payment method with its label, the capabilities it declares, whether it can be connection-checked, whether it can register its own webhook endpoint, whether it syncs products, what automatic sync is configured to do, and which one checkout starts on
check_provider POST /kahunacart/providers/{slug}/check kahunacart.settings slug, required Ask a payment provider to prove the credentials already configured for it actually work, and report what it resolved, such as the account and the environment
sync_provider POST /kahunacart/providers/{slug}/sync kahunacart.settings slug required; direction optional Push this store's catalog to a payment provider, or pull the provider's into it, and report how many products moved
setup_provider_webhook POST /kahunacart/providers/{slug}/webhook kahunacart.settings slug required; reset optional Ask a provider to register this store's webhook endpoint with it, subscribed to exactly the events it handles, and write whatever signing secret comes back into that provider's own plugin settings
make_default_provider POST /kahunacart/providers/{slug}/default kahunacart.settings slug, required Write checkout.default_provider so checkout starts on this payment method
list_provider_readers GET /kahunacart/providers/{slug}/readers kahunacart.settings slug, required The card readers on a payment provider's account, as the provider reports them now: each with its id, the label it was given, its status (online, offline or busy), its model and the provider's location reference, and how the provider pairs readers: pairing device_code (the reader shows a code, send it with pair_provider_reader) or app_code (get a code with begin_reader_pairing and type it into the reader)
pair_provider_reader POST /kahunacart/providers/{slug}/readers kahunacart.settings slug, code required; label, location_ref optional Register a card reader with a payment provider from the code the reader shows on its screen (a Stripe registration code, a SumUp pairing code), and answer the reader
begin_reader_pairing POST /kahunacart/providers/{slug}/readers/pairing kahunacart.settings slug required; label, location_ref optional For a provider whose readers pair from a code the provider makes (pairing app_code on list_provider_readers, like Square): ask the provider for a code, and answer the pairing with its id, the code to type into the reader, its status (waiting) and when it expires
get_reader_pairing GET /kahunacart/providers/{slug}/readers/pairing/{pairing} kahunacart.settings slug, pairing, all required How a pairing from begin_reader_pairing stands: waiting for the code to be typed into a reader, expired (start again), or paired, with the reader that took it
forget_provider_reader DELETE /kahunacart/providers/{slug}/readers/{reader} kahunacart.settings slug, reader, all required Take a card reader off a payment provider's account
start_reader_payment POST /kahunacart/orders/{id}/reader-payments kahunacart.orders.manage id, provider, reader required; amount_minor, amount, idempotency_key optional Put an amount on a card reader for an order and come back at once with the payment pending; the customer then taps, inserts or swipes on the reader
get_reader_payment GET /kahunacart/orders/{id}/reader-payments/{tid} kahunacart.orders.manage id, tid, all required How a card payment on a reader stands as the store has it: pending, checking, cancelling, succeeded, failed (with the reason in plain words), cancelled or timed_out, with the amount, the reader, the provider's reference and order_settled (false while the money is recorded and the order is still being completed)
refresh_reader_payment POST /kahunacart/orders/{id}/reader-payments/{tid}/refresh kahunacart.orders.manage id, tid, all required Ask the provider how a card payment on a reader stands and apply the answer exactly as its webhook would, so a store the provider cannot reach still settles; then answer the payment
cancel_reader_payment POST /kahunacart/orders/{id}/reader-payments/{tid}/cancel kahunacart.orders.manage id, tid, all required Clear the reader while the payment is still pending, because the customer changed their mind or the wrong amount was sent
abandon_reader_payment POST /kahunacart/orders/{id}/reader-payments/{tid}/abandon kahunacart.orders.manage id, tid, all required Give up on a card payment on a reader that the provider cannot say anything about: a start whose answer never came back (checking), or a payment whose provider cannot be reached
list_checkout_fields GET /kahunacart/checkout-fields kahunacart.products.manage none Every field the checkout form draws, in the order it draws them, with the three condition documents that decide when each is required, when it is hidden and what a valid answer is — plus the attribute vocabulary those conditions may be written against
create_checkout_field POST /kahunacart/checkout-fields kahunacart.products.manage key, label_key required; type, section, position, options, required, hidden, validation, enabled optional Add a field to the checkout form
update_checkout_field PUT /kahunacart/checkout-fields/{id} kahunacart.products.manage id required; key, label_key, type, section, position, options, required, hidden, validation, enabled optional Change a checkout field
delete_checkout_field DELETE /kahunacart/checkout-fields/{id} kahunacart.products.manage id, required Delete a field a merchant or an add-on added
get_country_locale GET /kahunacart/countries/{code}/locale kahunacart.products.manage code, required What one country expects of a postal address: the layout it prints in, the postcode grammar, the phone grammar and which address fields it will not do without
update_country_locale PUT /kahunacart/countries/{code}/locale kahunacart.products.manage code required; address_format, postcode_pattern, phone_pattern, required_fields, enabled, restore optional Fix one country without waiting for a release
get_report_comparison GET /kahunacart/reports/comparison kahunacart.reports currency_picker, currency, from, to, comparison, segment, filters Every published metric for a date range, compared with the period before it or with the same period a year earlier, and the change in basis points (250 means +2.5%)
get_report_vocabulary GET /kahunacart/reports/vocabulary kahunacart.reports none Every field a report can be filtered or broken down by, with its type, the operators it accepts, and whether a breakdown on it can be read from the rollup
list_report_views GET /kahunacart/report-views kahunacart.reports report_code, required The views the calling user has saved for one report, with their filters, columns, range and comparison mode, and which one is their default
create_report_view POST /kahunacart/report-views kahunacart.reports report_code, name required; filters, columns, range, is_default optional Save a named set of filters, columns, range and comparison mode for a report
update_report_view PATCH /kahunacart/report-views/{id} kahunacart.reports id required; report_code, name, filters, columns, range, is_default optional Change a saved view's filters, columns, range or default flag
delete_report_view DELETE /kahunacart/report-views/{id} kahunacart.reports id, required Delete one of your own saved views
get_view_config GET /kahunacart/view-configs/{entity} kahunacart.reports entity, required Every column an admin list screen offers, with whether the calling user sees it, and the store's own names for those fields
set_view_config PUT /kahunacart/view-configs/{entity} kahunacart.reports entity required; columns optional Save the calling user's column choice for a list screen
set_property_labels PUT /kahunacart/property-labels kahunacart.settings labels Replace the store's own names for fields
list_dashboard_alerts GET /kahunacart/alerts kahunacart.orders.view dismissed What needs attention right now, for the calling user: orders waiting to be paid, background jobs that are not running, stock running out, a report rollup that should be rebuilt, and anything other plugins have registered
dismiss_dashboard_alert POST /kahunacart/alerts/{id}/dismiss kahunacart.orders.view id required; restore optional Hide one alert for the calling user
rebuild_dashboard_alerts POST /kahunacart/alerts/rebuild kahunacart.settings none Clear every cached alert check so they all run again on the next read
import_config POST /kahunacart/config/import kahunacart.settings document required; dry_run optional Apply a configuration export: the plugin's YAML, plus whichever of the tax zones and rates, shipping zones, methods and collection points, coupons, campaigns, sales, settings rows and saved views the document carries
detect_config_contradictions GET /kahunacart/config/contradictions kahunacart.settings none Settings that are each valid on their own but wrong together: prices marked as including tax when no tax rate is set, tax-inclusive prices with delivery left untaxed, or checkout still on after the basket was turned off
apply_recommended_settings POST /kahunacart/config/contradictions/apply kahunacart.settings code, required Write one rule's recommended values into the store's own configuration file
get_agentic_status GET /kahunacart/agentic/status kahunacart.settings none Whether this store is ready for AI shopping agents
rotate_ucp_signing_key POST /kahunacart/agentic/keys/rotate kahunacart.settings none Make a new ES256 signing key and retire the current one
list_agent_sessions GET /kahunacart/agentic/sessions kahunacart.orders.view status, sort, dir, q, page, per_page UCP checkout and cart sessions and MCP carts, newest first: the kind, the status, the protocol, the platform it belongs to, the cart or order it points at (with the order number once the order is placed) and its dates
list_feeds GET /kahunacart/feeds kahunacart.settings none The Google Merchant Center and OpenAI product feed files, one per language (and, on a store that sells in more than one currency, one per currency, each row naming its currency): whether each is switched on, the URL to register it at, whether that URL needs ?key= (the key itself is not returned), when it was last built, how many rows it has and whether it is older than feeds.max_age
rebuild_feeds POST /kahunacart/feeds/rebuild kahunacart.settings none Queue a build of every enabled feed for every language, due now
mark_shipment_delivered POST /kahunacart/orders/{id}/shipments/{sid}/delivered kahunacart.orders.manage id, sid, all required Record that a parcel arrived
list_exchange_rates GET /kahunacart/currencies/rates kahunacart.settings none The exchange rates between the store's base currency and each currency it sells in
look_up_exchange_rates GET /kahunacart/currencies/rates/lookup kahunacart.settings none Ask Frankfurter once for today's market rates between the store's base currency and each currency it sells in, and write nothing
set_exchange_rate PUT /kahunacart/currencies/rates/{quote} kahunacart.settings quote, rate, all required Set the rate between the base currency and one of the store's other currencies by hand
delete_exchange_rate DELETE /kahunacart/currencies/rates/{quote} kahunacart.settings quote, required Remove the stored rate for one currency, whether a person typed it or a source fetched it
refresh_exchange_rates POST /kahunacart/currencies/rates/refresh kahunacart.settings none Fetch the rates from the configured source now, instead of waiting for the daily job
accept_exchange_rate POST /kahunacart/currencies/rates/{quote}/accept kahunacart.settings quote, new, all required Accept a rate a refresh held because it moved more than currencies.rates.hold_move_percent from the rate in use
keep_old_exchange_rate DELETE /kahunacart/currencies/rates/{quote}/held kahunacart.settings quote, required Turn down a held rate and keep the one in use
set_currency_pricing PUT /kahunacart/currencies/pricing kahunacart.settings pricing required; missing_price optional Save manual or converted pricing per enabled currency, with markup and rounding
get_report_currency GET /kahunacart/reports/currency kahunacart.reports none Read the saved currency selection and available completed-order currencies, base first
set_report_currency PUT /kahunacart/reports/currency kahunacart.reports currency, required Remember a known currency code or all for exact frozen-rate totals in base currency
check_payment_method_account POST /kahunacart/payment-methods/{id}/check-account kahunacart.settings id, required Check the saved credentials for this method’s account without returning secrets
setup_payment_method_webhook POST /kahunacart/payment-methods/{id}/setup-webhook kahunacart.settings id required; reset optional Register this method’s own endpoint and store its signing secret encrypted

Six things you can ask for

What you type What the assistant calls
"How many orders are unpaid from last week?" kahunacart_list_orders. Every row carries payment_status and created_at, and the assistant filters and counts. The list has no status filter of its own
"Add a Material attribute with cotton and linen, and set it on every tee." kahunacart_create_attribute with type: select, then kahunacart_list_products with q=tee, then kahunacart_update_product on each. attributes merges, so nothing else on the product changes
"Refund order 482 in full." kahunacart_list_orders with q=482 to turn the order number into an id, then kahunacart_refund_order with no amount
"Which products are low on stock?" kahunacart_report_low_stock, threshold 5 unless you name another
"Create a 20% summer sale on the Apparel category until 31 August." kahunacart_list_categories for the id, then kahunacart_create_sale with one rule of scope: category, mode: percent, percent: "20" and an expires_at timestamp
"Is Stripe still connected?" kahunacart_check_provider. A failed check is a successful call reporting ok: false with the provider's message

Permissions and safety

Three controls govern an assistant's access to your store.

The key decides which tools exist. GET /mcp/tools leaves out every tool whose permission the key does not hold. A key with only kahunacart.reports gets no refund tool. A super admin sees everything.

Every call is checked twice. grav-mcp checks the permission before calling, and the API plugin checks it again on the route. A tool that slipped through is still refused with 403.

Annotations tell the client what a tool is. Every GET is read-only. Deletes are destructive, and so are refund_order, cancel_order and revoke_download. A client can then ask before running one.

A store with demo.readonly on refuses every write with 403 before the handler runs, whatever the key holds. Reads are never blocked.

Tip

Give the assistant its own API key rather than reusing yours, with only the permissions the job needs. You can then revoke it without affecting your own access.

Goal Permissions on the key's account
Answer questions about the numbers api.access, kahunacart.reports
Answer questions about orders and customers api.access, kahunacart.orders.view, kahunacart.customers.view
Run the catalog api.access, kahunacart.products.manage
Run promotions api.access, kahunacart.sales.manage, kahunacart.settings
Process orders, without refunds api.access, kahunacart.orders.view, kahunacart.orders.manage
Process orders, refunds included the row above plus kahunacart.orders.refund
Export or restore the store api.access, kahunacart.export or kahunacart.restore, and each part's own permission, such as kahunacart.customers.view for the customers. See Export and restore
Everything the admin can do api.access and every kahunacart.* permission

To limit which plugins publish tools, set --plugin-tools or GRAV_MCP_PLUGIN_TOOLS. all is the default, none turns plugin tools off, and a comma-separated list of plugin slugs loads only those:

BASH
npx grav-mcp --url https://mysite.com/api --key grav_abc123 --plugin-tools kahunacart

Two conventions matter when reading the results.

Money goes in as decimal strings in the store currency ("19.99") and comes back as minor-unit integers (price_minor, total_minor, refunded_minor). Read currency_exponent from get_config to convert. See Money conventions.

attributes on a product update is a merge keyed by attribute slug. A slug you do not send keeps its value; a slug sent as null is removed. Every other list-valued field replaces its whole set.

Selling a grouped product as a set takes sell_as_set. create_product and update_product accept sell_as_set (true or false) on a grouped product or a kit. On, the product's own variant price is the bundle price of a full set of its children, and the children stay separate lines. Set the price through variants, as for any product. get_product answers set_price for these products: parts_minor, price_minor, saving_minor and sells_as_set, in the store's own currency. search_variants also takes ids, a comma-separated list such as 24,25, and answers those variants in that order instead of searching.

Add-ons

The Licenses, Subscriptions, Newsletters, Shipping, Bookings and Rentals add-ons ship their own mcp.yaml manifests. Installing one adds its tools to the same session, once the plugin is enabled and the tools are refreshed.

Licenses

The admin tools need kahunacart.licenses. Three of them call the public validation routes your software already uses, so an assistant can check a customer's key with no permission at all. What each does to a key is on Licenses.

Licenses publishes 21 tools over 32 admin routes, behind 1 permission. An assistant calls each one by its name below with kahunacart_licenses_ in front of it.

These 4 routes are not published as tools: POST /kahunacart/licenses/public/activate, POST /kahunacart/licenses/public/deactivate, GET /kahunacart/licenses/public/file, GET /kahunacart/licenses/public/release. Nor are the 7 OPTIONS preflight routes.

Tool Route Permission Arguments What it does
list_licenses GET /kahunacart/licenses kahunacart.licenses q, status, product_id, variant_id, customer_id, order_id, page, per_page, sort, dir Search issued license keys, newest first, each with its status, its project name, the seats in use against the seats allowed, its product, its order, its customer and its two dates
get_license GET /kahunacart/licenses/{id} kahunacart.licenses id, required One key with everything it knows about itself: the row and its effective status, every machine it has been activated on including the seats already freed, the product, variant, customer and order behind it, the licensing rule that governs it, every add-on that has been bought onto it, and a preview of the delivery copy and license files the customer receives
issue_license POST /kahunacart/licenses kahunacart.licenses product_id required; variant_id, customer_id, email, seats, term_days, updates_days, meta, note optional Mint a key with no order behind it, for a comp, a review copy or a replacement for somebody who bought through another channel, and return it the way get_license does
update_license PATCH /kahunacart/licenses/{id} kahunacart.licenses id required; label, seats, expires_at, updates_until, customer_id, meta, note optional Change what a merchant is allowed to change on an issued key: the project name, the seat allowance, the expiry, the updates window, the private note, the metadata and who it belongs to
suspend_license POST /kahunacart/licenses/{id}/suspend kahunacart.licenses id required; note optional Switch a key off for now, which is the one to use for a chargeback under investigation or a customer in arrears
reinstate_license POST /kahunacart/licenses/{id}/reinstate kahunacart.licenses id, required Undo a suspension and put the key back to active with its activations untouched
revoke_license POST /kahunacart/licenses/{id}/revoke kahunacart.licenses id, reason, all required Kill a key permanently and free every seat it holds
regenerate_license POST /kahunacart/licenses/{id}/regenerate kahunacart.licenses id, required Replace a leaked key
free_license_seat DELETE /kahunacart/licenses/{id}/activations/{activationId} kahunacart.licenses id, activationId, all required Release one seat on the customer's behalf, which is the support action for a machine that died still holding one
list_license_rules GET /kahunacart/licenses/products kahunacart.licenses none Every licensing rule in the store with the catalog beside it, plus the rule kinds, the subscription endings, the placeholder legend the delivery copy is written in and the store's default key layout with a sample key
order_issuance GET /kahunacart/licenses/issuance/{orderId} kahunacart.licenses orderId, required Whether one order's lines earn license keys, how many it has been given already, and which event the store mints them on — paid waits for the money to land, completed issues the moment the order is placed
save_product_rule PUT /kahunacart/licenses/products/{productId} kahunacart.licenses productId required; enabled, kind, renews_product_id, attaches_product_id, keys_per_qty, seats, seats_per_qty, term_days, updates_days, on_subscription_end, key_prefix, key_format, instructions, meta, grants, requires, includes, files optional Create or replace the product-wide licensing rule, which governs every variant that has no rule of its own
save_variant_rule PUT /kahunacart/licenses/products/{productId}/{variantId} kahunacart.licenses productId, variantId required; enabled, kind, renews_product_id, attaches_product_id, keys_per_qty, seats, seats_per_qty, term_days, updates_days, on_subscription_end, key_prefix, key_format, instructions, meta, grants, requires, includes, files optional Create or replace one variant's own licensing rule, which replaces the product-wide rule for that variant and leaves every other variant on the product's
delete_product_rule DELETE /kahunacart/licenses/products/{productId} kahunacart.licenses productId, required Remove the product-wide licensing rule, so the product stops earning keys unless one of its variants has a rule of its own
delete_variant_rule DELETE /kahunacart/licenses/products/{productId}/{variantId} kahunacart.licenses productId, variantId, all required Remove one variant's own licensing rule, which puts that variant back on the product-wide rule where there is one
get_license_settings GET /kahunacart/licenses/settings kahunacart.licenses none The store-wide licensing settings every rule falls back to: whether licensing is on, whether keys are issued when payment lands or when the order completes, the issuance defaults, the key layout with a sample key drawn by the class that mints them, the public API switches and rate limits, and the delivery copy and files
update_license_settings PATCH /kahunacart/licenses/settings kahunacart.licenses enabled, issue_on, order_data_key, key, defaults, api, delivery Save store-wide licensing settings to the plugin's config file
rotate_signing_secret POST /kahunacart/licenses/settings/rotate-secret kahunacart.licenses none Mint a new API signing secret, write it to the plugin config and return it once
validate_key POST /kahunacart/licenses/public/validate none (public route) key required; instance, product optional Ask whether a license key is good, the way a customer's own software does, and get back the status, the product, the seat counts, the two dates, the key's metadata and covers, every product and package slug the key is good for
check_updates GET /kahunacart/licenses/public/updates none (public route) key, released_at, all required Ask whether a key covers a release that came out at a given moment, which is the question an updater asks before it downloads
latest_release GET /kahunacart/licenses/public/latest none (public route) key, required Ask which store-hosted build a key is entitled to, with its version, its changelog, and each file's size, download address and sha256: checksum

Subscriptions

Reads need kahunacart.subscriptions.view, writes kahunacart.subscriptions.manage. A renewal is an ordinary order, so marking one paid is kahunacart_mark_order_paid from the base plugin. What each action means is on Subscriptions.

Subscriptions publishes 17 tools over 17 admin routes, behind 2 permissions. An assistant calls each one by its name below with kahunacart_subscriptions_ in front of it.

Tool Route Permission Arguments What it does
list_plans GET /kahunacart/subscription-plans kahunacart.subscriptions.view none Every subscription plan in the store, each with the product and variant it sells, its billing interval, its free trial, its billing mode, its renewal variant, its grace period, its own price and label if it has them, what happens to its place when a subscription ends, the customer group it puts live subscribers in, its position among the variant's billing options, its per-provider price references and how many live subscriptions are running on it
get_plan GET /kahunacart/subscription-plans/{id} kahunacart.subscriptions.view id, required One plan with the variant it sells, the variant a renewal carries, its interval, trial, billing mode, grace period, price, label, stock setting, customer group, sort position, per-provider references and its live subscription count, plus the list of registered payment providers
list_plan_variants GET /kahunacart/subscription-plans/variants kahunacart.subscriptions.view q Every purchasable variant in the catalog with its price in minor units, its product status, plan_ids listing every plan it already carries with the default first, and plan_id, the default plan, or null when it carries none
create_plan POST /kahunacart/subscription-plans kahunacart.subscriptions.manage variant_id required; interval_unit, interval_count, trial_days, billing_mode, renewal_variant_id, grace_days, price_minor, currency_amounts, label, stock_on_end, sort, customer_group, provider_refs, enabled optional Start selling an existing product variant on a calendar
update_plan PATCH /kahunacart/subscription-plans/{id} kahunacart.subscriptions.manage id required; interval_unit, interval_count, trial_days, billing_mode, renewal_variant_id, grace_days, price_minor, currency_amounts, label, stock_on_end, sort, customer_group, enabled optional Change a plan's calendar
delete_plan DELETE /kahunacart/subscription-plans/{id} kahunacart.subscriptions.manage id, required Remove a plan
set_plan_provider_refs PUT /kahunacart/subscription-plans/{id}/provider-refs/{provider} kahunacart.subscriptions.manage id, provider, refs, all required Write one payment platform's price or product ids onto a plan, so a provider-mode checkout opens the agreement against a price created by hand at the platform
list_subscriptions GET /kahunacart/subscriptions kahunacart.subscriptions.view q, status, billing_mode, provider, plan_id, customer_id, sort, dir, page, per_page A page of subscriptions, each with its status, plan, customer, billing mode, payment provider, current period, next billing date and frozen amount
get_subscription GET /kahunacart/subscriptions/{id} kahunacart.subscriptions.view id, required One subscription in full: the row with its status, frozen amount, current period, next billing date, billing mode, provider and failed-attempt count; the plan behind it; every order it has produced, which are ordinary KahunaCart orders with numbers, receipts and invoices of their own; and the whole timeline
get_metrics GET /kahunacart/subscriptions/metrics kahunacart.subscriptions.view none The store's recurring-revenue figures right now: monthly and annual recurring revenue per currency, the counts of active, trialing, past-due and paused subscriptions, and how many plans exist
cancel_subscription POST /kahunacart/subscriptions/{id}/cancel kahunacart.subscriptions.manage id required; at_period_end, reason optional End a subscription, at the end of the period already paid for or immediately
uncancel_subscription POST /kahunacart/subscriptions/{id}/uncancel kahunacart.subscriptions.manage id, required Clear a cancellation scheduled for the end of the period, which is what a retention call ends in
pause_subscription POST /kahunacart/subscriptions/{id}/pause kahunacart.subscriptions.manage id required; reason optional Stop the calendar without ending the subscription
resume_subscription POST /kahunacart/subscriptions/{id}/resume kahunacart.subscriptions.manage id, required Start the calendar again on a paused subscription, returning it to active — or call off a pause that was scheduled and has not taken effect, which is the same call at the platform and leaves the subscription running as it was
extend_subscription POST /kahunacart/subscriptions/{id}/extend kahunacart.subscriptions.manage id, days, note, all required Push the current period out by a number of days without billing for them, moving the next billing date with it
add_subscription_note POST /kahunacart/subscriptions/{id}/note kahunacart.subscriptions.manage id, note, all required Write a free-text note onto a subscription's timeline
resend_pay_link POST /kahunacart/subscriptions/{id}/resend-pay-link kahunacart.subscriptions.manage id, required Queue the pay-link email again for the renewal invoice a customer already owes, and record the resend on the timeline

Newsletters

Reads need kahunacart.newsletters.view, writes kahunacart.newsletters.manage. Starting, resuming or test-sending a campaign needs kahunacart.newsletters.send on top of .manage. What each action means is on Newsletters.

Newsletters publishes 77 tools over 85 admin routes, behind 3 permissions. An assistant calls each one by its name below with kahunacart_newsletters_ in front of it.

These 8 routes are not published as tools: GET /kahunacart/newsletters/campaigns/{id}/preview, GET /kahunacart/newsletters/campaigns/{id}/sends/{send}/view, POST /kahunacart/newsletters/imports, POST /kahunacart/newsletters/imports/preview, GET /kahunacart/newsletters/subscribers/export, GET /kahunacart/newsletters/labels, GET /kahunacart/newsletters/reports/{type}.csv, GET /kahunacart/newsletters/templates/{id}/preview.

Tool Route Permission Arguments What it does
list_imports GET /kahunacart/newsletters/imports kahunacart.newsletters.view none The recent runs of the CSV importer, newest first, with the file each one read, the list it wrote to, where the run got to and the four counters that say what it did: how many addresses were new, how many were already here, how many were skipped and how many ended in a suppression rather than a subscriber
get_import GET /kahunacart/newsletters/imports/{id} kahunacart.newsletters.view id, required One import run with its counters, its status, the reason it failed if it did, and how far through the file it is as a fraction — which is null until it has read its first chunk, because a run that has not started knows nothing and reporting zero would be reporting something
preview_customer_import POST /kahunacart/newsletters/imports/customers/preview kahunacart.newsletters.view filter, limit How many of the store's own customers match a filter and what would happen to each of them, without writing anything
start_customer_import POST /kahunacart/newsletters/imports/customers kahunacart.newsletters.manage list_id, mode, tag, filter Put the store's own customers on a list, as one import run with the same counters and the same history as a CSV import
get_overview GET /kahunacart/newsletters/overview kahunacart.newsletters.view none The front screen of the newsletter in one call: how many people are in each subscriber status and how many of them a campaign could actually reach, how many addresses are suppressed, thirty daily buckets of new signups, the last five campaigns with their open and click rates, the last twenty that actually went out with their open, click, unsubscribe, bounce and complaint rates side by side, how many campaigns are in each status, and every alert the store should act on before it sends anything else
get_settings GET /kahunacart/newsletters/settings kahunacart.newsletters.view none What this store is configured to send and whether it can
list_campaigns GET /kahunacart/newsletters/campaigns kahunacart.newsletters.view status, sort, dir, q, page, per_page Every campaign, newest first, with its status, the list it goes to, its counters and its open and click rates
get_campaign GET /kahunacart/newsletters/campaigns/{id} kahunacart.newsletters.view id, required One campaign with everything above its tables: the subject, the preheader, the Markdown body, the layout, the list, the sender overrides, the coupon, whether opens and clicks are counted, every counter, the open and click rates, and a breakdown of its recipients by what happened to each of them
count_campaign_audience GET /kahunacart/newsletters/campaigns/audience kahunacart.newsletters.view lists, segment How many people a campaign to these lists, narrowed by this segment, would mail right now, and up to twenty of them by name, before any campaign exists
create_campaign POST /kahunacart/newsletters/campaigns kahunacart.newsletters.manage name required; subject, preheader, body_md, layout, list_id, list_ids, segment_id, template_id, from_name, from_email, reply_to, coupon_code, track_opens, track_clicks, subject_b, ab_sample_percent, ab_window_minutes, utm optional Create a campaign in draft
update_campaign PATCH /kahunacart/newsletters/campaigns/{id} kahunacart.newsletters.manage id required; name, subject, preheader, body_md, layout, list_id, list_ids, segment_id, from_name, from_email, reply_to, coupon_code, track_opens, track_clicks, subject_b, ab_sample_percent, ab_window_minutes, utm optional Change a campaign that has not gone anywhere yet
delete_campaign DELETE /kahunacart/newsletters/campaigns/{id} kahunacart.newsletters.manage id, required Remove a campaign and everything it recorded
list_campaign_recipients GET /kahunacart/newsletters/campaigns/{id}/sends kahunacart.newsletters.view id required; status, sort, dir, q, page, per_page optional Who this campaign was sent to and what happened to each of them, a page at a time
list_campaign_links GET /kahunacart/newsletters/campaigns/{id}/links kahunacart.newsletters.view id, required Every link in the campaign with how many times it was pressed and what share of the people it reached that is, ordered by clicks so the link that worked is at the top rather than buried under the header image nobody pressed
get_campaign_timeline GET /kahunacart/newsletters/campaigns/{id}/timeline kahunacart.newsletters.view id, required Four cumulative series — sent, delivered, opened, clicked — in one-hour buckets from the moment the first message went out, over the first forty-eight hours
preview_campaign POST /kahunacart/newsletters/campaigns/{id}/preview kahunacart.newsletters.manage id required; subject, preheader, body_md, layout, coupon_code optional Render a campaign the way one person would receive it, through the real layout with the real Markdown and the real merge pass, and answer the HTML, the plain-text part and the subject
send_campaign_test POST /kahunacart/newsletters/campaigns/{id}/test kahunacart.newsletters.send id, email, all required Mail one copy of a campaign to an address, with the subject prefixed and nothing counted, so it can be looked at in a real inbox before anybody else sees it
start_campaign POST /kahunacart/newsletters/campaigns/{id}/start kahunacart.newsletters.send id required; scheduled_at optional Put this campaign in inboxes, now or at a moment
pause_campaign POST /kahunacart/newsletters/campaigns/{id}/pause kahunacart.newsletters.manage id, required Stop a sending or scheduled campaign within one message
resume_campaign POST /kahunacart/newsletters/campaigns/{id}/resume kahunacart.newsletters.send id, required Carry on with a paused campaign, or restart one that stopped itself after too many refused messages
cancel_campaign POST /kahunacart/newsletters/campaigns/{id}/cancel kahunacart.newsletters.manage id, required Stop a campaign for good
catch_up_campaign POST /kahunacart/newsletters/campaigns/{id}/catch-up kahunacart.newsletters.send id, required Send a campaign that has already gone out to the people who joined its list afterwards and never received it
preview_campaign_catch_up GET /kahunacart/newsletters/campaigns/{id}/catch-up kahunacart.newsletters.view id, required How many people would be mailed if this campaign were sent on to the people who joined its list after it went out, and up to twenty of them by name
retry_failed_campaign_sends POST /kahunacart/newsletters/campaigns/{id}/retry-failed kahunacart.newsletters.send id required; send_id optional Try the failed sends of a campaign that has gone out again: every failed send, or only the one named by send_id
list_subscribers GET /kahunacart/newsletters/subscribers kahunacart.newsletters.view q, status, source, list, list_status, tag, campaign_id, campaign_received, sort, dir, page, per_page Who is on the mailing list, with the lists and tags each person carries, how they joined, when they agreed and what state they are in
get_subscriber GET /kahunacart/newsletters/subscribers/{id} kahunacart.newsletters.view id, required One person with the whole record behind them: the consent — when they agreed, from what address, and whether the store still holds the sentence they were shown — every list they hold a membership on and its state, their tags, the import they came in with if they came in with one, the customer record they are linked to if there is one, whether the store has been told never to mail the address, and every campaign they were part of with what happened to it
export_subscriber_data GET /kahunacart/newsletters/subscribers/{id}/export kahunacart.newsletters.manage id, required Everything held about one person, as one JSON document, for answering somebody who asks what is kept about them: their profile, every list membership with its status and dates, their tags, the full consent history (each signup, confirmation, admin add or invite, import, unsubscribe and return, with the address and browser for the changes they made themselves and the note for an admin's), every message they were sent with when it was opened and each link they pressed, what the mail provider reported about them (deliveries, bounces, complaints), their automation walks, any suppression on their address, the import runs that brought them in, and any records the site adds of its own
add_subscriber POST /kahunacart/newsletters/subscribers kahunacart.newsletters.manage email required; name, list_id, tags, language, mode, note optional Add one person to a list by address, as source "admin"
update_subscriber PATCH /kahunacart/newsletters/subscribers/{id} kahunacart.newsletters.manage id required; name, language, lists, tags optional Change somebody's name, language, lists or tags
unsubscribe_subscriber POST /kahunacart/newsletters/subscribers/{id}/unsubscribe kahunacart.newsletters.manage id, required Take one person off every list, recorded as done by the store rather than by them
resubscribe_subscriber POST /kahunacart/newsletters/subscribers/{id}/resubscribe kahunacart.newsletters.manage id required; list_id, note optional Undo an unsubscribe, for somebody who has asked you to
confirm_subscriber POST /kahunacart/newsletters/subscribers/{id}/confirm kahunacart.newsletters.manage id, required Move a person who is waiting to confirm onto their lists, for the one whose confirmation email went to spam or never arrived and who has since asked, in writing, to be on the list
delete_subscriber DELETE /kahunacart/newsletters/subscribers/{id} kahunacart.newsletters.manage id, required Remove one person from the mailing list entirely — their memberships, their tags, every send they were part of and every click they made
bulk_subscribers POST /kahunacart/newsletters/subscribers/bulk kahunacart.newsletters.manage action required; ids, filter, all, after, max_id, addresses, list_id, mode, note, tag, tag_id optional One action over many people: add them to a list, take them off one, add or remove a tag, unsubscribe them from everything, or delete them
list_lists GET /kahunacart/newsletters/lists kahunacart.newsletters.view none Every list in the store with the number of people a campaign to it would reach — the subscribed ones, not the pending or the departed
create_list POST /kahunacart/newsletters/lists kahunacart.newsletters.manage code, name required; description, double_opt_in, is_default, public, position, from_name, from_email optional Add a topic people can subscribe to and leave separately
update_list PATCH /kahunacart/newsletters/lists/{id} kahunacart.newsletters.manage id required; name, description, double_opt_in, is_default, public, position, from_name, from_email optional Change a list's name, description, confirmation setting, default flag, whether it is offered on the preference center, or sender overrides
list_tags GET /kahunacart/newsletters/tags kahunacart.newsletters.view none Every tag in the store with how many people carry it
create_tag POST /kahunacart/newsletters/tags kahunacart.newsletters.manage code required; label optional Add a tag
delete_tag DELETE /kahunacart/newsletters/tags/{id} kahunacart.newsletters.manage id, required Remove a tag from the store and from everybody carrying it
list_templates GET /kahunacart/newsletters/templates kahunacart.newsletters.view active The starting points a merchant keeps — a subject, a preheader, some Markdown and a layout
get_template GET /kahunacart/newsletters/templates/{id} kahunacart.newsletters.view id, required One template with its subject, preheader, Markdown body and layout, plus the merge variables a campaign made from it may use
create_template POST /kahunacart/newsletters/templates kahunacart.newsletters.manage name required; subject, preheader, body_md, layout, is_active optional Save the wording and layout worth reusing, so every campaign after this one starts from it
update_template PATCH /kahunacart/newsletters/templates/{id} kahunacart.newsletters.manage id required; name, subject, preheader, body_md, layout, is_active optional Change a template
delete_template DELETE /kahunacart/newsletters/templates/{id} kahunacart.newsletters.manage id, required Remove a starting point
preview_template POST /kahunacart/newsletters/templates/{id}/preview kahunacart.newsletters.manage id required; subject, preheader, body_md, layout optional Render a template the way a campaign made from it would arrive, through the real layout with a sample subscriber and tracking off, and answer the HTML, the plain-text part and the subject
list_suppressions GET /kahunacart/newsletters/suppressions kahunacart.newsletters.view reason, sort, dir, q, page, per_page The addresses this store will never mail again, whatever any list or import says about them, with why each one is here and when
add_suppression POST /kahunacart/newsletters/suppressions kahunacart.newsletters.manage email required; reason, note optional Tell the store never to mail an address again, for somebody who asked directly rather than through a link
remove_suppression DELETE /kahunacart/newsletters/suppressions/{hash} kahunacart.newsletters.manage hash, required Remove one suppression, addressed by the SHA-256 of the address — which is how the row is keyed, because that is the half that survives an erasure
list_segments GET /kahunacart/newsletters/segments kahunacart.newsletters.view none Every saved segment, with the count of people it is about, when that count was taken, and how many campaigns are pointed at it
get_segment GET /kahunacart/newsletters/segments/{id} kahunacart.newsletters.view id, required One segment with its full condition document, its cached count and the list it is narrowed to
get_segment_vocabulary GET /kahunacart/newsletters/segments/vocabulary kahunacart.newsletters.view none Every field a segment condition may name, with its type, the operators it accepts, the group it belongs to and — for the fields that are chosen from a list — the options themselves
preview_segment POST /kahunacart/newsletters/segments/preview kahunacart.newsletters.view list_id, definition Answer a condition document straight away: how many people match it, ten of them, and how many matched but cannot be mailed — suppressed, gone from the list, or never confirmed
create_segment POST /kahunacart/newsletters/segments kahunacart.newsletters.manage name required; description, list_id, definition optional Save a condition document under a name
update_segment PATCH /kahunacart/newsletters/segments/{id} kahunacart.newsletters.manage id required; name, description, list_id, definition optional Edit a segment's name, description, list or conditions
delete_segment DELETE /kahunacart/newsletters/segments/{id} kahunacart.newsletters.manage id, required Remove a segment
count_segment POST /kahunacart/newsletters/segments/{id}/count kahunacart.newsletters.manage id, required Work out how many people a saved segment is about, right now, and store the answer with the moment it was taken
get_customer_subscriber GET /kahunacart/newsletters/customers/{id}/subscriber kahunacart.newsletters.view id, required Given a KahunaCart customer id, the subscriber record linked to it: their address and status, whether the store has been told never to mail them, every list they are on with how they got there, and the last campaign they were actually sent — with the date, and whether they opened it, clicked in it or it bounced
check_deliverability GET /kahunacart/newsletters/deliverability kahunacart.newsletters.view none Nine checks over the store's sending setup, each with a status, a sentence saying what is true and a sentence saying what to do about it
recheck_deliverability POST /kahunacart/newsletters/deliverability/recheck kahunacart.newsletters.manage none Throw away the cached DNS answers and look everything up again
generate_provider_secret POST /kahunacart/newsletters/providers/{name}/secret kahunacart.newsletters.manage name, required Mints a fresh random path secret for one of the seven email providers (smtp2go, ses, postmark, mailgun, sendgrid, resend or mailersend), writes it into the plugin's configuration, and answers the whole webhook URL to paste into that provider's dashboard
set_up_provider_webhook POST /kahunacart/newsletters/providers/{name}/setup kahunacart.newsletters.manage name, required Registers this store's webhook URL with the provider through its own API, for the delivered, bounce, spam complaint, open and click events, using the API key already in that provider's transport settings, so a merchant does not have to find the right screen in somebody else's dashboard
send_provider_webhook_test POST /kahunacart/newsletters/providers/{name}/test kahunacart.newsletters.manage name, required Sends one small test message through the site's own transport to check that the provider's webhook reports back, the same as the Send a test button on the provider's Settings card
get_provider_webhook_test GET /kahunacart/newsletters/providers/{name}/test kahunacart.newsletters.view name, required Where the provider's last webhook test stands: none (never tested), waiting, received (with the seconds it took), refused (the event arrived and its signature did not check out, nearly always a missing or wrong signing secret), or nothing (two minutes passed with no event, with url_changed when a new secret was made after Set up)
list_automations GET /kahunacart/newsletters/flows kahunacart.newsletters.view none Every automation this store has, with what starts it, how many steps it has, and where the people in it are: how many are moving, how many are waiting on a timer, how many have finished and how many left early
get_automation GET /kahunacart/newsletters/flows/{id} kahunacart.newsletters.view id required; status, page, sort, dir, q optional One automation with its steps in order, each email step's message summarized with what it has sent, opened and clicked, and a page of the people walking it — which step each of them is on, when they are due to move again, and why they left if they have
create_automation POST /kahunacart/newsletters/flows kahunacart.newsletters.manage name, trigger required; trigger_json, list_id, segment_id, allow_reentry, exit_on_purchase, steps optional Creates an automation in draft, with its steps
update_automation PATCH /kahunacart/newsletters/flows/{id} kahunacart.newsletters.manage id required; name, trigger, trigger_json, list_id, segment_id, allow_reentry, exit_on_purchase, steps optional Changes an automation's settings, its steps, or both
delete_automation DELETE /kahunacart/newsletters/flows/{id} kahunacart.newsletters.manage id required; force optional Deletes an automation, its steps and the messages its email steps own, sends and all
activate_automation POST /kahunacart/newsletters/flows/{id}/activate kahunacart.newsletters.manage id, required Starts an automation running
pause_automation POST /kahunacart/newsletters/flows/{id}/pause kahunacart.newsletters.manage id, required Holds everybody in an automation exactly where they are: no step runs, no mail goes out, nobody new is put in, and nobody is taken out
resume_automation POST /kahunacart/newsletters/flows/{id}/resume kahunacart.newsletters.manage id, required Sets a paused automation running again
enrol_in_automation POST /kahunacart/newsletters/flows/{id}/enrol kahunacart.newsletters.manage id, subscriber_id, all required Starts one subscriber on an automation by hand, under exactly the same rules a trigger applies: they have to be subscribed, on the automation's list if it names one, matching its segment if it names one, and new to it unless it allows re-entry
exit_automation_enrolment POST /kahunacart/newsletters/flows/{id}/enrolments/{eid}/exit kahunacart.newsletters.manage id, eid, all required Ends one person's walk through one automation, with "taken out by hand" against it
list_automation_recipes GET /kahunacart/newsletters/flows/recipes kahunacart.newsletters.view none The automations that come with the add-on ready to use — a welcome series, a thank you after somebody buys, a win-back — each with a key, a sentence saying what it does, its ladder written out a step a line, and what to change before switching it on
use_automation_recipe POST /kahunacart/newsletters/flows/recipes/{key} kahunacart.newsletters.manage key, required Builds a draft automation from one of the ready-made ones, with its steps and its messages already written
get_newsletters_report GET /kahunacart/newsletters/reports/{type} kahunacart.newsletters.view type required; from, to, preset, bucket, list, campaign, flow, refresh optional The Reports tab, as a call

Shipping

Setting up carriers, boxes and addresses needs kahunacart.shipping.manage. Quoting, buying and voiding a label needs kahunacart.shipping.labels, because it spends the store's money. Reading a label, its tracking or a packing preview needs kahunacart.orders.view. What each action does is on Shipping carriers.

Shipping publishes 27 tools over 29 admin routes, behind 3 permissions. An assistant calls each one by its name below with kahunacart_shipping_ in front of it.

These 2 routes are not published as tools: GET /kahunacart/shipping/labels/{id}/file, POST /kahunacart/shipping/public/webhook/{aggregator}/{secret}.

Tool Route Permission Arguments What it does
list_shipping_aggregators GET /kahunacart/shipping/aggregators kahunacart.shipping.manage none The carrier aggregator this store is configured for, with its capability matrix, whether a key has been pasted, and when the carrier list was last refreshed
check_shipping_aggregator POST /kahunacart/shipping/aggregators/{code}/check kahunacart.shipping.manage code, required Make a real call to the aggregator with the configured key and report whether it works, which account it reached and how many carrier accounts that account holds
get_shipping_webhook GET /kahunacart/shipping/aggregators/webhook kahunacart.shipping.manage none The inbound tracking webhook as this store has it: whether a secret is set, the public address it makes with the secret masked to its last four characters, whether the configured driver can register that address itself, and the last few deliveries the endpoint received
generate_shipping_webhook_secret POST /kahunacart/shipping/aggregators/webhook/secret kahunacart.shipping.manage none Generate the tracking webhook's secret and save it
register_shipping_webhook POST /kahunacart/shipping/aggregators/webhook/register kahunacart.shipping.manage reset Register this store's inbound tracking address with the aggregator
list_carrier_accounts GET /kahunacart/shipping/accounts kahunacart.shipping.manage none The carrier accounts this store holds at its aggregator — USPS, UPS, FedEx, Royal Mail, DPD and so on — with the services each one offers and whether this store offers it
sync_carrier_accounts POST /kahunacart/shipping/accounts kahunacart.shipping.manage none Read the carrier list from the aggregator again and store it, so a carrier switched on at the aggregator this morning is bindable here this afternoon
update_carrier_account PATCH /kahunacart/shipping/accounts/{id} kahunacart.shipping.manage id, enabled, all required Switch a carrier on or off for this store
list_shipping_boxes GET /kahunacart/shipping/boxes kahunacart.shipping.manage none The box catalogue, with the usable space inside each box after the packing-material allowance, and the built-in fallback box a store with no boxes is quoted for
create_shipping_box POST /kahunacart/shipping/boxes kahunacart.shipping.manage code, length_mm, width_mm, height_mm required; name, inner_length_mm, inner_width_mm, inner_height_mm, empty_weight_grams, max_weight_grams, carrier_template, enabled, position optional Add a box to the catalogue
update_shipping_box PUT /kahunacart/shipping/boxes/{id} kahunacart.shipping.manage id required; code, name, length_mm, width_mm, height_mm, inner_length_mm, inner_width_mm, inner_height_mm, empty_weight_grams, max_weight_grams, carrier_template, enabled, position optional Change a box in the catalogue
delete_shipping_box DELETE /kahunacart/shipping/boxes/{id} kahunacart.shipping.manage id, required Take a box out of the catalogue
list_shipping_locations GET /kahunacart/shipping/locations kahunacart.shipping.manage none The ship-from addresses, and whether the store has one at all — a store with none cannot be quoted by any carrier
create_shipping_location POST /kahunacart/shipping/locations kahunacart.shipping.manage code, address_1, city, country required; name, company, address_2, state, postcode, phone, email, is_default, enabled, position optional Add a ship-from address
update_shipping_location PUT /kahunacart/shipping/locations/{id} kahunacart.shipping.manage id required; code, name, company, address_1, address_2, city, state, postcode, country, phone, email, is_default, enabled, position optional Change a ship-from address
delete_shipping_location DELETE /kahunacart/shipping/locations/{id} kahunacart.shipping.manage id, required Take a ship-from address out
preview_packing GET /kahunacart/shipping/pack kahunacart.orders.view order, order_id, items What the packer would do with an order, or with a made-up basket: the boxes, what goes in each, the actual and dimensional weights, which of the two the carrier would bill, and anything the packer had to guess at
list_shipping_labels GET /kahunacart/shipping/labels kahunacart.orders.view status, order_id, sort, dir, q, page, per_page Every label this store has bought, tried to buy, or asked to have refunded, newest first
quote_order_label GET /kahunacart/orders/{id}/shipping/quote kahunacart.shipping.labels id, required Everything needed before buying a label for an order, in one call: the parcels the packer made, the box each one is in, the actual and billable weights, the services the carrier quoted with their prices and transit estimates, what the carrier makes of the delivery address, and which customs facts are missing per line
buy_shipping_label POST /kahunacart/orders/{id}/shipping/labels kahunacart.shipping.labels id required; service, notify optional Buy a label for what is still owed on an order, and record the parcel on it
void_shipping_label POST /kahunacart/shipping/labels/{id}/void kahunacart.shipping.labels id, required Ask the carrier to refund a label
record_shipping_parcel POST /kahunacart/shipping/labels/{id}/record kahunacart.shipping.labels id required; notify optional Write the shipment for a label that was bought and never recorded on its order — the state where the money is spent and the order does not know
get_label_tracking GET /kahunacart/shipping/labels/{id}/tracking kahunacart.orders.view id, required Every tracking event recorded for one parcel, oldest first, with the carrier's own wording beside each normalised status and a flag saying whether the event needs somebody to act on it
refresh_label_tracking POST /kahunacart/shipping/labels/{id}/tracking/refresh kahunacart.orders.view id, required Ask the carrier where a parcel is, now, and write anything new
validate_shipping_address POST /kahunacart/shipping/addresses/validate kahunacart.shipping.manage address1, city, country required; name, company, address2, region, postcode, phone, email, residential optional What the carrier makes of a delivery address, with its own corrected version where it offers one
get_product_customs GET /kahunacart/shipping/products/{id}/customs kahunacart.shipping.manage id required; variant_id optional The customs facts recorded against a product — its harmonised code, where it was made, and what it is called on a customs form
set_product_customs PUT /kahunacart/shipping/products/{id}/customs kahunacart.shipping.manage id required; variant_id, hs_code, origin_country, customs_description optional Record the customs facts for a product, or for one of its variants

Bookings

Reads need kahunacart.bookings.view and changes need kahunacart.bookings.manage. "What is free on Thursday" and "book Mrs Patel in at three" are both here. What each action means is on Bookings.

Bookings publishes 52 tools over 78 admin routes, behind 2 permissions. An assistant calls each one by its name below with kahunacart_bookings_ in front of it.

These 26 routes are not published as tools: GET /kahunacart/bookings/admin-slots/{id}.js, GET /kahunacart/bookings/export, POST /kahunacart/bookings/desk/bookings, GET /kahunacart/bookings/desk/bookings/{id}, POST /kahunacart/bookings/desk/bookings/{id}/{action}, GET /kahunacart/bookings/desk/customers, POST /kahunacart/bookings/desk/customers, GET /kahunacart/bookings/desk/customers/{id}, POST /kahunacart/bookings/desk/customers/{id}, GET /kahunacart/bookings/desk/diary, GET /kahunacart/bookings/desk/me, GET /kahunacart/bookings/desk/services, GET /kahunacart/bookings/desk/slots, GET /kahunacart/bookings/public/availability, POST /kahunacart/bookings/public/book, GET /kahunacart/bookings/public/channels/{token}.ics, GET /kahunacart/bookings/public/feeds/{token}.ics, POST /kahunacart/bookings/public/holds, DELETE /kahunacart/bookings/public/holds/{id}, GET /kahunacart/bookings/public/manage/{token}, POST /kahunacart/bookings/public/manage/{token}/cancel, POST /kahunacart/bookings/public/manage/{token}/claim-offer, POST /kahunacart/bookings/public/manage/{token}/reschedule, GET /kahunacart/bookings/public/me, GET /kahunacart/bookings/public/services, GET /kahunacart/bookings/public/sessions.

Tool Route Permission Arguments What it does
list_bookings GET /kahunacart/bookings kahunacart.bookings.view from, to, status, customer_id, product_id, order_id, bookable_id, conflict, page, per_page, sort, dir List bookings, newest first, with filters for a date range, a status, a customer, a product, an order or one person's column
create_booking POST /kahunacart/bookings kahunacart.bookings.manage product_id, start required; variant_id, seats, with, tz, email, name, notes, take_payment, paid optional Put somebody in the diary
read_booking GET /kahunacart/bookings/{id} kahunacart.bookings.view id, required One booking in full, with the times it is sitting on and everything that has been done to it
approve_booking POST /kahunacart/bookings/{id}/approve kahunacart.bookings.manage id, required Say yes to a booking that was waiting for the shop to agree to it
reject_booking POST /kahunacart/bookings/{id}/reject kahunacart.bookings.manage id required; reason optional Say no to a booking that was waiting
cancel_booking POST /kahunacart/bookings/{id}/cancel kahunacart.bookings.manage id required; reason optional Call a booking off and give the time back
complete_booking POST /kahunacart/bookings/{id}/complete kahunacart.bookings.manage id, required Mark a booking as done
mark_booking_no_show POST /kahunacart/bookings/{id}/no-show kahunacart.bookings.manage id required; no_show optional Record that somebody did not turn up, or take that mark off again
reschedule_booking POST /kahunacart/bookings/{id}/reschedule kahunacart.bookings.manage id, start, all required Move a booking to a different time
list_booking_holds GET /kahunacart/bookings/holds kahunacart.bookings.view limit Every time somebody is standing on right now while they finish paying
booking_overview GET /kahunacart/bookings/overview kahunacart.bookings.view from, to The diary over a span, as the owner's calendar draws it — everyone and everything that can be booked with the hours they work and the days they are away, every booking between the two instants, and how many are waiting, in conflict, no-shows or cancelled
troubleshoot_booking_slots GET /kahunacart/bookings/troubleshoot kahunacart.bookings.view product_id required; variant_id, with, date, tz optional Every candidate start time on one day for one service, with a tick or the exact reason it is not on offer — too soon, too far ahead, full, booked, held by somebody else, time off, closed, busy on another calendar, or which need failed and why
get_booking_settings GET /kahunacart/bookings/settings kahunacart.bookings.view none What this add-on is set to do, store-wide — whether the front desk is switched on and where it answers, whether a place that comes free on a full class is offered automatically or by hand and for how long it is held, what a published calendar says about each booking, how long a slot is held in the basket and at the payment page, how the shop writes a time of day, and the defaults a service falls back to when it says nothing of its own
list_booking_products GET /kahunacart/bookings/setup/products kahunacart.bookings.view none Every product of type Booking, with its options and whether a booking rule has been written for each
list_booking_accounts GET /kahunacart/bookings/setup/accounts kahunacart.bookings.view none The sign-in accounts a person can be tied to, so somebody who works here can be given the account they use at the front desk
list_bookables GET /kahunacart/bookings/setup/bookables kahunacart.bookings.view enabled_only, type Everyone and everything whose time can be taken — the people, the rooms, the equipment — with how many identical ones there are of each
create_bookable POST /kahunacart/bookings/setup/bookables kahunacart.bookings.manage type, slug required; name, units, timezone, schedule_id, location_id, user_id, enabled, position optional Add somebody or something whose time can be booked
update_bookable PATCH /kahunacart/bookings/setup/bookables/{id} kahunacart.bookings.manage id required; name, units, timezone, schedule_id, location_id, user_id, enabled, position optional Change somebody or something that can be booked
delete_bookable DELETE /kahunacart/bookings/setup/bookables/{id} kahunacart.bookings.manage id, required Remove somebody or something that can be booked
list_booking_schedules GET /kahunacart/bookings/setup/schedules kahunacart.bookings.view none The named sets of working hours, each with its own weekly pattern and the dates that are different
create_booking_schedule POST /kahunacart/bookings/setup/schedules kahunacart.bookings.manage slug required; name, timezone, days, overrides optional Add a set of working hours
update_booking_schedule PATCH /kahunacart/bookings/setup/schedules/{id} kahunacart.bookings.manage id required; name, timezone, days, overrides optional Change a set of working hours
delete_booking_schedule DELETE /kahunacart/bookings/setup/schedules/{id} kahunacart.bookings.manage id, required Remove a set of working hours
list_booking_time_off GET /kahunacart/bookings/setup/time-off kahunacart.bookings.view bookable_id, required The whole days one person or thing is away
create_booking_time_off POST /kahunacart/bookings/setup/time-off kahunacart.bookings.manage bookable_id, from, to required; note optional Book somebody or something out for whole days
update_booking_time_off PATCH /kahunacart/bookings/setup/time-off/{id} kahunacart.bookings.manage id required; from, to, note optional Change a stretch of time off
delete_booking_time_off DELETE /kahunacart/bookings/setup/time-off/{id} kahunacart.bookings.manage id, required Take a stretch of time off back out, so those days can be booked again
list_booking_locations GET /kahunacart/bookings/setup/locations kahunacart.bookings.view none Where bookings happen, each with its address and its own hours
create_booking_location POST /kahunacart/bookings/setup/locations kahunacart.bookings.manage slug required; name, address, timezone, schedule_id optional Add a place bookings happen
update_booking_location PATCH /kahunacart/bookings/setup/locations/{id} kahunacart.bookings.manage id required; name, address, timezone, schedule_id optional Change a place bookings happen
delete_booking_location DELETE /kahunacart/bookings/setup/locations/{id} kahunacart.bookings.manage id, required Remove a place bookings happen
list_booking_services GET /kahunacart/bookings/setup/services kahunacart.bookings.view none Every product the shop books, with how long it takes, how often a start is offered, what it needs, and the product it hangs off
save_booking_service PUT /kahunacart/bookings/setup/services/{productId} kahunacart.bookings.manage productId required; duration, slot_interval, buffer_before, buffer_after, capacity, party_size, names_say_length, notice_hours, window_days, cutoff_hours, workflow, payment, mode, schedule_id, location_id, enabled optional Write the booking rules for a product — how long, how often, how much notice, how far ahead, and whether it is confirmed straight away or waits for the shop to agree
save_booking_variant_service PUT /kahunacart/bookings/setup/services/{productId}/{variantId} kahunacart.bookings.manage productId, variantId required; duration, slot_interval, buffer_before, buffer_after, capacity, party_size, names_say_length, notice_hours, window_days, cutoff_hours, workflow, payment, schedule_id, location_id, enabled optional Write the booking rules for one option of a product, replacing the product's own rule for that option only
delete_booking_service DELETE /kahunacart/bookings/setup/services/{id} kahunacart.bookings.manage id, required Stop booking a product, or stop one of its options having a rule of its own
list_booking_needs GET /kahunacart/bookings/setup/services/{serviceId}/needs kahunacart.bookings.view serviceId, required What a service occupies — one groomer from Ann or Bo with the customer choosing, and the tub with no choice — each with the pool it draws on
create_booking_need POST /kahunacart/bookings/setup/services/{serviceId}/needs kahunacart.bookings.manage serviceId, label required; units, customer_chooses, position, bookables optional Say that a service occupies one more thing
update_booking_need PATCH /kahunacart/bookings/setup/needs/{id} kahunacart.bookings.manage id required; label, units, customer_chooses, position, bookables optional Change what a service occupies, or who is in the pool
delete_booking_need DELETE /kahunacart/bookings/setup/needs/{id} kahunacart.bookings.manage id, required Stop a service occupying something
create_bookable_feed_token POST /kahunacart/bookings/setup/bookables/{id}/feed-token kahunacart.bookings.manage id, required Make a calendar link for one person, room or piece of equipment, so it can be subscribed to from a phone
delete_bookable_feed_token DELETE /kahunacart/bookings/setup/bookables/{id}/feed-token kahunacart.bookings.manage id, required Take a calendar link away with nothing put back
create_bookable_channel_link POST /kahunacart/bookings/setup/bookables/{id}/channel-token kahunacart.bookings.manage id, required Make the calendar link a channel gets — Airbnb, VRBO, Booking.com — for one property, room or pitch
delete_bookable_channel_link DELETE /kahunacart/bookings/setup/bookables/{id}/channel-token kahunacart.bookings.manage id, required Take a channel link away with nothing put back
test_bookable_busy_feed POST /kahunacart/bookings/setup/bookables/{id}/busy-feeds/test kahunacart.bookings.manage id, url required; days optional Fetch an outside calendar right now and say how many busy times are in it over the next few weeks, or what went wrong in plain words
list_booking_sessions GET /kahunacart/bookings/sessions kahunacart.bookings.view days Every class and workshop the shop runs, with the dates coming up, how many places are left on each and whether a date is open, full or closed
get_booking_session GET /kahunacart/bookings/sessions/{serviceId} kahunacart.bookings.view serviceId required; from, to, days optional One class, its recurrence rule and its exceptions, with every date in the range and the places left on each
get_session_register GET /kahunacart/bookings/sessions/{serviceId}/register kahunacart.bookings.view serviceId required; occurrence optional Who is coming on one date of a class and who is waiting behind them, in the order places would be offered
run_session_waitlist POST /kahunacart/bookings/sessions/{serviceId}/promote kahunacart.bookings.manage serviceId required; occurrence optional Give away or offer every place that has come free on one date of a class
cancel_session_date POST /kahunacart/bookings/sessions/{serviceId}/cancel-date kahunacart.bookings.manage serviceId required; occurrence, reason optional Call one date of a class off
mark_session_room POST /kahunacart/bookings/sessions/{serviceId}/mark kahunacart.bookings.manage serviceId required; occurrence, action optional Mark everybody on one date of a class at once, as having come or as not having come
offer_booking_seat POST /kahunacart/bookings/{id}/offer kahunacart.bookings.manage id, required Hold a place for one person who is waiting and tell them it is theirs if they pay for it
promote_booking POST /kahunacart/bookings/{id}/promote kahunacart.bookings.manage id, required Give one person who is waiting a place outright, whatever the class costs

Rentals

Rentals uses the Bookings permissions: kahunacart.bookings.view to read a rate card or quote a stay, kahunacart.bookings.manage to change one. Anything a guest does, such as asking what is free next August, is a Bookings tool. See Rentals.

Rentals publishes 9 tools over 9 admin routes, behind 2 permissions. An assistant calls each one by its name below with kahunacart_rentals_ in front of it.

Tool Route Permission Arguments What it does
list_rental_properties GET /kahunacart/rentals/properties kahunacart.bookings.view none Every property in this store that books by the night: its Bookings service id, what the product and option are called, how many it sleeps, the zone its dates are in, what a night costs in the store's own currency before any rate band, and how many bands its rate card has
get_rental_rules GET /kahunacart/rentals/rules/{serviceId} kahunacart.bookings.view serviceId, required One property's stay rules: what is stored, the same thing with the defaults filled in, how many it sleeps, its timezone and what it is called
save_rental_rules PUT /kahunacart/rentals/rules/{serviceId} kahunacart.bookings.manage serviceId required; check_in_time, check_out_time, min_nights, max_nights, arrival_days, departure_days, guests_included, cleaning_fee_minor, extra_guest_minor, damage_deposit_minor, monthly_minor, avoid_gaps, deposit_percent, balance_days_before, pre_arrival_text, currency_amounts optional Change a property's stay rules — check-in and check-out times, the shortest and longest stay, which weekdays a stay may start and end on, how many guests the nightly rate covers, the fees, the deposit terms, the cancellation policy and the pre-arrival note
list_rental_rates GET /kahunacart/rentals/rates kahunacart.bookings.view service_id, required One property's rate card: every dated band in position order, lowest first, with the property's own nightly price and currency beside them
create_rental_rate POST /kahunacart/rentals/rates kahunacart.bookings.manage service_id, starts_on, ends_on required; label, nightly_minor, percent, weekend_minor, weekend_days, min_nights, arrival_days, position, currency_amounts optional Add a dated band to a property's rate card
update_rental_rate PATCH /kahunacart/rentals/rates/{id} kahunacart.bookings.manage id required; starts_on, ends_on, label, nightly_minor, percent, weekend_minor, weekend_days, min_nights, arrival_days, position, currency_amounts optional Change one band on a rate card
delete_rental_rate DELETE /kahunacart/rentals/rates/{id} kahunacart.bookings.manage id, required Take one band off a rate card
reorder_rental_rates POST /kahunacart/rentals/rates/reorder kahunacart.bookings.manage service_id, ids, all required Renumber one property's rate bands in the order the ids are given, which is what decides who wins a night two bands both cover
quote_rental_stay GET /kahunacart/rentals/quote kahunacart.bookings.view product_id, arrival, departure required; variant_id, guests, exclude_booking_id, currency optional What a stay at a property would come to — every night with the band it fell in, the extra guests, the cleaning, the total, what is due today and when the balance falls due

Refresh after installing a plugin

Install or enable a plugin while the assistant is connected, then ask it to run refresh_plugin_tools. It re-reads every manifest and updates the tool list in place: new tools added, changed ones updated, tools from a disabled or removed plugin dropped. It reports added, updated, removed and any manifest warnings, and tells the client the list changed.

No restart is required. refresh_plugin_tools requires only api.access.

Note

refresh_plugin_tools still works when plugin tools were started with --plugin-tools none. It reports that they are disabled rather than loading them.

Write tools for your own plugin

Any Grav plugin can publish its own routes. Add an mcp.yaml in the plugin root, beside blueprints.yaml and permissions.yaml. Describe each route with a name, a description written for the model, the method and path, the permission it enforces, and a JSON Schema for the arguments. The API plugin serves it and grav-mcp registers it, with no code on either side.

YAML
version: 1
prefix: kahunacart          # optional; defaults to the plugin slug. Tool name = "{prefix}_{name}"
tools:
  - name: list_products
    title: List products
    description: >
      List catalog products with paging, search and status filters. Returns product rows with their
      variants and attributes. Use get_product for one product with everything on it.
    method: GET
    path: /kahunacart/products
    permission: kahunacart.products.manage
    annotations:
      readOnly: true
    input:
      type: object
      properties:
        q: { type: string, description: "Search title, slug or SKU" }
        status: { type: string, enum: [draft, published, archived] }
        page: { type: integer, minimum: 1, default: 1 }
        per_page: { type: integer, minimum: 1, maximum: 100, default: 20 }

  - name: update_product
    title: Update a product
    description: Change one or more fields of a product. Only the fields sent are changed.
    method: PATCH
    path: /kahunacart/products/{id}
    permission: kahunacart.products.manage
    annotations:
      idempotent: true
    input:
      type: object
      required: [id]
      properties:
        id: { type: integer, description: "Product id" }
        title: { type: string }
        status: { type: string, enum: [draft, published, archived] }

Manifest fields

Key Required Meaning
version yes Manifest format version. Only 1 exists
prefix no Tool-name prefix. Defaults to the plugin slug with - replaced by _
tools[].name yes ^[a-z][a-z0-9_]*$. The final name is {prefix}_{name} and must be 64 characters or fewer
tools[].title no Human title an MCP client shows
tools[].description yes What the tool does and returns, when to use it, and anything the model must know to call it well. One to four sentences. Do not repeat the permission; the loader appends [Requires: <permission>] itself
tools[].method yes GET, POST, PATCH, PUT or DELETE
tools[].path yes Route path relative to the API base, starting with /. Each {name} placeholder must exist in input.properties and is treated as required
tools[].permission no The permission the route enforces. A caller who lacks it never sees the tool
tools[].annotations no readOnly, destructive and idempotent booleans
tools[].input no A JSON Schema object in the subset below. Omit for a tool with no arguments
tools[].query no For POST, PATCH, PUT and DELETE: the properties to send as query-string parameters instead of in the body. GET sends every non-path property as a query parameter

Annotations default by method: GET is read-only and idempotent, DELETE destructive and idempotent, PUT and PATCH idempotent, POST none of them. Setting a key overrides that one default. Only JSON bodies work, so leave multipart routes out.

The JSON Schema subset

grav-mcp converts input to a validator at load time, so use only what the converter understands.

Allowed per property: type (string, integer, number, boolean, array, object), description, default, enum, nullable, minimum, maximum, minLength, maxLength, pattern, format (date, date-time, email, uri), items for arrays, and properties with required and additionalProperties for nested objects. An object with no properties is a free-form map.

Not allowed: $ref, oneOf, anyOf, allOf, not, if/then, tuple items, patternProperties, const and dependencies. A tool that uses one of these is skipped and reported under warnings.

Tools built in code

If your tool list depends on data rather than a file, add entries in code. The API plugin fires onApiMcpTools with an McpToolCollector under $event['tools']:

PHP
public function onApiMcpTools(Event $event): void
{
    $event['tools']->add('kahunacart', [
        'name' => 'sync_stripe',
        'description' => 'Push the catalog to Stripe.',
        'method' => 'POST',
        'path' => '/kahunacart/providers/stripe/sync',
        'permission' => 'kahunacart.settings',
    ]);
}

The first argument is your plugin slug. Entries are validated like manifest entries. Manifest files are read first, so a name your mcp.yaml already claimed wins and the duplicate is dropped with a warning.

What the endpoint answers

GET /mcp/tools requires api.access and returns the usual response envelope with three keys inside data:

Key What it holds
tools One entry per tool the caller may see, with annotations fully populated and input_schema always present
plugins One entry per enabled plugin that contributed tools: slug, name, version, and how many of its tools this caller can see
warnings Every entry that was skipped and why. Safe to show to any authenticated caller; it holds no secrets

data.fingerprint hashes the enabled-plugin set and the manifest mtimes and is sent as the ETag, so a client can tell whether anything changed without diffing. A YAML parse error becomes one warning for that plugin, and the rest of the response is still served.

The full contract is in the API plugin README.

Troubleshooting

Symptom Cause Fix
No kahunacart_ tools in the assistant's list grav-mcp could not load plugin tools at startup, or they are switched off Read grav-mcp's stderr for the warning. Check that --plugin-tools is not none, and that a slug list includes kahunacart. Then run refresh_plugin_tools
Warning: Failed to load plugin tools: … Plugin tools need the 'api.access' permission. The key's account has no api.access, so GET /mcp/tools answered 401 or 403 Grant api.access to that account, or use a key from an account that has it. A key created with a scope list is capped to those scopes whatever the account may do
Warning: Failed to load plugin tools: this site's API plugin has no /mcp/tools endpoint… The API plugin predates 1.0.22, so /mcp/tools answers 404 Update the API plugin, then run refresh_plugin_tools
Some kahunacart_ tools are there and others are not The key holds some kahunacart.* permissions and not others Grant the missing permission. The tool list is filtered per caller
A tool answers 403 The key lacks that permission, or the store has demo.readonly on and the tool writes Grant the permission, or turn demo.readonly off on a store that is not a demo
delete_attribute answers 409 Products still carry a value for that attribute. The message names the count Send force: true to delete the values too, or clear them first
No tools from a plugin you just enabled grav-mcp registered its tools at startup, before the plugin existed Run refresh_plugin_tools. Only enabled plugins are read