{
  "_comment": "Generated by scripts/gen-docs.py from the canonical Markdown sources. Structured companion to llms-full.txt. Do not hand-edit.",
  "entities": {
    "Coupon": {
      "fields": [
        {
          "name": "id",
          "type": "integer",
          "settable": false,
          "notes": "Omit to create."
        },
        {
          "name": "code",
          "type": "string",
          "settable": true,
          "notes": "Human code, unique per shop (renamable)."
        },
        {
          "name": "created / updated",
          "type": "string",
          "settable": false,
          "notes": "RFC3339, read-only."
        },
        {
          "name": "type",
          "type": "string",
          "settable": true,
          "notes": "`fixed` or `percent`."
        },
        {
          "name": "value",
          "type": "integer",
          "settable": true,
          "notes": "**`fixed`: cents. `percent`: basis points** (10000 = 100%)."
        },
        {
          "name": "min_order",
          "type": "integer",
          "settable": true,
          "notes": "Minimum order subtotal in cents."
        },
        {
          "name": "max_uses",
          "type": "integer",
          "settable": true,
          "notes": "`0` = unlimited."
        },
        {
          "name": "uses",
          "type": "integer",
          "settable": false,
          "notes": "Redemption count (platform-maintained)."
        },
        {
          "name": "products",
          "type": "[]integer",
          "settable": true,
          "notes": "Restrict to these product ids."
        },
        {
          "name": "exclude_products",
          "type": "[]integer",
          "settable": true,
          "notes": "Never discount these product ids. Applied **after** `products`/`tags`/`attrs` and outranking them, so an excluded product stays out however it qualified \u2014 \"everything tagged `sale` except this one\". Also suppresses the product's offer badge, so nothing is advertised that the checkout would refuse. Listing the same id in both `products` and `exclude_products` is rejected on save (it could only ever discount nothing). Duplicates are collapsed; absent when empty."
        },
        {
          "name": "tags",
          "type": "[]string",
          "settable": true,
          "notes": "Restrict to products with these tags."
        },
        {
          "name": "attrs",
          "type": "[]object",
          "settable": true,
          "notes": "Restrict to products carrying an attribute: `[{ name, value }]`. Entries with the same `name` are alternatives (the item matches **any** of them); entries with different names must **all** be satisfied \u2014 `[{name:\"Brand\",value:\"Acme\"},{name:\"Material\",value:\"Gold\"},{name:\"Material\",value:\"Silver\"}]` selects Acme items in gold or silver. Names are grouped case-insensitively but each entry matches the attribute name exactly. Omit `value` to match any value of that attribute. Matched against the product's attributes plus those of the line's selected variant. Combined with `products`/`tags` it narrows them (the item must satisfy both). `name` is required. Absent when empty."
        },
        {
          "name": "price_levels",
          "type": "[]string",
          "settable": true,
          "notes": "Restrict the coupon to shoppers on these customer price levels. Absent/empty = every shopper. **`\"\"` is the regular price** \u2014 a guest, or a customer with no level \u2014 because that is the value `Customer.price_level` holds for them; so `[\"\", \"wholesale\"]` means \"regular and wholesale customers, nobody else\". A shopper outside the list is refused the code and never shown the offer."
        },
        {
          "name": "start",
          "type": "string\\",
          "settable": true,
          "notes": "RFC3339 valid-from. Absent when unset."
        },
        {
          "name": "end",
          "type": "string\\",
          "settable": true,
          "notes": "RFC3339 valid-until. Absent when unset."
        },
        {
          "name": "active",
          "type": "boolean",
          "settable": true,
          "notes": ""
        },
        {
          "name": "passive",
          "type": "boolean",
          "settable": true,
          "notes": "Auto-apply at checkout."
        },
        {
          "name": "exclusive",
          "type": "boolean",
          "settable": true,
          "notes": "Cannot combine with other coupons."
        },
        {
          "name": "featured",
          "type": "boolean",
          "settable": true,
          "notes": "Shown on storefront."
        },
        {
          "name": "meta",
          "type": "object",
          "settable": true,
          "notes": "Free-form plugin storage (opaque, **not** queryable) \u2014 **except** keys beginning with `_`, which derive the queryable `index` field below. Absent when empty. Merged by key on save, never replaced wholesale. See Plugins.md \u2192 *Attaching plugin data (`.meta`)*."
        },
        {
          "name": "index",
          "type": "[]string",
          "settable": false,
          "notes": "Read-only, derived from `meta` keys that start with `_` (`meta._foo = \"bar\"` \u2192 `\"meta#foo#bar\"`) \u2014 same rules as Product's `index`. Filter it to resolve/enumerate coupons by a hidden marker: `sw.coupons.list({ filters: { index: \"meta#extid#A123\" } })` (equality, standalone \u2014 no `order`, no composite index)."
        }
      ]
    },
    "Custom records": {
      "fields": [
        {
          "name": "id",
          "type": "integer",
          "settable": false,
          "notes": "Omit to create."
        },
        {
          "name": "kind",
          "type": "string",
          "settable": false,
          "notes": "The record type id (e.g. `demo_record`). Read-only."
        },
        {
          "name": "created / updated",
          "type": "string",
          "settable": false,
          "notes": "RFC3339, read-only."
        },
        {
          "name": "(declared fields)",
          "type": "per manifest",
          "settable": false,
          "notes": "`string` / `number` / `boolean` / `json` per the `custom_records[].fields[].type` you declared."
        }
      ]
    },
    "Customer": {
      "fields": [
        {
          "name": "id",
          "type": "integer",
          "settable": false,
          "notes": "Omit to create."
        },
        {
          "name": "created / updated",
          "type": "string",
          "settable": false,
          "notes": "RFC3339, read-only."
        },
        {
          "name": "email",
          "type": "string",
          "settable": true,
          "notes": "Required, unique per shop; lowercased/validated on save."
        },
        {
          "name": "name",
          "type": "string",
          "settable": true,
          "notes": ""
        },
        {
          "name": "type",
          "type": "string",
          "settable": true,
          "notes": "`lead` (default) or `customer`. Other values fail the save."
        },
        {
          "name": "price_level",
          "type": "string",
          "settable": true,
          "notes": "Names a price-level / `product.prices` key for B2B/tier pricing; empty = retail. Absent when empty."
        },
        {
          "name": "addresses",
          "type": "[][Address](#address)",
          "settable": true,
          "notes": ""
        },
        {
          "name": "cart",
          "type": "[][CartItem](#cartitem)",
          "settable": true,
          "notes": "The customer's saved cart."
        },
        {
          "name": "fields",
          "type": "object<string,string>",
          "settable": true,
          "notes": "Custom fields \u2014 what the shopper filled in at signup, plus anything you or the store's staff add (they're visible and editable on the customer's admin page). Absent when empty, and a key you set to `\"\"` is removed rather than stored blank. A key beginning with `_` also derives an `index` entry (below), which is how you keep a queryable key on a customer without hiding it from the merchant. At most 20 such keys are indexed, each up to 200 characters; a `_` key can only be set by you, the admin or an import \u2014 never by the signup form."
        },
        {
          "name": "meta",
          "type": "object",
          "settable": true,
          "notes": "Free-form plugin storage (opaque, **not** queryable) \u2014 **except** keys beginning with `_`, which derive the queryable `index` field below. Merged by key on save, never replaced wholesale. See Plugins.md \u2192 *Attaching plugin data (`.meta`)*."
        },
        {
          "name": "index",
          "type": "[]string",
          "settable": false,
          "notes": "Read-only, derived from `meta` **and** `fields` keys that start with `_` \u2014 `meta._foo = \"bar\"` \u2192 `\"meta#foo#bar\"`, `fields._extid = \"A123\"` \u2192 `\"fields#extid#A123\"`. Filter it to resolve/enumerate customers by your own marker: `sw.customers.list({ filters: { index: \"fields#extid#A123\" } })` (equality, standalone \u2014 no `order`, no composite index). Use `meta` for a key the merchant shouldn't see or edit, `fields` for one they should."
        },
        {
          "name": "alerts",
          "type": "{ opt_out_all, categories, updated, source }",
          "settable": true,
          "notes": "The customer's email preferences. `opt_out_all` silences every category except account/security mail. `categories` is an object of `\"<category key>\": boolean` holding only the choices that **differ from the category default** \u2014 an absent key means \"whatever that category defaults to\", so don't read it as \"unsubscribed\"; ask through a send instead. Keys are the store's own (`orders`, `shipping`, `subscriptions`, `account`, `marketing`) or a plugin's (`plugin:<id>:<key>`). `updated` is RFC3339 and `source` names where the last change came from (`account`, `unsubscribe`, `checkout`, `admin`, or a plugin id). Writing it directly is discouraged \u2014 send through `sw.notify.customer`, which applies these preferences for you."
        },
        {
          "name": "payment_method",
          "type": "{ brand, last4, exp_month, exp_year, label }",
          "settable": false,
          "notes": "Non-secret display hint for the saved method on file. A card sets `brand`/`last4`/expiry; a non-card method (Cash App Pay, Link, Amazon Pay, bank debit, \u2026) has no card fields and sets a ready-to-show `label` (e.g. `\"Cash App Pay\"`) instead \u2014 render `label` when present, else `brand`+`last4`. Read-only here (set via `sw.customers.setPaymentMethod`/`clearPaymentMethod`); the reusable token itself is never exposed. Absent when nothing is saved."
        },
        {
          "name": "payment_gateway",
          "type": "string",
          "settable": false,
          "notes": "The gateway id (e.g. `\"stripe\"`) that vaulted the saved method \u2014 matches your payment script's `gateway_id`. Charges against the saved method must go back to this gateway, so check `customer.payment_gateway === \"<your-gateway>\"` before offering \"pay with saved method\" (after a shop switches providers, a card vaulted by another gateway isn't yours to charge). Read-only; set alongside `payment_method`, absent when nothing is saved."
        },
        {
          "name": "anonymized",
          "type": "string\\",
          "settable": false,
          "notes": "RFC3339 set when PII was erased (GDPR). Absent otherwise."
        }
      ]
    },
    "Hook ctx": {
      "fields": [
        {
          "name": "ctx.type",
          "type": "string",
          "settable": false,
          "notes": "Always. The hook name, e.g. `\"order.after_save\"`."
        },
        {
          "name": "ctx.data",
          "type": "object",
          "settable": false,
          "notes": "Always. The hook payload \u2014 see the per-hook table. **Mutating it in place is how you modify the entity** (see below)."
        },
        {
          "name": "ctx.old_data",
          "type": "object",
          "settable": false,
          "notes": "Only on the CRUD `*.before_save` / `*.after_save` / `*.before_delete` / `*.after_delete` hooks \u2014 the pre-change entity. Absent otherwise."
        },
        {
          "name": "ctx.settings",
          "type": "object",
          "settable": false,
          "notes": "Always. The plugin's merged settings (`{}` if none)."
        },
        {
          "name": "ctx.plan",
          "type": "string",
          "settable": false,
          "notes": "Always. Active plan key for this shop+plugin (`\"\"` = none)."
        },
        {
          "name": "ctx.shop_id",
          "type": "integer",
          "settable": false,
          "notes": "Always."
        },
        {
          "name": "ctx.dev",
          "type": "boolean",
          "settable": false,
          "notes": "Always. `true` when the plugin is running as your live local copy during a dev session; `false` for an installed plugin. Guard production-only side effects with it (see below)."
        },
        {
          "name": "ctx.request",
          "type": "object",
          "settable": false,
          "notes": "Only for storefront-dispatched hooks \u2014 the request map. Absent for scheduled/webhook dispatches."
        },
        {
          "name": "ctx.timeoutRemaining()",
          "type": "function \u2192 integer",
          "settable": false,
          "notes": "Always. Milliseconds left in the hook's budget (0 if exceeded)."
        },
        {
          "name": "ctx.stop(reason?)",
          "type": "function",
          "settable": false,
          "notes": "Always. Suppresses the platform default cleanly (see below)."
        }
      ]
    },
    "Message": {
      "fields": [
        {
          "name": "id",
          "type": "integer",
          "settable": false,
          "notes": ""
        },
        {
          "name": "created",
          "type": "string",
          "settable": false,
          "notes": ""
        },
        {
          "name": "direction",
          "type": "string",
          "settable": false,
          "notes": "`in` (the customer wrote) or `out` (the store wrote)."
        },
        {
          "name": "status",
          "type": "string",
          "settable": false,
          "notes": "Going out: `queued` (being sent), `sent`, `failed` (see `error`), `bounced`. Coming in: `received` (a person), `auto` (an auto-reply), `bounced` (a delivery failure), `spam`."
        },
        {
          "name": "unread",
          "type": "boolean",
          "settable": false,
          "notes": "Coming in only: not yet read by staff."
        },
        {
          "name": "channel",
          "type": "string",
          "settable": false,
          "notes": "`email`."
        },
        {
          "name": "customer_id",
          "type": "integer",
          "settable": false,
          "notes": "`0` when the sender isn't a customer yet."
        },
        {
          "name": "email",
          "type": "string",
          "settable": false,
          "notes": "The customer's address, lowercase. Always set."
        },
        {
          "name": "name",
          "type": "string",
          "settable": false,
          "notes": "The sender's display name (coming in)."
        },
        {
          "name": "order_id",
          "type": "integer",
          "settable": false,
          "notes": "The order it concerns, when known. Absent otherwise."
        },
        {
          "name": "subject",
          "type": "string",
          "settable": false,
          "notes": ""
        },
        {
          "name": "preview",
          "type": "string",
          "settable": false,
          "notes": "The first ~280 characters of the text, on one line."
        },
        {
          "name": "text",
          "type": "string",
          "settable": false,
          "notes": "The full plain text. Present on `get`, `send` and the hooks; omitted from `list`."
        },
        {
          "name": "html",
          "type": "string",
          "settable": false,
          "notes": "The formatted email, when stored. `get` only. Coming in, it's already cleaned: text formatting and links only."
        },
        {
          "name": "has_body",
          "type": "boolean",
          "settable": false,
          "notes": "Whether a formatted copy is stored."
        },
        {
          "name": "attachments",
          "type": "array",
          "settable": false,
          "notes": "`{ name, size, content_type }` for each file kept."
        },
        {
          "name": "dropped",
          "type": "array",
          "settable": false,
          "notes": "Names of attached files that weren't kept (type or size not allowed)."
        },
        {
          "name": "parent_id",
          "type": "integer",
          "settable": false,
          "notes": "The message this one answers, when known."
        },
        {
          "name": "source",
          "type": "string",
          "settable": false,
          "notes": "Who caused it: `platform:<email type>` (e.g. `platform:order_confirmation`), `staff`, `plugin:<id>`, or `email` (a customer's email)."
        },
        {
          "name": "template",
          "type": "string",
          "settable": false,
          "notes": "The email type it was sent as (going out)."
        },
        {
          "name": "user_id",
          "type": "integer",
          "settable": false,
          "notes": "The staff member who wrote it (`source: \"staff\"`)."
        },
        {
          "name": "category",
          "type": "string",
          "settable": false,
          "notes": "The email preference topic it was sent under (going out), e.g. `orders` or `messages`."
        },
        {
          "name": "error",
          "type": "string",
          "settable": false,
          "notes": "Why a `failed` message wasn't sent."
        },
        {
          "name": "auth",
          "type": "string",
          "settable": false,
          "notes": "Sender checks on a received email, e.g. `spf=pass dkim=pass dmarc=pass`."
        }
      ]
    },
    "Order": {
      "fields": [
        {
          "name": "id",
          "type": "integer",
          "settable": false,
          "notes": "Omit to create."
        },
        {
          "name": "shop_id",
          "type": "integer",
          "settable": false,
          "notes": "Owning shop. Read-only."
        },
        {
          "name": "created / updated",
          "type": "string",
          "settable": false,
          "notes": "RFC3339, read-only."
        },
        {
          "name": "number",
          "type": "string",
          "settable": true,
          "notes": "Human order number. Auto-generated (unique per shop) if you don't set it; setting a duplicate fails the save."
        },
        {
          "name": "status",
          "type": "string",
          "settable": true,
          "notes": "One of `created`, `processing`, `ready_for_pickup`, `shipped`, `cancelled`, `refunded`, `partially_refunded`, `payment_failed`. Any other value fails the save. Derived from the order's facts (`payment.status`, `ready_at`, `shipped_at`, `cancelled_at`); writing a status performs the matching action \u2014 `shipped` records the shipment (stamps `shipped_at`), `ready_for_pickup` records that a pickup order is waiting at the counter (stamps `ready_at`), `cancelled` records the cancellation, `processing` captures an offline payment, `refunded`/`partially_refunded`/`payment_failed` update `payment.status`, and `created` reopens/resets. A write the facts contradict (e.g. `payment_failed` on a captured payment) is a no-op and the save returns the truthful status. `ready_for_pickup` on an order that isn't collected in store **fails** the save."
        },
        {
          "name": "currency",
          "type": "string",
          "settable": true,
          "notes": "Absent when empty."
        },
        {
          "name": "customer",
          "type": "[OrderCustomer](#ordercustomer)",
          "settable": true,
          "notes": "Snapshot at order time."
        },
        {
          "name": "shipping",
          "type": "[Address](#address)",
          "settable": true,
          "notes": "Ship-to address."
        },
        {
          "name": "payment",
          "type": "[OrderPayment](#orderpayment)",
          "settable": true,
          "notes": ""
        },
        {
          "name": "totals",
          "type": "[OrderTotals](#ordertotals)",
          "settable": true,
          "notes": ""
        },
        {
          "name": "items",
          "type": "[][OrderItem](#orderitem)",
          "settable": true,
          "notes": "Line items."
        },
        {
          "name": "shipping_method",
          "type": "[OrderShippingMethod](#ordershippingmethod)",
          "settable": true,
          "notes": "Absent when unset."
        },
        {
          "name": "trackings",
          "type": "[][Tracking](#tracking)",
          "settable": true,
          "notes": "Absent when empty."
        },
        {
          "name": "ready_at",
          "type": "string",
          "settable": true,
          "notes": "RFC3339; when a store-pickup order was set aside at the counter for the customer to collect. Absent until then, and only orders the customer collects in store may carry it \u2014 writing it on any other order fails the save. Setting `status` to `ready_for_pickup` stamps it; recording the handover (`shipped`) outranks it."
        },
        {
          "name": "shipped_at",
          "type": "string",
          "settable": true,
          "notes": "RFC3339; when the order was shipped \u2014 or, for a pickup order, collected. Absent until then. Setting `status` to `shipped` stamps it; useful as the anchor for time-window logic (e.g. returns)."
        },
        {
          "name": "cancelled_at",
          "type": "string",
          "settable": true,
          "notes": "RFC3339; when the order was cancelled. Absent unless cancelled. Setting `status` to `cancelled` stamps it."
        },
        {
          "name": "coupon_codes",
          "type": "[]string",
          "settable": true,
          "notes": "Absent when empty."
        },
        {
          "name": "tax_name",
          "type": "string",
          "settable": true,
          "notes": "Label for the tax line. Absent when empty."
        },
        {
          "name": "digital_only",
          "type": "boolean",
          "settable": false,
          "notes": "Computed from items in before-save. Read-only."
        },
        {
          "name": "reseller_shop_id",
          "type": "integer",
          "settable": false,
          "notes": "Absent when 0."
        },
        {
          "name": "subscription_id",
          "type": "integer",
          "settable": false,
          "notes": "Links a renewal invoice to its contract; absent for one-time orders."
        },
        {
          "name": "fields",
          "type": "object<string,string>",
          "settable": true,
          "notes": "The extra details checkout asked the shopper for (a phone number, a PO number, delivery instructions\u2026), keyed by the handleized label the merchant configured \u2014 \"PO Number\" arrives as `po-number`. A snapshot of what was asked on this order, so it stays accurate after the merchant changes the questions. Absent when empty; blank answers aren't stored. Unlike `meta` it derives no `index` entries \u2014 the values are typed by the shopper. A collected phone also lands on `customer.phone`."
        },
        {
          "name": "meta",
          "type": "object",
          "settable": true,
          "notes": "Free-form plugin storage (opaque, **not** queryable) \u2014 **except** keys beginning with `_`, which derive the queryable `index` field below. Merged by key on save, never replaced wholesale. See Plugins.md \u2192 *Attaching plugin data (`.meta`)*."
        },
        {
          "name": "index",
          "type": "[]string",
          "settable": false,
          "notes": "Read-only, derived from `meta` keys that start with `_` (`meta._foo = \"bar\"` \u2192 `\"meta#foo#bar\"`) \u2014 same rules as Product's `index`. Filter it to resolve/enumerate orders by a hidden marker: `sw.orders.list({ filters: { index: \"meta#extid#A123\" } })` (equality, standalone \u2014 no `order`, no composite index)."
        },
        {
          "name": "fulfillment_ids",
          "type": "[]integer",
          "settable": false,
          "notes": "Absent when empty."
        },
        {
          "name": "manual",
          "type": "boolean",
          "settable": true,
          "notes": "`true` = admin/POS/quote-composed (reserves no stock until paid). Absent when false."
        },
        {
          "name": "note",
          "type": "string",
          "settable": true,
          "notes": "Internal admin note. Absent when empty."
        },
        {
          "name": "created_by_user_id",
          "type": "integer",
          "settable": false,
          "notes": "Staff/rep who placed it on the customer's behalf; `0`/absent for storefront self-checkout. **Indexed** (filterable in `list`)."
        },
        {
          "name": "stock_reserved",
          "type": "boolean",
          "settable": false,
          "notes": "Whether the order currently holds an inventory reservation."
        },
        {
          "name": "adjustments",
          "type": "[][PaymentAdjustment](#paymentadjustment)",
          "settable": false,
          "notes": "Plugin-contributed +/- total lines. Absent when empty."
        },
        {
          "name": "refunds",
          "type": "[][OrderRefund](#orderrefund)",
          "settable": false,
          "notes": "Absent when empty."
        },
        {
          "name": "Field",
          "type": "Type",
          "settable": false,
          "notes": "Notes"
        },
        {
          "name": "product_id",
          "type": "integer",
          "settable": false,
          "notes": ""
        },
        {
          "name": "shop_id",
          "type": "integer",
          "settable": false,
          "notes": "`0` = own product; `>0` = supplier shop (wired). Absent when 0."
        },
        {
          "name": "name",
          "type": "string",
          "settable": false,
          "notes": ""
        },
        {
          "name": "sku",
          "type": "string",
          "settable": false,
          "notes": "Absent when empty."
        },
        {
          "name": "price",
          "type": "integer",
          "settable": false,
          "notes": "Unit price in cents."
        },
        {
          "name": "qty",
          "type": "integer",
          "settable": false,
          "notes": ""
        },
        {
          "name": "set",
          "type": "object<string,string>",
          "settable": false,
          "notes": "Chosen variant options. Absent when empty."
        },
        {
          "name": "image",
          "type": "string",
          "settable": false,
          "notes": "Absent when empty."
        },
        {
          "name": "plan",
          "type": "string",
          "settable": false,
          "notes": "Subscription plan key; empty = one-time."
        },
        {
          "name": "tax_exempt",
          "type": "boolean",
          "settable": false,
          "notes": "This line was excluded from the taxable total, copied from the product when the order was built. Absent when false. Kept on the order so a receipt or a refund can show why the tax is what it is even after the product changes."
        },
        {
          "name": "Field",
          "type": "Type",
          "settable": false,
          "notes": "Notes"
        },
        {
          "name": "provider",
          "type": "string",
          "settable": false,
          "notes": "Gateway id (`stripe`, `square`, \u2026); empty/`manual` = offline."
        },
        {
          "name": "method",
          "type": "string",
          "settable": false,
          "notes": "Tender: `card`, `cash`, `bank_transfer`, `ach`, \u2026 Absent when empty."
        },
        {
          "name": "payment_id",
          "type": "string",
          "settable": false,
          "notes": "Gateway payment id."
        },
        {
          "name": "status",
          "type": "string",
          "settable": false,
          "notes": "`pending`, `paid`, `failed`, `partially_refunded`, `refunded`. The money truth for the order \u2014 the order's headline `status` is derived from it (together with the ship/cancel facts)."
        },
        {
          "name": "Field",
          "type": "Type",
          "settable": false,
          "notes": "Notes"
        },
        {
          "name": "id",
          "type": "string",
          "settable": false,
          "notes": "Shop shipping-method id (for re-pricing)."
        },
        {
          "name": "name",
          "type": "string",
          "settable": false,
          "notes": ""
        },
        {
          "name": "type",
          "type": "string",
          "settable": false,
          "notes": "`pickup`."
        },
        {
          "name": "pickup",
          "type": "[ShippingPickupDetails](#shippingpickupdetails)",
          "settable": false,
          "notes": "Only for pickup."
        },
        {
          "name": "Field",
          "type": "Type",
          "settable": false,
          "notes": "Notes"
        },
        {
          "name": "id",
          "type": "string",
          "settable": false,
          "notes": "Internal id; also the gateway idempotency key."
        },
        {
          "name": "amount",
          "type": "integer",
          "settable": false,
          "notes": "Positive cents refunded."
        },
        {
          "name": "reason",
          "type": "string",
          "settable": false,
          "notes": "Optional admin note."
        },
        {
          "name": "status",
          "type": "string",
          "settable": false,
          "notes": "`failed`."
        },
        {
          "name": "refund_id",
          "type": "string",
          "settable": false,
          "notes": "Provider refund id; empty for manual."
        },
        {
          "name": "manual",
          "type": "boolean",
          "settable": false,
          "notes": "Recorded only; gateway not called."
        },
        {
          "name": "restock",
          "type": "boolean",
          "settable": false,
          "notes": ""
        },
        {
          "name": "items",
          "type": "[]{ product_id, shop_id?, quantity }",
          "settable": false,
          "notes": "Optional per-line breakdown."
        },
        {
          "name": "error",
          "type": "string",
          "settable": false,
          "notes": "Gateway error when `status == failed`."
        },
        {
          "name": "created_by",
          "type": "string",
          "settable": false,
          "notes": "Admin email."
        },
        {
          "name": "created_at",
          "type": "string",
          "settable": false,
          "notes": "RFC3339."
        }
      ]
    },
    "OrderItem": {
      "fields": [
        {
          "name": "product_id",
          "type": "integer",
          "settable": false,
          "notes": ""
        },
        {
          "name": "shop_id",
          "type": "integer",
          "settable": false,
          "notes": "`0` = own product; `>0` = supplier shop (wired). Absent when 0."
        },
        {
          "name": "name",
          "type": "string",
          "settable": false,
          "notes": ""
        },
        {
          "name": "sku",
          "type": "string",
          "settable": false,
          "notes": "Absent when empty."
        },
        {
          "name": "price",
          "type": "integer",
          "settable": false,
          "notes": "Unit price in cents."
        },
        {
          "name": "qty",
          "type": "integer",
          "settable": false,
          "notes": ""
        },
        {
          "name": "set",
          "type": "object<string,string>",
          "settable": false,
          "notes": "Chosen variant options. Absent when empty."
        },
        {
          "name": "image",
          "type": "string",
          "settable": false,
          "notes": "Absent when empty."
        },
        {
          "name": "plan",
          "type": "string",
          "settable": false,
          "notes": "Subscription plan key; empty = one-time."
        },
        {
          "name": "tax_exempt",
          "type": "boolean",
          "settable": false,
          "notes": "This line was excluded from the taxable total, copied from the product when the order was built. Absent when false. Kept on the order so a receipt or a refund can show why the tax is what it is even after the product changes."
        }
      ]
    },
    "OrderPayment": {
      "fields": [
        {
          "name": "provider",
          "type": "string",
          "settable": false,
          "notes": "Gateway id (`stripe`, `square`, \u2026); empty/`manual` = offline."
        },
        {
          "name": "method",
          "type": "string",
          "settable": false,
          "notes": "Tender: `card`, `cash`, `bank_transfer`, `ach`, \u2026 Absent when empty."
        },
        {
          "name": "payment_id",
          "type": "string",
          "settable": false,
          "notes": "Gateway payment id."
        },
        {
          "name": "status",
          "type": "string",
          "settable": false,
          "notes": "`pending`, `paid`, `failed`, `partially_refunded`, `refunded`. The money truth for the order \u2014 the order's headline `status` is derived from it (together with the ship/cancel facts)."
        }
      ]
    },
    "OrderRefund": {
      "fields": [
        {
          "name": "id",
          "type": "string",
          "settable": false,
          "notes": "Internal id; also the gateway idempotency key."
        },
        {
          "name": "amount",
          "type": "integer",
          "settable": false,
          "notes": "Positive cents refunded."
        },
        {
          "name": "reason",
          "type": "string",
          "settable": false,
          "notes": "Optional admin note."
        },
        {
          "name": "status",
          "type": "string",
          "settable": false,
          "notes": "`failed`."
        },
        {
          "name": "refund_id",
          "type": "string",
          "settable": false,
          "notes": "Provider refund id; empty for manual."
        },
        {
          "name": "manual",
          "type": "boolean",
          "settable": false,
          "notes": "Recorded only; gateway not called."
        },
        {
          "name": "restock",
          "type": "boolean",
          "settable": false,
          "notes": ""
        },
        {
          "name": "items",
          "type": "[]{ product_id, shop_id?, quantity }",
          "settable": false,
          "notes": "Optional per-line breakdown."
        },
        {
          "name": "error",
          "type": "string",
          "settable": false,
          "notes": "Gateway error when `status == failed`."
        },
        {
          "name": "created_by",
          "type": "string",
          "settable": false,
          "notes": "Admin email."
        },
        {
          "name": "created_at",
          "type": "string",
          "settable": false,
          "notes": "RFC3339."
        }
      ]
    },
    "OrderShippingMethod": {
      "fields": [
        {
          "name": "id",
          "type": "string",
          "settable": false,
          "notes": "Shop shipping-method id (for re-pricing)."
        },
        {
          "name": "name",
          "type": "string",
          "settable": false,
          "notes": ""
        },
        {
          "name": "type",
          "type": "string",
          "settable": false,
          "notes": "`pickup`."
        },
        {
          "name": "pickup",
          "type": "[ShippingPickupDetails](#shippingpickupdetails)",
          "settable": false,
          "notes": "Only for pickup."
        }
      ]
    },
    "Product": {
      "fields": [
        {
          "name": "id",
          "type": "integer",
          "settable": false,
          "notes": "Omit to create; set to update."
        },
        {
          "name": "shop_id",
          "type": "integer",
          "settable": false,
          "notes": "The owning shop. Read-only."
        },
        {
          "name": "created / updated",
          "type": "string",
          "settable": false,
          "notes": "RFC3339, read-only."
        },
        {
          "name": "sku",
          "type": "string",
          "settable": true,
          "notes": ""
        },
        {
          "name": "skus",
          "type": "[]string",
          "settable": false,
          "notes": "Read-only, derived: the product SKU plus every variant SKU (deduped). Rebuilt on each save. Filter by it to resolve a product from **any** of its SKUs \u2014 including a variant SKU: `sw.products.list({ filters: { skus: \"ABC-S\" } })`."
        },
        {
          "name": "index",
          "type": "[]string",
          "settable": false,
          "notes": "Read-only, derived on each save from two sources. **`meta` keys that start with `_`**: `meta._foo = \"bar\"` \u2192 entry `\"meta#foo#bar\"` (the `meta#` prefix namespaces meta-derived entries; numbers/booleans stringified; scalar arrays expand to one entry each). This is the **queryable** counterpart to the opaque `meta` blob \u2014 filter it to enumerate or resolve products by a hidden marker: `sw.products.list({ filters: { index: \"meta#erp_id#123\" } })`. **And one entry per tag**, `\"tag#\"` + the tag's canonical form: lower-cased, with spaces, hyphens and underscores all reduced to a single hyphen and other punctuation dropped \u2014 so `Men's Shoes`, `mens shoes` and `Mens-Shoes` all yield `\"tag#mens-shoes\"`. Use it to list products by tag without knowing how the tag was capitalized or punctuated: `sw.products.list({ filters: { index: \"tag#mens-shoes\" } })`. **Equality only** (it's multi-valued, so a range/`*` prefix over-matches) and **standalone** (no `order`), so it needs no composite index. A marker over **200 characters** is left out of `index` \u2014 the `meta` value itself is stored and readable as always, it simply can't be resolved through a filter, and the save succeeds either way. Markers are for ids, codes and flags; put a note or a payload in a plain `meta` key."
        },
        {
          "name": "name",
          "type": "string",
          "settable": true,
          "notes": "Required."
        },
        {
          "name": "slug",
          "type": "string",
          "settable": true,
          "notes": "Custom storefront slug; `\"\"` = derive URL from name+id. Handleized + unique per shop on save; setting it 301-redirects the old slug. Absent when empty."
        },
        {
          "name": "desc",
          "type": "string",
          "settable": true,
          "notes": ""
        },
        {
          "name": "price",
          "type": "integer",
          "settable": true,
          "notes": "Selling price in **cents**."
        },
        {
          "name": "compare_price",
          "type": "integer",
          "settable": true,
          "notes": "MSRP / strike-through price in cents."
        },
        {
          "name": "prices",
          "type": "object<string,integer>",
          "settable": true,
          "notes": "Named price tiers, e.g. `{ \"wholesale\": 1999 }`. Keyed by a price-level / customer `price_level` name."
        },
        {
          "name": "stock",
          "type": "integer",
          "settable": true,
          "notes": "`-1` = unlimited (never counts down or sells out) \u2014 use it for digital products rather than a large number."
        },
        {
          "name": "oversell",
          "type": "boolean",
          "settable": true,
          "notes": "Allow back-orders past `stock`."
        },
        {
          "name": "weight",
          "type": "number",
          "settable": true,
          "notes": "In lbs."
        },
        {
          "name": "images",
          "type": "[]string",
          "settable": true,
          "notes": "URLs."
        },
        {
          "name": "tags",
          "type": "[]string",
          "settable": true,
          "notes": ""
        },
        {
          "name": "active",
          "type": "boolean",
          "settable": true,
          "notes": "New products default to `active: true`."
        },
        {
          "name": "digital",
          "type": "boolean",
          "settable": true,
          "notes": "Digital good (no shipping)."
        },
        {
          "name": "tax_exempt",
          "type": "boolean",
          "settable": true,
          "notes": "Leaves this product out of the taxable total wherever it is sold \u2014 groceries, medicine, gift cards, a service line. The rest of the basket is still rated normally. A discount reduces the taxable total only by the part that reaches taxable lines, worked out per coupon: one restricted to exempt products doesn't lower the tax at all, one restricted to taxable products comes off in full, and an unrestricted one is split across the basket by value. Stacked codes are attributed separately, so a code covering a taxable item outright still leaves nothing of it to tax when another code is applied alongside. Read live at every point tax is calculated, so flipping it takes effect immediately."
        },
        {
          "name": "files",
          "type": "[]string",
          "settable": true,
          "notes": "What buyers download. Each entry is one of the store's own files, as `/api/blob/{shop_id}/private/<path>` (handed only to a buyer whose order is paid) or a public file URL, or a full `https://` link (public files and links open for anyone who has them). Saving rejects anything else, including another store's file."
        },
        {
          "name": "options",
          "type": "[][Option](#option)",
          "settable": true,
          "notes": "Option dimensions (Size, Color)."
        },
        {
          "name": "variants",
          "type": "[][Variant](#variant)",
          "settable": true,
          "notes": "Per-combination SKU/price/stock."
        },
        {
          "name": "attrs",
          "type": "[][Attribute](#attribute)",
          "settable": true,
          "notes": "Custom attributes / facets."
        },
        {
          "name": "price_tiers",
          "type": "[][PriceTier](#pricetier)",
          "settable": true,
          "notes": "Quantity price breaks."
        },
        {
          "name": "subscription",
          "type": "[ProductSubscription](#productsubscription)",
          "settable": true,
          "notes": "Recurring-purchase config; absent = one-time only."
        },
        {
          "name": "meta",
          "type": "object",
          "settable": true,
          "notes": "Free-form plugin storage (opaque, **not** queryable) \u2014 **except** keys beginning with `_`, which derive the queryable `index` field above. Merged by key on save, never replaced wholesale. See Plugins.md \u2192 *Attaching plugin data (`.meta`)*."
        },
        {
          "name": "selected_set",
          "type": "object<string,string>",
          "settable": false,
          "notes": "Only present on a storefront-priced product (the chosen variant); not persisted."
        },
        {
          "name": "offer",
          "type": "object",
          "settable": false,
          "notes": "\"fixed\", percent, amount, min_order, ends }` \u2014 `percent` is the percentage off ready to print (`\"50\"`, `\"12.5\"`, empty for a fixed offer), `amount` the money off in cents, `min_order` the order minimum in cents (`0` = none), `ends` when it stops (absent = open-ended). It **advertises**; `price` is unchanged, and the discount is worked out against the real cart at checkout. Not persisted. See Themes.md \u2192 *Automatic discount offers*."
        },
        {
          "name": "Field",
          "type": "Type",
          "settable": false,
          "notes": "Notes"
        },
        {
          "name": "set",
          "type": "object<string,string>",
          "settable": false,
          "notes": "The option combination, e.g. `{ \"Size\": \"S\", \"Color\": \"Red\" }`."
        },
        {
          "name": "sku",
          "type": "string",
          "settable": false,
          "notes": ""
        },
        {
          "name": "price",
          "type": "integer\\",
          "settable": false,
          "notes": "`null` = use base price."
        },
        {
          "name": "compare_price",
          "type": "integer\\",
          "settable": false,
          "notes": ""
        },
        {
          "name": "prices",
          "type": "object<string,integer\\",
          "settable": false,
          "notes": "Per-tier overrides."
        },
        {
          "name": "stock",
          "type": "integer\\",
          "settable": false,
          "notes": "`null` = use base stock; `-1` = unlimited."
        },
        {
          "name": "oversell",
          "type": "boolean\\",
          "settable": false,
          "notes": "`null` = inherit product policy."
        },
        {
          "name": "images",
          "type": "[]string",
          "settable": false,
          "notes": ""
        },
        {
          "name": "attrs",
          "type": "[][Attribute](#attribute)",
          "settable": false,
          "notes": ""
        },
        {
          "name": "price_tiers",
          "type": "[][PriceTier](#pricetier)",
          "settable": false,
          "notes": "Empty = use product-level tiers."
        },
        {
          "name": "Field",
          "type": "Type",
          "settable": false,
          "notes": "Notes"
        },
        {
          "name": "enabled",
          "type": "boolean",
          "settable": false,
          "notes": ""
        },
        {
          "name": "required",
          "type": "boolean",
          "settable": false,
          "notes": "`true` = subscription-only (no one-time buy)."
        },
        {
          "name": "trial_days",
          "type": "integer",
          "settable": false,
          "notes": ""
        },
        {
          "name": "max_cycles",
          "type": "integer",
          "settable": false,
          "notes": "`0` = unlimited."
        },
        {
          "name": "plans",
          "type": "[][SubscriptionPlan](#subscriptionplan)",
          "settable": false,
          "notes": ""
        },
        {
          "name": "Field",
          "type": "Type",
          "settable": false,
          "notes": "Notes"
        },
        {
          "name": "key",
          "type": "string",
          "settable": false,
          "notes": "Stable id stored on the order line (e.g. `\"monthly\"`)."
        },
        {
          "name": "label",
          "type": "string",
          "settable": false,
          "notes": "Shown on the product page."
        },
        {
          "name": "interval",
          "type": "string",
          "settable": false,
          "notes": "`yearly`."
        },
        {
          "name": "price",
          "type": "integer\\",
          "settable": false,
          "notes": "Fixed per-cycle cents; `null` = derive from product/variant price."
        },
        {
          "name": "discount",
          "type": "integer",
          "settable": false,
          "notes": "% off base price when `price` is null."
        },
        {
          "name": "first_cycle_discount",
          "type": "integer",
          "settable": false,
          "notes": "% off the first charge only."
        },
        {
          "name": "variant",
          "type": "object<string,string>",
          "settable": false,
          "notes": "Option set this plan is scoped to; empty = all variants."
        },
        {
          "name": "anchor",
          "type": "string",
          "settable": false,
          "notes": "Pins renewals to a calendar position instead of each customer's signup date. `\"\"` (default) = signup anniversary, `day_of_month`, `day_of_week`."
        },
        {
          "name": "anchor_value",
          "type": "string",
          "settable": false,
          "notes": "The position `anchor` refers to: `\"1\"`\u2013`\"31\"` or `\"last\"` for `day_of_month`; `\"mon\"`\u2013`\"sun\"` for `day_of_week`. A day later than a given month has (the 31st in February) bills on that month's last day, then returns to the 31st in longer months."
        },
        {
          "name": "first_cycle",
          "type": "string",
          "settable": false,
          "notes": "What happens between signup and the first anchored billing date: `full` (default) charges a whole cycle at signup, `wait` charges nothing until that date. Requires an `anchor`."
        },
        {
          "name": "bounds",
          "type": "[SubscriptionBounds](#subscriptionbounds)",
          "settable": false,
          "notes": "Present when the plan's amount is decided per cycle rather than fixed. Absent = a plain fixed-price plan."
        },
        {
          "name": "Field",
          "type": "Type",
          "settable": false,
          "notes": "Notes"
        },
        {
          "name": "variable",
          "type": "boolean",
          "settable": false,
          "notes": "`true` = never bill this without an amount from a handler. A renewal nothing answers for is held for the store owner to review instead of charged at the plan price. Leave it off when the plan price is a sensible default."
        },
        {
          "name": "min_qty",
          "type": "integer",
          "settable": false,
          "notes": "Lowest quantity a cycle may bill. `0` means a cycle may bill nothing at all (\"no lessons in August\")."
        },
        {
          "name": "max_qty",
          "type": "integer",
          "settable": false,
          "notes": "Highest quantity a cycle may bill. `0` = quantity is locked to what the contract already says."
        },
        {
          "name": "min_price",
          "type": "integer\\",
          "settable": false,
          "notes": "Lowest per-unit price in cents. `null` = a floor of 0."
        },
        {
          "name": "max_price",
          "type": "integer\\",
          "settable": false,
          "notes": "Highest per-unit price in cents. `null` = the unit price is locked."
        }
      ]
    },
    "ProductSubscription": {
      "fields": [
        {
          "name": "enabled",
          "type": "boolean",
          "settable": false,
          "notes": ""
        },
        {
          "name": "required",
          "type": "boolean",
          "settable": false,
          "notes": "`true` = subscription-only (no one-time buy)."
        },
        {
          "name": "trial_days",
          "type": "integer",
          "settable": false,
          "notes": ""
        },
        {
          "name": "max_cycles",
          "type": "integer",
          "settable": false,
          "notes": "`0` = unlimited."
        },
        {
          "name": "plans",
          "type": "[][SubscriptionPlan](#subscriptionplan)",
          "settable": false,
          "notes": ""
        }
      ]
    },
    "Scheduled / background-task ctx": {
      "fields": [
        {
          "name": "ctx.args",
          "type": "any",
          "settable": false,
          "notes": "`sw.task.bg` closure \u2014 the payload you enqueued (object, array, or literal), verbatim; `undefined` if none."
        },
        {
          "name": "ctx.params",
          "type": "object",
          "settable": false,
          "notes": "Action scripts \u2014 the action param values."
        },
        {
          "name": "ctx.continue",
          "type": "{ depth, data }",
          "settable": false,
          "notes": "Continuation state for a re-enqueued task."
        },
        {
          "name": "ctx.shop",
          "type": "object",
          "settable": false,
          "notes": "The shop projection, when in shop context."
        }
      ]
    },
    "Shop projection (ctx.shop)": {
      "fields": [
        {
          "name": "id",
          "type": "integer",
          "settable": false,
          "notes": "integer"
        },
        {
          "name": "name",
          "type": "string",
          "settable": false,
          "notes": "string"
        },
        {
          "name": "slogan",
          "type": "string",
          "settable": false,
          "notes": "string"
        },
        {
          "name": "subdomain",
          "type": "string",
          "settable": false,
          "notes": "string"
        },
        {
          "name": "domains",
          "type": "[]string",
          "settable": false,
          "notes": "[]string"
        },
        {
          "name": "currency",
          "type": "string",
          "settable": false,
          "notes": "string"
        },
        {
          "name": "payment_provider",
          "type": "string",
          "settable": false,
          "notes": "string"
        },
        {
          "name": "canonical_host",
          "type": "string (computed)",
          "settable": false,
          "notes": "string (computed)"
        },
        {
          "name": "canonical_url",
          "type": "string (computed)",
          "settable": false,
          "notes": "string (computed)"
        },
        {
          "name": "passwordless_login / require_account",
          "type": "boolean",
          "settable": false,
          "notes": "boolean"
        },
        {
          "name": "logo_url",
          "type": "string",
          "settable": false,
          "notes": "string"
        },
        {
          "name": "color_primary, color_primary_hover, color_bg, color_surface, color_text_main, color_text_muted, color_error, color_success",
          "type": "string",
          "settable": false,
          "notes": "string"
        },
        {
          "name": "theme",
          "type": "object (theme config)",
          "settable": false,
          "notes": "object (theme config)"
        },
        {
          "name": "auth",
          "type": "object (auth config)",
          "settable": false,
          "notes": "object (auth config)"
        }
      ]
    },
    "Subscription": {
      "fields": [
        {
          "name": "id",
          "type": "integer",
          "settable": false,
          "notes": ""
        },
        {
          "name": "shop_id",
          "type": "integer",
          "settable": false,
          "notes": ""
        },
        {
          "name": "customer_id",
          "type": "integer",
          "settable": false,
          "notes": "Who is billed. Not changeable."
        },
        {
          "name": "status",
          "type": "string",
          "settable": false,
          "notes": "`active`, `trialing` (method saved, first charge deferred), `past_due` (a charge failed, retries in progress), `paused`, `cancelled` (final). Moved with `pause`/`resume`/`cancel`, not by `update`."
        },
        {
          "name": "gateway",
          "type": "string",
          "settable": false,
          "notes": "Payment provider the contract was created with."
        },
        {
          "name": "interval",
          "type": "string",
          "settable": true,
          "notes": "`yearly`."
        },
        {
          "name": "anchor",
          "type": "string",
          "settable": true,
          "notes": "Pins renewals to a calendar position instead of the signup anniversary: `\"\"` (anniversary), `day_of_month`, `day_of_week`. Must suit the interval \u2014 only a weekly contract can use `day_of_week`. Snapshotted from the plan at signup, so later edits to the product don't re-cadence existing customers."
        },
        {
          "name": "anchor_value",
          "type": "string",
          "settable": true,
          "notes": "`\"1\"`\u2013`\"31\"` or `\"last\"` for `day_of_month`; `\"mon\"`\u2013`\"sun\"` for `day_of_week`. A day later than a given month has bills on that month's last day and returns to the intended day afterwards."
        },
        {
          "name": "next_bill_at",
          "type": "string",
          "settable": true,
          "notes": "When the next charge is due. Must be in the future."
        },
        {
          "name": "cycle_count",
          "type": "integer",
          "settable": false,
          "notes": "Successful renewals so far."
        },
        {
          "name": "max_cycles",
          "type": "integer",
          "settable": true,
          "notes": "`0` = unlimited. Must exceed `cycle_count`."
        },
        {
          "name": "failure_count",
          "type": "integer",
          "settable": false,
          "notes": "Consecutive declines; the contract is cancelled after 3."
        },
        {
          "name": "paused_until",
          "type": "string",
          "settable": false,
          "notes": "Set when a pause has an end date."
        },
        {
          "name": "attempts",
          "type": "integer",
          "settable": false,
          "notes": "Delivery attempts for the *current* cycle, including ones that never reached the provider. Distinct from `failure_count`, which counts declines across cycles."
        },
        {
          "name": "retry_at",
          "type": "string",
          "settable": false,
          "notes": "When the next attempt is expected."
        },
        {
          "name": "last_error",
          "type": "[SubscriptionError](#subscriptionerror)",
          "settable": false,
          "notes": "Why the current cycle hasn't collected. Absent when nothing is wrong."
        },
        {
          "name": "hold",
          "type": "[SubscriptionHold](#subscriptionhold)",
          "settable": false,
          "notes": "Present when automatic billing has stopped and the merchant must intervene. Absent normally."
        },
        {
          "name": "items",
          "type": "[][OrderItem](#orderitem)",
          "settable": true,
          "notes": "What renews. Supply quantities and prices; totals are recalculated for you."
        },
        {
          "name": "totals",
          "type": "[OrderTotals](#ordertotals)",
          "settable": false,
          "notes": "Read-only \u2014 derived from `items`, `shipping`, and the store's tax/shipping rules on every change."
        },
        {
          "name": "shipping",
          "type": "[Address](#address)",
          "settable": true,
          "notes": "Changing it re-prices shipping and tax for future renewals."
        },
        {
          "name": "shipping_method",
          "type": "object",
          "settable": false,
          "notes": "Method captured at signup, re-priced when the address changes."
        },
        {
          "name": "currency",
          "type": "string",
          "settable": false,
          "notes": ""
        },
        {
          "name": "bounds",
          "type": "[SubscriptionBounds](#subscriptionbounds)",
          "settable": false,
          "notes": "The range a cycle may move within, copied from the plan when the customer subscribed. Absent on a fixed-price contract. **Not writable by a plugin** \u2014 see below."
        },
        {
          "name": "max_cycle_amount",
          "type": "integer",
          "settable": false,
          "notes": "The most a single cycle of this contract may ever bill, in cents, including tax and shipping \u2014 the total the customer agreed to when they subscribed. `0` on a fixed-price contract. Derived, never set directly."
        },
        {
          "name": "original_order_id",
          "type": "integer",
          "settable": false,
          "notes": "The order the contract was created from."
        },
        {
          "name": "created / updated",
          "type": "string",
          "settable": false,
          "notes": ""
        },
        {
          "name": "meta",
          "type": "object",
          "settable": false,
          "notes": "Free-form plugin storage."
        },
        {
          "name": "Field",
          "type": "Type",
          "settable": false,
          "notes": "Notes"
        },
        {
          "name": "kind",
          "type": "string",
          "settable": false,
          "notes": "`decline` \u2014 the charge was refused and the **customer** must act (update their payment method). `system` \u2014 the attempt itself failed and is being retried automatically; nothing for the customer to do."
        },
        {
          "name": "code",
          "type": "string",
          "settable": false,
          "notes": "`card_declined`, `no_payment_method`, `gateway_unavailable`, `internal`, `cycle_unresolved` (working out what this cycle should bill failed; nothing was attempted against the payment method)."
        },
        {
          "name": "message",
          "type": "string",
          "settable": false,
          "notes": "Wording safe to show a shopper."
        },
        {
          "name": "at",
          "type": "string",
          "settable": false,
          "notes": ""
        },
        {
          "name": "Field",
          "type": "Type",
          "settable": false,
          "notes": "Notes"
        },
        {
          "name": "reason",
          "type": "string",
          "settable": false,
          "notes": "`retries_exhausted` \u2014 automatic retries ran out on failures outside the customer's control. `cycle_missing` \u2014 a variable contract reached its billing date with no amount decided for it. `over_cap` \u2014 the amount asked for falls outside the range agreed for this contract."
        },
        {
          "name": "message",
          "type": "string",
          "settable": false,
          "notes": ""
        },
        {
          "name": "set_by",
          "type": "string",
          "settable": false,
          "notes": ""
        },
        {
          "name": "at",
          "type": "string",
          "settable": false,
          "notes": ""
        }
      ]
    },
    "SubscriptionBounds": {
      "fields": [
        {
          "name": "variable",
          "type": "boolean",
          "settable": false,
          "notes": "`true` = never bill this without an amount from a handler. A renewal nothing answers for is held for the store owner to review instead of charged at the plan price. Leave it off when the plan price is a sensible default."
        },
        {
          "name": "min_qty",
          "type": "integer",
          "settable": false,
          "notes": "Lowest quantity a cycle may bill. `0` means a cycle may bill nothing at all (\"no lessons in August\")."
        },
        {
          "name": "max_qty",
          "type": "integer",
          "settable": false,
          "notes": "Highest quantity a cycle may bill. `0` = quantity is locked to what the contract already says."
        },
        {
          "name": "min_price",
          "type": "integer\\",
          "settable": false,
          "notes": "Lowest per-unit price in cents. `null` = a floor of 0."
        },
        {
          "name": "max_price",
          "type": "integer\\",
          "settable": false,
          "notes": "Highest per-unit price in cents. `null` = the unit price is locked."
        }
      ]
    },
    "SubscriptionError": {
      "fields": [
        {
          "name": "kind",
          "type": "string",
          "settable": false,
          "notes": "`decline` \u2014 the charge was refused and the **customer** must act (update their payment method). `system` \u2014 the attempt itself failed and is being retried automatically; nothing for the customer to do."
        },
        {
          "name": "code",
          "type": "string",
          "settable": false,
          "notes": "`card_declined`, `no_payment_method`, `gateway_unavailable`, `internal`, `cycle_unresolved` (working out what this cycle should bill failed; nothing was attempted against the payment method)."
        },
        {
          "name": "message",
          "type": "string",
          "settable": false,
          "notes": "Wording safe to show a shopper."
        },
        {
          "name": "at",
          "type": "string",
          "settable": false,
          "notes": ""
        }
      ]
    },
    "SubscriptionHold": {
      "fields": [
        {
          "name": "reason",
          "type": "string",
          "settable": false,
          "notes": "`retries_exhausted` \u2014 automatic retries ran out on failures outside the customer's control. `cycle_missing` \u2014 a variable contract reached its billing date with no amount decided for it. `over_cap` \u2014 the amount asked for falls outside the range agreed for this contract."
        },
        {
          "name": "message",
          "type": "string",
          "settable": false,
          "notes": ""
        },
        {
          "name": "set_by",
          "type": "string",
          "settable": false,
          "notes": ""
        },
        {
          "name": "at",
          "type": "string",
          "settable": false,
          "notes": ""
        }
      ]
    },
    "SubscriptionPlan": {
      "fields": [
        {
          "name": "key",
          "type": "string",
          "settable": false,
          "notes": "Stable id stored on the order line (e.g. `\"monthly\"`)."
        },
        {
          "name": "label",
          "type": "string",
          "settable": false,
          "notes": "Shown on the product page."
        },
        {
          "name": "interval",
          "type": "string",
          "settable": false,
          "notes": "`yearly`."
        },
        {
          "name": "price",
          "type": "integer\\",
          "settable": false,
          "notes": "Fixed per-cycle cents; `null` = derive from product/variant price."
        },
        {
          "name": "discount",
          "type": "integer",
          "settable": false,
          "notes": "% off base price when `price` is null."
        },
        {
          "name": "first_cycle_discount",
          "type": "integer",
          "settable": false,
          "notes": "% off the first charge only."
        },
        {
          "name": "variant",
          "type": "object<string,string>",
          "settable": false,
          "notes": "Option set this plan is scoped to; empty = all variants."
        },
        {
          "name": "anchor",
          "type": "string",
          "settable": false,
          "notes": "Pins renewals to a calendar position instead of each customer's signup date. `\"\"` (default) = signup anniversary, `day_of_month`, `day_of_week`."
        },
        {
          "name": "anchor_value",
          "type": "string",
          "settable": false,
          "notes": "The position `anchor` refers to: `\"1\"`\u2013`\"31\"` or `\"last\"` for `day_of_month`; `\"mon\"`\u2013`\"sun\"` for `day_of_week`. A day later than a given month has (the 31st in February) bills on that month's last day, then returns to the 31st in longer months."
        },
        {
          "name": "first_cycle",
          "type": "string",
          "settable": false,
          "notes": "What happens between signup and the first anchored billing date: `full` (default) charges a whole cycle at signup, `wait` charges nothing until that date. Requires an `anchor`."
        },
        {
          "name": "bounds",
          "type": "[SubscriptionBounds](#subscriptionbounds)",
          "settable": false,
          "notes": "Present when the plan's amount is decided per cycle rather than fixed. Absent = a plain fixed-price plan."
        }
      ]
    },
    "The ctx object": {
      "fields": [
        {
          "name": "ctx.type",
          "type": "string",
          "settable": false,
          "notes": "Always. The hook name, e.g. `\"order.after_save\"`."
        },
        {
          "name": "ctx.data",
          "type": "object",
          "settable": false,
          "notes": "Always. The hook payload \u2014 see the per-hook table. **Mutating it in place is how you modify the entity** (see below)."
        },
        {
          "name": "ctx.old_data",
          "type": "object",
          "settable": false,
          "notes": "Only on the CRUD `*.before_save` / `*.after_save` / `*.before_delete` / `*.after_delete` hooks \u2014 the pre-change entity. Absent otherwise."
        },
        {
          "name": "ctx.settings",
          "type": "object",
          "settable": false,
          "notes": "Always. The plugin's merged settings (`{}` if none)."
        },
        {
          "name": "ctx.plan",
          "type": "string",
          "settable": false,
          "notes": "Always. Active plan key for this shop+plugin (`\"\"` = none)."
        },
        {
          "name": "ctx.shop_id",
          "type": "integer",
          "settable": false,
          "notes": "Always."
        },
        {
          "name": "ctx.dev",
          "type": "boolean",
          "settable": false,
          "notes": "Always. `true` when the plugin is running as your live local copy during a dev session; `false` for an installed plugin. Guard production-only side effects with it (see below)."
        },
        {
          "name": "ctx.request",
          "type": "object",
          "settable": false,
          "notes": "Only for storefront-dispatched hooks \u2014 the request map. Absent for scheduled/webhook dispatches."
        },
        {
          "name": "ctx.timeoutRemaining()",
          "type": "function \u2192 integer",
          "settable": false,
          "notes": "Always. Milliseconds left in the hook's budget (0 if exceeded)."
        },
        {
          "name": "ctx.stop(reason?)",
          "type": "function",
          "settable": false,
          "notes": "Always. Suppresses the platform default cleanly (see below)."
        },
        {
          "name": "Hook family",
          "type": "ctx.data",
          "settable": false,
          "notes": "`ctx.data`"
        },
        {
          "name": "product.before_save / .after_save / .before_delete / .after_delete",
          "type": "[Product](#product)",
          "settable": false,
          "notes": "Product"
        },
        {
          "name": "order.before_save / .after_save / .before_delete / .after_delete",
          "type": "[Order](#order)",
          "settable": false,
          "notes": "Order"
        },
        {
          "name": "customer.before_save / .after_save / .before_delete / .after_delete",
          "type": "[Customer](#customer)",
          "settable": false,
          "notes": "Customer"
        },
        {
          "name": "coupon.before_save / .after_save / .before_delete / .after_delete",
          "type": "[Coupon](#coupon)",
          "settable": false,
          "notes": "Coupon"
        },
        {
          "name": "record.<kind>.before_save / .after_save / .before_delete / .after_delete",
          "type": "[flattened custom record](#custom-records)",
          "settable": false,
          "notes": "flattened custom record"
        },
        {
          "name": "wired_fulfillment.before_save / .after_save / .before_delete / .after_delete",
          "type": "Fulfillment",
          "settable": false,
          "notes": "Fulfillment"
        },
        {
          "name": "Hook",
          "type": "ctx.data",
          "settable": false,
          "notes": "`ctx.data`"
        },
        {
          "name": "cart.calculate_prices",
          "type": "{ items: [{ product_id, shop_id, name, set, qty, price }], shop }",
          "settable": false,
          "notes": "`{ items: [{ product_id, shop_id, name, set, qty, price }], shop }`"
        },
        {
          "name": "coupon.validate",
          "type": "{ coupon, subtotal, cart }",
          "settable": false,
          "notes": "`{ coupon, subtotal, cart }`"
        },
        {
          "name": "checkout.before_create",
          "type": "{ order, cart, shop }",
          "settable": false,
          "notes": "`{ order, cart, shop }`"
        },
        {
          "name": "checkout.after_payment",
          "type": "{ order, provider, status }",
          "settable": false,
          "notes": "`{ order, provider, status }`"
        },
        {
          "name": "payment.before_intent",
          "type": "{ order, shop }",
          "settable": false,
          "notes": "`{ order, shop }`"
        },
        {
          "name": "payment.calculate_adjustment",
          "type": "{ order, shop, payment_method, adjustments: [] } \u2192 push { label, amount }",
          "settable": false,
          "notes": "`{ order, shop, payment_method, adjustments: [] }` \u2192 push `{ label, amount }`"
        },
        {
          "name": "payment.create_intent",
          "type": "{ provider, shop, order, payment }",
          "settable": false,
          "notes": "`{ provider, shop, order, payment }`"
        },
        {
          "name": "payment.refund",
          "type": "{ provider, order, amount, currency, reason, idempotency_key, payment_id } \u2192 set refund_id, status",
          "settable": false,
          "notes": "`{ provider, order, amount, currency, reason, idempotency_key, payment_id }` \u2192 set `refund_id`, `status`"
        },
        {
          "name": "payment.webhook",
          "type": "{ provider, body, headers }",
          "settable": false,
          "notes": "`{ provider, body, headers }`"
        },
        {
          "name": "payment.webhook_account",
          "type": "{ body, headers } \u2192 set account_id (runs with no sw. bridges)",
          "settable": false,
          "notes": "`{ body, headers }` \u2192 set `account_id` (runs with **no** `sw.*` bridges)"
        },
        {
          "name": "shipping.calculate",
          "type": "{ cart, weight, address, options: [] } \u2192 replace options (each option is { id, name, price, type, price_note?, pickup? }; set price_note to a short string like \"\u2014\" to show that text instead of the price when the final amount is pending, so a price: 0 placeholder isn't mistaken for \"Free\")",
          "settable": false,
          "notes": "`{ cart, weight, address, options: [] }` \u2192 replace `options` (each option is `{ id, name, price, type, price_note?, pickup? }`; set `price_note` to a short string like `\"\u2014\"` to show that text instead of the price when the final amount is pending, so a `price: 0` placeholder isn't mistaken for \"Free\")"
        },
        {
          "name": "tax.calculate",
          "type": "{ cart, subtotal, shipping, address } \u2192 set tax, name",
          "settable": false,
          "notes": "`{ cart, subtotal, shipping, address }` \u2192 set `tax`, `name`"
        },
        {
          "name": "Hook",
          "type": "ctx.data",
          "settable": false,
          "notes": "`ctx.data`"
        },
        {
          "name": "template.before_render",
          "type": "{ template, bindings } (+ ctx.customer = logged-in customer)",
          "settable": false,
          "notes": "`{ template, bindings }` (+ `ctx.customer` = logged-in customer)"
        },
        {
          "name": "email.marketing",
          "type": "{ to, customer_id, category, category_label, class, subject, template_name, data, html, text } \u2014 marketing-class messages only, before the store's layout is applied. html/text is the message's own content; data carries unsubscribe_url / preferences_url / shop. stop() hands delivery to you and skips the platform's send entirely",
          "settable": false,
          "notes": "`{ to, customer_id, category, category_label, class, subject, template_name, data, html, text }` \u2014 marketing-class messages only, before the store's layout is applied. `html`/`text` is the message's own content; `data` carries `unsubscribe_url` / `preferences_url` / `shop`. `stop()` hands delivery to you and skips the platform's send entirely"
        },
        {
          "name": "email.before_render",
          "type": "{ to, subject, template_name, template, bindings, notify_category, notify_class } \u2014 stop() cancels the send",
          "settable": false,
          "notes": "`{ to, subject, template_name, template, bindings, notify_category, notify_class }` \u2014 `stop()` cancels the send"
        },
        {
          "name": "email.send",
          "type": "{ to, cc, bcc, reply_to, from, from_name, subject, html, text, notify_category, notify_class }",
          "settable": false,
          "notes": "`{ to, cc, bcc, reply_to, from, from_name, subject, html, text, notify_category, notify_class }`"
        },
        {
          "name": "message.received",
          "type": "{ message, customer } \u2014 a [Message](#message) a customer sent, and their [Customer](#customer) (null if they aren't one yet). Set message.status = \"spam\" or message.unread = false; stop() skips the staff notification",
          "settable": false,
          "notes": "`{ message, customer }` \u2014 a Message a customer sent, and their Customer (`null` if they aren't one yet). Set `message.status = \"spam\"` or `message.unread = false`; `stop()` skips the staff notification"
        },
        {
          "name": "message.before_send",
          "type": "{ message } \u2014 a [Message](#message) staff or a plugin wrote, status: \"queued\". Set message.text / message.subject to reword; stop() holds it back",
          "settable": false,
          "notes": "`{ message }` \u2014 a Message staff or a plugin wrote, `status: \"queued\"`. Set `message.text` / `message.subject` to reword; `stop()` holds it back"
        },
        {
          "name": "sitemap.urls",
          "type": "{ urls: [] } \u2192 return [{ loc, lastmod, changefreq, priority }]",
          "settable": false,
          "notes": "`{ urls: [] }` \u2192 return `[{ loc, lastmod, changefreq, priority }]`"
        },
        {
          "name": "robots.txt",
          "type": "{ lines: [] } \u2192 append strings",
          "settable": false,
          "notes": "`{ lines: [] }` \u2192 append strings"
        },
        {
          "name": "search.query",
          "type": "{ query, cursor, limit, own_only, sort, price_min, price_max, stream, filters?, facets?, count_accuracy } \u2192 set products: [{ id, shop_id }], cursor, result_count?",
          "settable": false,
          "notes": "`{ query, cursor, limit, own_only, sort, price_min, price_max, stream, filters?, facets?, count_accuracy }` \u2192 set `products: [{ id, shop_id }]`, `cursor`, `result_count?`"
        },
        {
          "name": "search.index",
          "type": "{ products: [<product maps>] }",
          "settable": false,
          "notes": "`{ products: [<product maps>] }`"
        },
        {
          "name": "search.remove",
          "type": "{ product_ids: [{ id, shop_id }] }",
          "settable": false,
          "notes": "`{ product_ids: [{ id, shop_id }] }`"
        },
        {
          "name": "search.drop",
          "type": "{}",
          "settable": false,
          "notes": "`{}`"
        },
        {
          "name": "Hook",
          "type": "ctx.data",
          "settable": false,
          "notes": "`ctx.data`"
        },
        {
          "name": "plugin.activate / plugin.deactivate / plugin.uninstall",
          "type": "{ plugin_id, version }",
          "settable": false,
          "notes": "`{ plugin_id, version }`"
        },
        {
          "name": "plugin.change_version",
          "type": "{ plugin_id, version, old_version }",
          "settable": false,
          "notes": "`{ plugin_id, version, old_version }`"
        },
        {
          "name": "iap.purchase",
          "type": "{ plugin_id, product_key, type, amount, credits, purchase_id, dev }",
          "settable": false,
          "notes": "`{ plugin_id, product_key, type, amount, credits, purchase_id, dev }`"
        },
        {
          "name": "container.job.completed",
          "type": "{ job_id, status, exit_code, cost_cents, result_url, error }",
          "settable": false,
          "notes": "`{ job_id, status, exit_code, cost_cents, result_url, error }`"
        },
        {
          "name": "Field",
          "type": "Type",
          "settable": false,
          "notes": "Notes"
        },
        {
          "name": "ctx.request",
          "type": "object",
          "settable": false,
          "notes": "The request map + body accessors."
        },
        {
          "name": "ctx.shop_id",
          "type": "integer",
          "settable": false,
          "notes": ""
        },
        {
          "name": "ctx.user_id",
          "type": "integer",
          "settable": false,
          "notes": "Acting staff user."
        },
        {
          "name": "ctx.role",
          "type": "string",
          "settable": false,
          "notes": "That user's shop role."
        },
        {
          "name": "ctx.permissions",
          "type": "[]string",
          "settable": false,
          "notes": "The plugin's granted permissions for this user."
        },
        {
          "name": "ctx.settings / ctx.plan / ctx.dev",
          "type": "object / string / boolean",
          "settable": false,
          "notes": "As in hooks."
        },
        {
          "name": "ctx.widget",
          "type": "object",
          "settable": false,
          "notes": "See below."
        },
        {
          "name": "Field",
          "type": "Type",
          "settable": false,
          "notes": "Notes"
        },
        {
          "name": "id",
          "type": "string",
          "settable": false,
          "notes": "Widget id."
        },
        {
          "name": "key",
          "type": "string",
          "settable": false,
          "notes": "Dashboard placement key (empty for page widgets)."
        },
        {
          "name": "config",
          "type": "object",
          "settable": false,
          "notes": "Merchant-configured instance config (per `config_defs`)."
        },
        {
          "name": "csrf",
          "type": "string",
          "settable": false,
          "notes": "Token for the widget's own POSTs (`sw-post` / `sw.fetch`)."
        },
        {
          "name": "page",
          "type": "boolean",
          "settable": false,
          "notes": ""
        },
        {
          "name": "dashboard_id",
          "type": "integer",
          "settable": false,
          "notes": "Only when placed on a dashboard."
        },
        {
          "name": "placement",
          "type": "string",
          "settable": false,
          "notes": "`\"button\"`."
        },
        {
          "name": "entity",
          "type": "{ type, id }",
          "settable": false,
          "notes": "**Detail-page widgets only** \u2014 the bound record (e.g. `{ type: \"order\", id: 42 }`). Load it via the matching `sw.*` bridge."
        },
        {
          "name": "user",
          "type": "{ id, email, role, permissions }",
          "settable": false,
          "notes": "When a user resolved."
        },
        {
          "name": "shop",
          "type": "object",
          "settable": false,
          "notes": "The shop projection."
        },
        {
          "name": "base",
          "type": "string",
          "settable": false,
          "notes": "Base path for the widget."
        },
        {
          "name": "url(p)",
          "type": "function \u2192 string",
          "settable": false,
          "notes": "Builds a URL under `base`."
        },
        {
          "name": "Header key",
          "type": "Meaning",
          "settable": false,
          "notes": "Meaning"
        },
        {
          "name": "X-Real-Ip",
          "type": "Client IP.",
          "settable": false,
          "notes": "Client IP."
        },
        {
          "name": "X-Geo-Country / X-Geo-Region / X-Geo-City / X-Geo-Postal / X-Geo-Latlong",
          "type": "Geo-IP (X-Geo-Latlong is one \"lat,long\" string).",
          "settable": false,
          "notes": "Geo-IP (`X-Geo-Latlong` is one `\"lat,long\"` string)."
        },
        {
          "name": "X-Bot-Score / X-Verified-Bot",
          "type": "Bot detection.",
          "settable": false,
          "notes": "Bot detection."
        },
        {
          "name": "Host",
          "type": "Canonical forwarded host.",
          "settable": false,
          "notes": "Canonical forwarded host."
        },
        {
          "name": "Field",
          "type": "Type",
          "settable": false,
          "notes": "Where"
        },
        {
          "name": "ctx.args",
          "type": "any",
          "settable": false,
          "notes": "`sw.task.bg` closure \u2014 the payload you enqueued (object, array, or literal), verbatim; `undefined` if none."
        },
        {
          "name": "ctx.params",
          "type": "object",
          "settable": false,
          "notes": "Action scripts \u2014 the action param values."
        },
        {
          "name": "ctx.continue",
          "type": "{ depth, data }",
          "settable": false,
          "notes": "Continuation state for a re-enqueued task."
        },
        {
          "name": "ctx.shop",
          "type": "object",
          "settable": false,
          "notes": "The shop projection, when in shop context."
        }
      ]
    },
    "Variant": {
      "fields": [
        {
          "name": "set",
          "type": "object<string,string>",
          "settable": false,
          "notes": "The option combination, e.g. `{ \"Size\": \"S\", \"Color\": \"Red\" }`."
        },
        {
          "name": "sku",
          "type": "string",
          "settable": false,
          "notes": ""
        },
        {
          "name": "price",
          "type": "integer\\",
          "settable": false,
          "notes": "`null` = use base price."
        },
        {
          "name": "compare_price",
          "type": "integer\\",
          "settable": false,
          "notes": ""
        },
        {
          "name": "prices",
          "type": "object<string,integer\\",
          "settable": false,
          "notes": "Per-tier overrides."
        },
        {
          "name": "stock",
          "type": "integer\\",
          "settable": false,
          "notes": "`null` = use base stock; `-1` = unlimited."
        },
        {
          "name": "oversell",
          "type": "boolean\\",
          "settable": false,
          "notes": "`null` = inherit product policy."
        },
        {
          "name": "images",
          "type": "[]string",
          "settable": false,
          "notes": ""
        },
        {
          "name": "attrs",
          "type": "[][Attribute](#attribute)",
          "settable": false,
          "notes": ""
        },
        {
          "name": "price_tiers",
          "type": "[][PriceTier](#pricetier)",
          "settable": false,
          "notes": "Empty = use product-level tiers."
        }
      ]
    },
    "Widget ctx": {
      "fields": [
        {
          "name": "ctx.request",
          "type": "object",
          "settable": false,
          "notes": "The request map + body accessors."
        },
        {
          "name": "ctx.shop_id",
          "type": "integer",
          "settable": false,
          "notes": ""
        },
        {
          "name": "ctx.user_id",
          "type": "integer",
          "settable": false,
          "notes": "Acting staff user."
        },
        {
          "name": "ctx.role",
          "type": "string",
          "settable": false,
          "notes": "That user's shop role."
        },
        {
          "name": "ctx.permissions",
          "type": "[]string",
          "settable": false,
          "notes": "The plugin's granted permissions for this user."
        },
        {
          "name": "ctx.settings / ctx.plan / ctx.dev",
          "type": "object / string / boolean",
          "settable": false,
          "notes": "As in hooks."
        },
        {
          "name": "ctx.widget",
          "type": "object",
          "settable": false,
          "notes": "See below."
        },
        {
          "name": "Field",
          "type": "Type",
          "settable": false,
          "notes": "Notes"
        },
        {
          "name": "id",
          "type": "string",
          "settable": false,
          "notes": "Widget id."
        },
        {
          "name": "key",
          "type": "string",
          "settable": false,
          "notes": "Dashboard placement key (empty for page widgets)."
        },
        {
          "name": "config",
          "type": "object",
          "settable": false,
          "notes": "Merchant-configured instance config (per `config_defs`)."
        },
        {
          "name": "csrf",
          "type": "string",
          "settable": false,
          "notes": "Token for the widget's own POSTs (`sw-post` / `sw.fetch`)."
        },
        {
          "name": "page",
          "type": "boolean",
          "settable": false,
          "notes": ""
        },
        {
          "name": "dashboard_id",
          "type": "integer",
          "settable": false,
          "notes": "Only when placed on a dashboard."
        },
        {
          "name": "placement",
          "type": "string",
          "settable": false,
          "notes": "`\"button\"`."
        },
        {
          "name": "entity",
          "type": "{ type, id }",
          "settable": false,
          "notes": "**Detail-page widgets only** \u2014 the bound record (e.g. `{ type: \"order\", id: 42 }`). Load it via the matching `sw.*` bridge."
        },
        {
          "name": "user",
          "type": "{ id, email, role, permissions }",
          "settable": false,
          "notes": "When a user resolved."
        },
        {
          "name": "shop",
          "type": "object",
          "settable": false,
          "notes": "The shop projection."
        },
        {
          "name": "base",
          "type": "string",
          "settable": false,
          "notes": "Base path for the widget."
        },
        {
          "name": "url(p)",
          "type": "function \u2192 string",
          "settable": false,
          "notes": "Builds a URL under `base`."
        }
      ]
    }
  },
  "hooks": {
    "cart.calculate_prices": {
      "ctx_data": "`{ items: [{ product_id, shop_id, name, set, qty, price }], shop }`"
    },
    "checkout.after_payment": {
      "ctx_data": "`{ order, provider, status }`"
    },
    "checkout.before_create": {
      "ctx_data": "`{ order, cart, shop }`"
    },
    "container.job.completed": {
      "ctx_data": "`{ job_id, status, exit_code, cost_cents, result_url, error }`"
    },
    "coupon.after_delete": {
      "ctx_data": "Coupon"
    },
    "coupon.after_save": {
      "ctx_data": "Coupon"
    },
    "coupon.before_delete": {
      "ctx_data": "Coupon"
    },
    "coupon.before_save": {
      "ctx_data": "Coupon"
    },
    "coupon.validate": {
      "ctx_data": "`{ coupon, subtotal, cart }`"
    },
    "customer.after_delete": {
      "ctx_data": "Customer"
    },
    "customer.after_save": {
      "ctx_data": "Customer"
    },
    "customer.before_delete": {
      "ctx_data": "Customer"
    },
    "customer.before_save": {
      "ctx_data": "Customer"
    },
    "email.before_render": {
      "ctx_data": "`{ to, subject, template_name, template, bindings, notify_category, notify_class }` \u2014 `stop()` cancels the send"
    },
    "email.marketing": {
      "ctx_data": "`{ to, customer_id, category, category_label, class, subject, template_name, data, html, text }` \u2014 marketing-class messages only, before the store's layout is applied. `html`/`text` is the message's own content; `data` carries `unsubscribe_url` / `preferences_url` / `shop`. `stop()` hands delivery to you and skips the platform's send entirely"
    },
    "email.send": {
      "ctx_data": "`{ to, cc, bcc, reply_to, from, from_name, subject, html, text, notify_category, notify_class }`"
    },
    "iap.purchase": {
      "ctx_data": "`{ plugin_id, product_key, type, amount, credits, purchase_id, dev }`"
    },
    "message.before_send": {
      "ctx_data": "`{ message }` \u2014 a Message staff or a plugin wrote, `status: \"queued\"`. Set `message.text` / `message.subject` to reword; `stop()` holds it back"
    },
    "message.received": {
      "ctx_data": "`{ message, customer }` \u2014 a Message a customer sent, and their Customer (`null` if they aren't one yet). Set `message.status = \"spam\"` or `message.unread = false`; `stop()` skips the staff notification"
    },
    "order.after_delete": {
      "ctx_data": "Order"
    },
    "order.after_save": {
      "ctx_data": "Order"
    },
    "order.before_delete": {
      "ctx_data": "Order"
    },
    "order.before_save": {
      "ctx_data": "Order"
    },
    "payment.before_intent": {
      "ctx_data": "`{ order, shop }`"
    },
    "payment.calculate_adjustment": {
      "ctx_data": "`{ order, shop, payment_method, adjustments: [] }` \u2192 push `{ label, amount }`"
    },
    "payment.create_intent": {
      "ctx_data": "`{ provider, shop, order, payment }`"
    },
    "payment.refund": {
      "ctx_data": "`{ provider, order, amount, currency, reason, idempotency_key, payment_id }` \u2192 set `refund_id`, `status`"
    },
    "payment.webhook": {
      "ctx_data": "`{ provider, body, headers }`"
    },
    "payment.webhook_account": {
      "ctx_data": "`{ body, headers }` \u2192 set `account_id` (runs with **no** `sw.*` bridges)"
    },
    "plugin.activate": {
      "ctx_data": "`{ plugin_id, version }`"
    },
    "plugin.change_version": {
      "ctx_data": "`{ plugin_id, version, old_version }`"
    },
    "plugin.deactivate": {
      "ctx_data": "`{ plugin_id, version }`"
    },
    "plugin.uninstall": {
      "ctx_data": "`{ plugin_id, version }`"
    },
    "product.after_delete": {
      "ctx_data": "Product"
    },
    "product.after_save": {
      "ctx_data": "Product"
    },
    "product.before_delete": {
      "ctx_data": "Product"
    },
    "product.before_save": {
      "ctx_data": "Product"
    },
    "record.<kind>.after_delete": {
      "ctx_data": "flattened custom record"
    },
    "record.<kind>.after_save": {
      "ctx_data": "flattened custom record"
    },
    "record.<kind>.before_delete": {
      "ctx_data": "flattened custom record"
    },
    "record.<kind>.before_save": {
      "ctx_data": "flattened custom record"
    },
    "robots.txt": {
      "ctx_data": "`{ lines: [] }` \u2192 append strings"
    },
    "search.drop": {
      "ctx_data": "`{}`"
    },
    "search.index": {
      "ctx_data": "`{ products: [<product maps>] }`"
    },
    "search.query": {
      "ctx_data": "`{ query, cursor, limit, own_only, sort, price_min, price_max, stream, filters?, facets?, count_accuracy }` \u2192 set `products: [{ id, shop_id }]`, `cursor`, `result_count?`"
    },
    "search.remove": {
      "ctx_data": "`{ product_ids: [{ id, shop_id }] }`"
    },
    "shipping.calculate": {
      "ctx_data": "`{ cart, weight, address, options: [] }` \u2192 replace `options` (each option is `{ id, name, price, type, price_note?, pickup? }`; set `price_note` to a short string like `\"\u2014\"` to show that text instead of the price when the final amount is pending, so a `price: 0` placeholder isn't mistaken for \"Free\")"
    },
    "sitemap.urls": {
      "ctx_data": "`{ urls: [] }` \u2192 return `[{ loc, lastmod, changefreq, priority }]`"
    },
    "tax.calculate": {
      "ctx_data": "`{ cart, subtotal, shipping, address }` \u2192 set `tax`, `name`"
    },
    "template.before_render": {
      "ctx_data": "`{ template, bindings }` (+ `ctx.customer` = logged-in customer)"
    },
    "wired_fulfillment.after_delete": {
      "ctx_data": "Fulfillment"
    },
    "wired_fulfillment.after_save": {
      "ctx_data": "Fulfillment"
    },
    "wired_fulfillment.before_delete": {
      "ctx_data": "Fulfillment"
    },
    "wired_fulfillment.before_save": {
      "ctx_data": "Fulfillment"
    }
  },
  "bridges": [
    "sw.assets",
    "sw.audit",
    "sw.base",
    "sw.bus",
    "sw.cache",
    "sw.close",
    "sw.confirm",
    "sw.container",
    "sw.coupons",
    "sw.crypto",
    "sw.csrf",
    "sw.csv",
    "sw.customers",
    "sw.email",
    "sw.entity",
    "sw.excel",
    "sw.fetch",
    "sw.field",
    "sw.files",
    "sw.gdrive",
    "sw.hooks",
    "sw.iap",
    "sw.init",
    "sw.jwt",
    "sw.ledger",
    "sw.liquid",
    "sw.messages",
    "sw.notify",
    "sw.on",
    "sw.open",
    "sw.orders",
    "sw.page",
    "sw.payments",
    "sw.pick",
    "sw.products",
    "sw.records",
    "sw.refresh",
    "sw.search",
    "sw.secrets",
    "sw.sql",
    "sw.state",
    "sw.storage",
    "sw.subscriptions",
    "sw.task",
    "sw.test",
    "sw.time",
    "sw.toast",
    "sw.track",
    "sw.widget",
    "sw.window",
    "sw.zip"
  ]
}
