Custom fields, settings and zones
The extension platform: custom fields, the add-on settings store, admin zones, schema extensions, hook points, product revisions and part payment.
What an add-on uses to put its own data and its own screens into a KahunaCart store: field definitions on any entity, the values stored under them, a namespaced settings store, the zones a widget can mount into, the schema extensions and hook points a plugin registers against, and the payment routes that act on a stored transaction or token. The product revision, schedule, prerequisite and part-payment routes are filed here too, because each of them is a record kept beside a core row rather than a change to one. See Custom fields and the settings store and Admin sections.
List custom fields
/api/v1/kahunacart/custom-fields
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. Also lists the entities fields may hang off and the types and visibilities a new field may declare.
Parameters
| Name | Type | Description |
|---|---|---|
| entity optional | string | Only the fields on this entity. |
Create a custom field
/api/v1/kahunacart/custom-fields
Define a custom field on an entity. The name is lowercase snake_case and unique on the entity; the type is one of text, textarea, integer, boolean, select or json and cannot change afterwards; visibility decides where values travel — public reaches templates and the API, readonly reaches templates but cannot be written through the API, internal never reaches a template. A select needs validate.options; an integer may carry validate.min and validate.max; text may carry validate.max_length and validate.pattern.
Parameters
| Name | Type | Description |
|---|---|---|
| entity required | string |
One of:
product, variant, order, order_item, customer, address |
| name required | string | |
| type optional | string |
One of:
text, textarea, integer, boolean, select, json |
| label_key optional | string | A translation key or a plain label. |
| help_key optional | string | |
| visibility optional | string |
One of:
public, readonly, internal |
| sortable optional | boolean | |
| filterable optional | boolean | |
| required optional | boolean | |
| default optional | string | |
| validate optional | object | |
| position optional | integer |
Update a custom field
/api/v1/kahunacart/custom-fields/{id}
Change a custom field's label, help, visibility, sortable and filterable flags, required flag, default, validation or position. The entity, the name and the type cannot change, because the values already stored were written against them.
Parameters
| Name | Type | Description |
|---|---|---|
| id required | integer | |
| label_key optional | string | |
| help_key optional | string | |
| visibility optional | string |
One of:
public, readonly, internal |
| sortable optional | boolean | |
| filterable optional | boolean | |
| required optional | boolean | |
| default optional | string | |
| validate optional | object | |
| position optional | integer |
Delete a custom field
/api/v1/kahunacart/custom-fields/{id}
Delete a custom field definition and every value stored under it, on every row of its entity. The answer says how many values went with it. A field an add-on declares comes back on the next request unless the add-on stops declaring it.
Parameters
| Name | Type | Description |
|---|---|---|
| id required | integer |
Read custom field values
/api/v1/kahunacart/custom-fields/values/{entity}/{entityId}
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. Internal fields are included: this is the admin's view, not a template's.
Parameters
| Name | Type | Description |
|---|---|---|
| entity required | string | |
| entityId required | integer |
Write custom field values
/api/v1/kahunacart/custom-fields/values/{entity}/{entityId}
Set custom field values on one row of an entity. Every value is checked against its field's type and validation before any is written, an unknown field name is refused, a readonly field is refused, and null clears a value so its default answers again. Needs the permission that writes the row — products.manage for a product or variant, orders.manage for an order or line, customers.manage for a customer or address.
Parameters
| Name | Type | Description |
|---|---|---|
| entity required | string | |
| entityId required | integer | |
| values required | object | Field name to value. |
Read a settings namespace
/api/v1/kahunacart/settings-store/{namespace}
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. Only the keys the caller's permissions allow are returned; a per-user key answers the caller's own value. A namespace no add-on has registered is a 404. The base plugin's own namespace is layout, which holds each zone's widget order and the caller's dev-mode switch.
Parameters
| Name | Type | Description |
|---|---|---|
| namespace required | string |
Write a settings namespace
/api/v1/kahunacart/settings-store/{namespace}
Write one or more keys in a namespace of the add-on settings store. Each key's own permission is checked and the whole write is refused if any fails; each value goes through the key's validator and the whole write is refused if any is rejected. Reorder a zone's widgets by writing layout with the zone id as the key and the widget ids as the value; switch dev mode on for yourself with dev_mode: true in the same namespace.
Parameters
| Name | Type | Description |
|---|---|---|
| namespace required | string | |
| values required | object | Key to value. |
List extension zones
/api/v1/kahunacart/zones
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. Zones an add-on declared on its own screens follow the core ones.
List schema extensions
/api/v1/kahunacart/schema-extensions
What every installed add-on has declared it adds to KahunaCart's own responses, and where. Each registration names the extension point, the namespace the data lands under inside the reserved extensions object, the plugin that owns that namespace, and every key it promises with its declared type. Check conflicts first. If two plugins claim the same namespace the second one is refused rather than overwritten, and the entry names both plugins, so you can tell from here which add-on's data never reaches a client without going near a log file.
List compensating hook points
/api/v1/kahunacart/hook-points
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. Check non_compensating. If a handler has no undo and a later step fails, what that handler did stays done and somebody has to undo it by hand. runs counts what has run, what has been undone, and what failed on the way.
List a product's revisions
/api/v1/kahunacart/products/{id}/revisions
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. A row with pending: true is a queued change that has not happened yet. It describes the future rather than the past, and it is never pruned. Snapshots are not included here because they are large. restore_product_revision fetches the one it applies.
Parameters
| Name | Type | Description |
|---|---|---|
| id required | integer | Product id |
Restore a product revision
/api/v1/kahunacart/products/{id}/revisions/{revision}/restore
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. Two things are left alone on purpose: stock on a variant that still exists, because putting it back would un-sell whatever has sold since, and product files and releases, because they point at files on disk. The restore writes a NEW revision rather than rewinding history, so the state you restored from is still there to go back to. Refused with 409 if the revision's slug now belongs to another product.
Parameters
| Name | Type | Description |
|---|---|---|
| id required | integer | Product id |
| revision required | integer | The revision number to restore, as listed by list_product_revisions |
Response
Answers with the restored product and the number of the new revision the restore wrote. A revision whose slug now belongs to another product is a 409 naming it.
Duplicate a product
/api/v1/kahunacart/products/{id}/duplicate
Copy a product into a new draft and answer with the copy, in the same form create_product answers. The copy carries the description, type, visibility, SEO fields, quantity rules, tax and shipping settings, variants with their option combinations and prices, option axes and values, option prices, categories, tags, attribute values, custom-field values, customer-group restrictions, related products, prerequisites, kit items, price-list prices, quantity tiers, translations and the media — the picture files themselves are copied, not shared. It is titled "Copy of <title>" with a slug derived from that, it is a draft whatever the original was, and its publish and unpublish dates are cleared. Each copied variant comes out with no SKU, so the store's own SKU template mints one, and with zero stock. Downloadable files and releases do not come across, and neither do reviews, sales figures, history, revisions, wishlist entries or download grants.
Parameters
| Name | Type | Description |
|---|---|---|
| id required | integer | Id of the product to copy |
Set a product's publish window
/api/v1/kahunacart/products/{id}/schedule
Give a product a time to go live and a time to come down, as UTC epoch seconds. A product given a future publish time is put into draft on the spot, so scheduling it for next Tuesday does not leave it live for the rest of today. The background worker publishes it at its time and marks it dirty on the catalog index in the same tick. Send null to clear either date. Leave a key out and that date is untouched, so setting a take-down date never silently cancels a publish date you already set.
Parameters
| Name | Type | Description |
|---|---|---|
| id required | integer | Product id |
| publish_at optional | integer or null | UTC epoch seconds to publish at; null clears it |
| unpublish_at optional | integer or null | UTC epoch seconds to take it down at; null clears it |
List a product's prerequisites
/api/v1/kahunacart/products/{id}/prerequisites
What a customer has to have bought before they may buy this product. Each row names the required product by id and carries its current title and slug, so an editor can draw the list without a second request per row. A required that comes back null means the product has since been deleted. The rule is broken, and the storefront refuses the add to cart rather than treating a broken rule as no rule.
Parameters
| Name | Type | Description |
|---|---|---|
| id required | integer | Product id |
Set a product's prerequisites
/api/v1/kahunacart/products/{id}/prerequisites
Replace the list wholesale. An empty list clears it. mode is any — one completed purchase from the list is enough, which is the upgrade path from three earlier editions — or all, which is part three of a course. Only a COMPLETED order satisfies a prerequisite: pending and cancelled orders do not, and which statuses count is read off the order-status registry's complete flag rather than off the word "completed", so a merchant's own Dispatched counts.
Parameters
| Name | Type | Description |
|---|---|---|
| id required | integer | Product id |
| mode optional | string |
Whether one completed purchase from the list is enough, or every one is needed
One of:
any, allDefault:
any |
| prerequisites required | array of object | The required products, in the order they should be listed. Keys: `required_product_id`, `message_key` |
List payment collections
/api/v1/kahunacart/payment-collections
The orders that have been part paid, with what each owes, what has been authorised, captured and refunded, and what is still outstanding. A collection is open while money is still owed, settled when everything owed has been captured, and refunded when everything captured has gone back. An order is NOT counted as paid until its balance is paid. The payment status sits at partially_paid, which has counts_as_paid turned off, so no report treats a deposit as a completed sale.
Parameters
| Name | Type | Description |
|---|---|---|
| status optional | string |
Only collections in this state
One of:
open, settled, refunded, voided |
| page optional | integer | At least 1. |
| per_page optional | integer | At least 1, at most 200. |
| sort optional | string |
A column to sort every row by before it is paged; leave out for the usual order
One of:
order, status, owed, captured, refunded, balance, updated |
| dir optional | string |
One of:
asc, descDefault:
asc |
| q optional | string | Words to search for in the order number and the email on the order; every word has to be there, in any order. Applied before the sort and the page, and total counts what it found |
Take a deposit on an order
/api/v1/kahunacart/orders/{id}/deposit
Open a payment collection on one order and record the deposit session against it. Give amount as a decimal string in the store currency, or percent as a decimal string, or neither — in which case the store's own policy decides, reading the products on the order first and falling back to checkout.deposits.default_percent. Idempotent on the order. Asking twice returns the collection that already exists rather than opening a second one. Refused with 409 if checkout.deposits.enabled is off.
Parameters
| Name | Type | Description |
|---|---|---|
| id required | integer | Order id |
| amount optional | string | The deposit as a decimal amount in the store currency. Wins over percent |
| percent optional | string | The deposit as a percentage of the order total, as a decimal string |
| provider optional | string | Which gateway the deposit is taken through; the order's own provider by default |
Ask for the balance on a part-paid order
/api/v1/kahunacart/orders/{id}/collect-balance
Send the balance request email to a part-paid order's customer and record the send on the order. Opens the balance session if one is not open already, queues the message, and answers with the signed pay link the message carries so you can read it out over the phone. The link charges whatever is outstanding rather than the order total, so there is one payment route for the deposit and for the balance. Refused with 409 on an order with no deposit on it, on one that has been paid in full, and when the balance request email is switched off on the Notifications screen. Refused with 422 when the order carries no email address.
Parameters
| Name | Type | Description |
|---|---|---|
| id required | integer | Order id |
Capture an authorised payment
/api/v1/kahunacart/transactions/{id}/capture
Take some or all of the money a provider is holding against an order. The transaction id is the authorisation's ledger row, on authorization in get_order. Omit the amount to take the whole authorised amount; give amount_minor or amount to take less, and the provider releases the rest. The order then counts as paid: the payment-received email goes out, onKahunaCartOrderPaid and onKahunaCartPaymentCaptured fire, and download grants are issued. Refused, with a reason and before the provider is called, when the amount exceeds what was authorised, the authorisation was already captured or voided, or the provider cannot capture through its API.
Parameters
| Name | Type | Description |
|---|---|---|
| id required | integer | Authorisation transaction id, from get_order |
| amount_minor optional | integer | How much to take, in minor units. Omit to take the whole authorised amount. |
| amount optional | string | The same as a decimal in the order currency, for example "12.50". |
Void an authorised payment
/api/v1/kahunacart/transactions/{id}/void
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. Refused, with a reason and before the provider is called, when the authorisation was already captured (refund it instead), already voided, or the provider cannot void through its API.
Parameters
| Name | Type | Description |
|---|---|---|
| id required | integer | Authorisation transaction id, from get_order |
| reason optional | string | Recorded on the ledger row and in the order history |
List a customer's saved payment methods
/api/v1/kahunacart/customers/{id}/payment-tokens
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. Never a card number — the store holds none. available says whether any registered provider can keep a method at all.
Parameters
| Name | Type | Description |
|---|---|---|
| id required | integer | Customer id |
Remove a saved payment method
/api/v1/kahunacart/payment-tokens/{id}
Ask the provider to forget a stored payment method, then retire the store's own record of it. Fails, and leaves the record standing, when the provider refuses or its plugin is not enabled — the method is never shown as gone while the platform can still charge it.
Parameters
| Name | Type | Description |
|---|---|---|
| id required | integer | Saved payment method id, from list_payment_tokens |
Get a zone widget script
/api/v1/kahunacart/zones/widgets/{id}.js
Serve the JavaScript for one mounted zone widget, as the bytes are on disk
Writing a custom field value takes the permission that writes the row it sits on: kahunacart.products.manage for a product or variant, kahunacart.orders.manage for an order or an order line, kahunacart.customers.manage for a customer or an address, and kahunacart.settings for an entity an add-on registered.
Every value in a settings-store write is checked before any is written, and the whole write is refused if one fails.
GET /kahunacart/schema-extensions answers with every registration an add-on made through onKahunaCartRegisterSchemaExtensions: the extension point, the namespace, the owning plugin and every declared key with its type, plus the namespaces in use and any clash that was refused. Read conflicts first. A plugin whose namespace was refused is running with none of its data reaching a client, and nothing else in the store says so. Everything an add-on returns lands inside one reserved extensions object keyed by its namespace, so no key KahunaCart documents ever moves.
GET /kahunacart/hook-points answers with the five points a compensating hook can attach to (order_completed, payment_captured, fulfillment_created, order_canceled and order_refunded), the handlers on each, which of them declared no undo under non_compensating, and how many runs are in each state. A handler with no compensation is a decision rather than an omission: when a later step in the same flow fails, what it did stays done and somebody has to undo it by hand. There is one run row per point, handler and subject, and a run row is never deleted when its compensation runs, because "this ran and was then undone" is a different fact from "this never ran".
The revision routes read and restore the snapshot KahunaCart takes on every product save, capped at catalog.revisions.keep. A restore puts every row the product owns back with its ids and writes a new revision recording the restore, so history is never rewound. Stock is never restored onto a variant that still exists, and files and releases are not restored at all. A restore whose slug now belongs to a different product answers 409. PUT /kahunacart/products/{id}/schedule sets publish_at and unpublish_at as UTC epoch seconds, and setting a future publish_at moves the product to Draft now rather than leaving it live until then. Prerequisites are replaced wholesale, an empty list clears them, and only an order at a status flagged complete satisfies one. All five need kahunacart.products.manage.
GET /kahunacart/payment-collections lists the part-paid orders with what each still owes, and POST /kahunacart/orders/{id}/deposit opens a collection and takes the deposit. Leave the amount out to use the store's own policy, or send an amount or a percent. The call is idempotent on the order, because an order owes what it owes however many times somebody presses the button. A part-paid order is not a paid order: its payment status is Part paid, whose counts_as_paid flag is false, so no report and no fulfillment queue has to know deposits exist to get the right answer. The balance is collected through the pay link the order already has. Both routes need checkout.deposits.enabled.