Skip to content

REST API Reference

{/* AUTO-GENERATED from ../docs/rest-api-reference.md by scripts/sync-dev-docs.mjs — do not edit by hand. */}

Dino Discounts exposes 32 admin REST routes under /wp-json/dino-discounts/v1/ — counted as register_rest_route() calls in includes/API/. One of those calls (/onboarding) bundles three HTTP methods (GET / POST / DELETE) into a single registration, so the HTTP-method count is slightly higher. Plus a WooCommerce Store API extension that decorates /wp-json/wc/store/v1/cart with active-discount metadata. This page is the authoritative reference for integrators — every route below matches a register_rest_route() call in includes/API/.

Rex rex. Enjoy the docs.

  • Version: v1 (baked into the namespace — there is no /v2)
  • Base URL: /wp-json/dino-discounts/v1
  • Content type: All request bodies and responses are JSON unless an endpoint declares otherwise (CSV / ZIP / TXT exports).
  • Related reading: Architecture · Hooks reference

  1. Authentication
  2. Conventions
  3. Rules/rules, /rules/diff-summary, /rules/draft
  4. Global settings/global_settings
  5. Rule snapshots/snapshots
  6. Cart preview/preview
  7. Cart scenarios/cart-scenarios
  8. Coupon campaigns/coupon-campaigns
  9. Analytics/analytics/*
  10. Product & taxonomy search/products/search, /taxonomy/search
  11. Diagnostics/performance/log
  12. Onboarding/onboarding
  13. Store API extensionwc/store/v1/cart extensions.dino-discounts
  14. Error shape

Every route registers AbstractController::check_permission() as its permission_callback. That callback requires the current user to have the manage_woocommerce capability. Unauthenticated or under-privileged requests get the standard WordPress rest_forbidden / rest_cookie_invalid_nonce response (HTTP 401 / 403).

There are no public routes — every route is admin-only.

MethodHeader / paramNotes
Logged-in cookie + nonceX-WP-Nonce: <nonce>The canonical admin path. The plugin localises a fresh wp_rest nonce into window.dinoDiscountsAdminData.nonce (includes/Admin.php:311).
Application PasswordAuthorization: Basic <base64(user:app_password)>WordPress built-in. Works on HTTPS.
Basic Auth plugin / JWT / OAuth1Per pluginNot bundled — use if you have an existing setup.

The plugin does not add custom CORS headers. If you need to call these endpoints from another origin, configure CORS at the WordPress layer (rest_pre_serve_request) or a reverse proxy.

$nonce = wp_create_nonce( 'wp_rest' );

From JavaScript inside the admin app, use window.dinoDiscountsAdminData.nonce.

StatusError codeMeaning
401rest_not_logged_inNo cookie / application password.
403rest_forbiddenLogged in but missing manage_woocommerce.
403rest_cookie_invalid_nonceNonce header missing, stale (WP default TTL is 24h via the nonce_life filter), or bound to a different user. Fetch a fresh nonce.

JSON only. Set Content-Type: application/json on every POST/PUT/PATCH/DELETE that carries a body.

There is no single envelope — each route returns a resource-shaped object. Mutating routes typically return { "success": true, ... } plus the updated resource; read routes return the resource directly.

Only /analytics/discounts, /analytics/export, and /products/search paginate. They all use the same pattern:

  • page — 1-indexed, default 1
  • per_page — default varies (25 for analytics, 20 for products), capped at 100
  • Response includes a pagination object (analytics) or top-level total / totalPages (products).

Other list routes (snapshots, scenarios, campaigns, etc.) return the full collection in one response. These lists are bounded by the domain (200 rules max, small snapshot counts in practice).

None is enforced by the plugin. Bulk coupon generation is capped at 10,000 codes per request; rule saves are capped at 200 rules total.

Date parameters use YYYY-MM-DD. Analytics date ranges are interpreted against the store timezone (DateTimeHelper::store_now()); the resolved timezone is echoed back in the response under date_range.timezone or timezone.


Manage the active discount rule set and its in-progress draft.

List the currently-active rules and any saved draft.

Response — 200 OK

{
"rules": [
{
"id": "rule-abc",
"name": "10% off Summer",
"type": "tiered",
"is_active": true,
"priority": 1,
"targeting_tree": { "children": [...] },
"tiers": [...]
}
],
"draft": {
"rules": [...],
"global_settings": {...}
}
}

draft is omitted if no draft is saved.


Overwrite the active rule set. The engine cache is invalidated and an audit snapshot is written; if the snapshot write fails, the save is rolled back.

Request body

FieldTypeRequiredNotes
rulesarrayList of rule objects. Order is the source of truth for evaluation priority. Max 200.
snapshot_namestringOptional name for the audit snapshot.
commentstringOptional free-text comment attached to the snapshot.

Response — 200 OK

{ "success": true, "rules": [ /* sanitised rules */ ] }

Errors

StatusCodeWhen
400invalid_datarules is not an array.
400too_many_rulesMore than 200 rules supplied.
400unknown_rule_fieldRuleSanitizer rejected an unknown or malformed field.
500snapshot_failedThe audit snapshot could not be written; the save was rolled back.

Example

Terminal window
curl -X POST "https://example.com/wp-json/dino-discounts/v1/rules" \
-H "X-WP-Nonce: $NONCE" \
-H "Content-Type: application/json" \
--cookie "$WP_COOKIE" \
-d '{"rules":[{"id":"rule-abc","name":"10% off Summer","type":"tiered","is_active":true,"tiers":[{"min":2,"percent":10}]}]}'

Fires: dino_discounts_rules_saved, dino_discounts_cache_flush.


Compare a proposed rule set against the currently-saved rules without saving. Useful for diff previews in the admin UI.

Request body: same rules array as POST /rules.

Response — 200 OK

{
"diff_summary": {
"added": [...],
"removed": [...],
"changed": [...]
}
}

Errors: invalid_data, too_many_rules, unknown_rule_field (all 400).


Save an in-progress rule edit session (rules + global_settings) so it survives page reloads. Does not affect the live rules — the draft is a display-layer convenience.

Request body

FieldTypeRequired
rulesarray
global_settingsobject

Response — 200 OK

{ "success": true }

Errors: too_many_rules (400), unknown_rule_field (400).


Discard the saved draft.

Response — 200 OK

{ "success": true }

Store-wide discount configuration (fallback stacking mode, display strings, custom zones, performance flags).

Response — 200 OK

Returns the full dino_discounts_global_settings option. Shape is defined by GlobalSettingsDefaults::DEFAULTS.


Request body

FieldTypeRequiredNotes
settingsobjectFull or partial settings object. Omitting custom_zones preserves the existing zones (a missing key is not treated as “clear the zones”).

Response — 200 OK

{ "success": true, "settings": { /* sanitised settings */ } }

Errors

StatusCodeWhen
400invalid_datasettings is missing or not an object.

Fires: dino_discounts_settings_saved, dino_discounts_cache_flush.

Note: this route is registered under the namespace path /global_settings (underscore) — the only underscore in the route inventory. Keep it exact.


Historical copies of the rule set written on every POST /rules. Used for audit and restore.

List snapshot metadata (no rule bodies).

Response — 200 OK

[
{ "id": 42, "snapshot_name": "Pre-Black-Friday", "comment": "...", "created_at": "2026-04-18T09:00:00Z", "rule_count": 14, "pinned": true }
]

Fetch a single snapshot including its full rules payload.

Response — 200 OK: the snapshot object with a populated rules array.

Errors

StatusCodeWhen
404not_foundNo snapshot with that id.
500snapshot_corruptThe stored rules_json blob is invalid. Delete and recreate.

Update snapshot metadata (rename, edit comment, pin). Body accepts snapshot_name and/or comment; omitted fields are left unchanged.

Response — 200 OK

{ "success": true }

Errors: not_found (404), db_error (500).


Delete a snapshot.

Response — 200 OK: { "success": true }.

Errors: cannot_delete (404) — the snapshot no longer exists.


Replace the active rule set with the contents of this snapshot. Invalidates the engine cache.

Response — 200 OK

{ "success": true, "rules": [ /* restored rules */ ] }

Errors: not_found (404).

Example

Terminal window
curl -X POST "https://example.com/wp-json/dino-discounts/v1/snapshots/42/restore" \
-H "X-WP-Nonce: $NONCE" \
--cookie "$WP_COOKIE"

Run the discount engine against a hypothetical cart with in-memory rules — no session, no persistence. Used by the admin live previewer.

Request body

FieldTypeRequiredNotes
cartarrayArray of { product_id, variation_id, quantity, price, name, isCustom }.
rulesarrayRule set to evaluate (does not have to match the saved rules).
global_settingsobjectIf set, replaces the store’s global settings for this evaluation.
cart_totalnumberIgnored — recalculated server-side from cart.
currencystringPreview currency (e.g. USD). If the store has WCPBC, the zone price resolver runs against this.
countrystringISO-2 country code.
user_rolestringSimulate a specific user role (e.g. customer, subscriber).
coupon_codesarray of stringSimulated applied coupon codes.
coupon_codestringLegacy single-code param, kept for backwards compat. Prefer coupon_codes.
order_countintSimulated customer order_count.
total_spentnumberSimulated customer total_spent.
days_since_last_orderintSimulated history field.
days_since_registrationintSimulated history field.
debugboolIf true, includes per-rule trace output.

Response — 200 OK

{
"success": true,
"discounts": [
{ "rule_id": "rule-abc", "label": "10% off Summer", "amount": 3.50, "percent": 10, "discount_type": "tiered" }
],
"cart": [ /* sanitised + repriced cart */ ],
"cart_total": 35.00,
"trace": [ /* only when debug=true */ ]
}

When WP_DEBUG is defined and truthy, an additional _debug object is included.

Errors: unknown_rule_field (400).


Merchant-saveable named cart-preview contexts (items + filters), shared across admins. Stored in the dino_discounts_cart_scenarios option. On first read, six defaults are seeded.

Response — 200 OK

{
"scenarios": [
{
"id": "default-small-cart",
"name": "Small cart",
"is_default": true,
"items": [ { "product_id": 12, "variation_id": 0, "quantity": 1, "price": 9.99, "name": "Widget", "is_stale": false } ],
"filters": { "currency": "USD", "isLoggedIn": true }
}
]
}

Each item carries is_stale: true if the referenced product no longer exists in the catalogue.


Create a new scenario.

Request body

FieldTypeRequired
namestring
itemsarray
filtersobject

Response — 200 OK: { "success": true, "scenario": {...} }.

Errors: invalid_params (400).


Partial update. Any of name, items, filters may be omitted — omitted fields are left unchanged.

Response — 200 OK: { "success": true, "scenario": {...} }.

Errors: invalid_params (400), not_found (404).


Delete a scenario (defaults and merchant-saved alike).

Response — 200 OK: { "success": true, "id": "..." }.


Re-seed the six default scenarios. Non-destructive: merchant-saved scenarios are preserved.

Response — 200 OK: { "success": true, "scenarios": [...] }.


Manage code pools and single codes used to unlock rules.

Every per-campaign route below — /history, /export, /export-txt, /archive, /delete — uses the same route regex [a-zA-Z0-9_\-]+ for the {campaign} segment. The regex requires at least one character, so the empty-string branch of each handler’s “is the campaign empty?” check is guarded away at the router — invalid_campaign (400) is unreachable via the registered route.

Handlers still contain the empty( $campaign ) check as defence-in-depth, and the error is listed per route for completeness. Integrators should not expect to see invalid_campaign (400) in practice on these routes. A malformed or missing {campaign} segment produces a 404 from WordPress’s REST router before the handler runs.

(/generate and /history/{batch_id}/mark-downloaded use the same regex guard but return the broader invalid_params error code, which remains reachable via the other parameters those handlers validate — it is not covered by this note.)

List campaigns.

Query

ParamTypeDefault
include_archivedboolfalse

Response — 200 OK: [ { campaign, activation_type, usage_limit, total, used, expires_at, ... } ].


Create a campaign. The body differs by activation type.

Common fields

FieldTypeRequiredNotes
campaignstring✅ (unless generate_random=true)Campaign slug (A–Z, 0–9, _, -).
activation_typestringsingle_code (default), url_token, or bulk_pool.
generate_randomboolServer-generates a unique code. Only valid for single_code.
usage_limitintPer-code usage limit. Default 0 (unlimited).
expires_atstringDatetime in the store timezone (parsed via DateTimeHelper::parse_store_datetime).

bulk_pool extras

FieldTypeRequiredNotes
quantityint1 ≤ quantity ≤ 10000.

The campaign code is always used as the prefix for generated bulk codes.

Response — 200 OK — single_code / url_token

{ "success": true, "campaign": "SUMMER10", "generated_random": false }

Response — 200 OK — bulk_pool

{
"success": true,
"complete": true,
"partial": false,
"message": "",
"requested": 1000,
"campaign": "SUMMER",
"generated": 1000,
"attempts": 1024,
"collisions": 24,
"exhausted": false,
"stats": { "total": 1000, "used": 0, "remaining": 1000 }
}

If not all codes could be generated due to collisions, complete: false, partial: true, and a message explaining the top-up path are returned.

Errors: invalid_params (400), campaign_exists (400), quantity_too_large (400), code_generation_failed (500), create_failed (500).


Search campaigns by code or fragment (case-insensitive, alphanumeric + -_). Returns up to 50 ranked matches per page.

Query

ParamTypeRequiredNotes
codestringFull code or substring.
offsetintDefault 0.

Response — 200 OK

{
"found": true,
"total": 3,
"results": [ { "campaign": "SUMMER10", "activation_type": "single_code", ... } ],
"result": { /* first result, back-compat */ }
}

Errors: invalid_code (400).


Generate a unique random campaign code without saving it.

Query

ParamTypeRequired
prefixstring

Response — 200 OK: { "success": true, "code": "PFX-4F9A-12BD" }.

Errors: code_generation_failed (500).


Check whether a WooCommerce native coupon already owns the given code.

Query

ParamTypeRequired
codestring✅ (else returns clash: false)

Response — 200 OK: { "clash": true, "code": "SUMMER10" }.


Given a snapshot id, list active campaigns that would lose their linked rule if that snapshot were restored.

Query

ParamTypeRequired
snapshot_idint

Response — 200 OK: { "orphaned": ["SUMMER10", "FALL20"] }.

Errors: invalid_snapshot (400), not_found (404).


Generation-batch audit trail for a bulk campaign, annotated with per-user “downloaded” flags.

Response — 200 OK

{
"batches": [
{ "batch_id": 7, "generated_at": "2026-04-01T12:00:00Z", "user_id": 3, "quantity": 500, "downloaded": true }
]
}

Errors: invalid_campaign (400) — unreachable via the registered route.


POST /coupon-campaigns/{campaign}/history/{batch_id}/mark-downloaded

Section titled “POST /coupon-campaigns/{campaign}/history/{batch_id}/mark-downloaded”

Mark a generation batch as “downloaded” for the current user (audit flag).

Response — 200 OK: { "success": true, "batch_id": 7 }.

Errors: invalid_params (400), rest_not_logged_in (401), not_found (404).


Download codes as CSV.

Query

ParamValueEffect
batch_idintExport just one generation batch.
formatinfo or auditInclude batch metadata columns (batch_no, times_used, generated_date, generated_by). audit is kept as an alias of info.

Response: text/csv with Content-Disposition: attachment. Not a JSON response — the handler streams bytes and exits.

Errors: invalid_campaign (400, unreachable via the registered route), not_found (404).


GET /coupon-campaigns/{campaign}/export-txt

Section titled “GET /coupon-campaigns/{campaign}/export-txt”

Download all codes as plain text (one per line). Response: text/plain attachment.

Errors: invalid_campaign (400) — unreachable via the registered route.


Download a campaigns-summary CSV for all active campaigns (one row per campaign).

Response: text/csv attachment, filename dino-campaigns.csv.


Download a ZIP containing the summary CSV plus one CSV per bulk-pool campaign.

Response: application/zip attachment, filename dino-campaigns-export-<date>.zip.

Errors: zip_unavailable (500) if PHP ZipArchive is missing; zip_open_failed (500).


Soft-delete (archive) a campaign.

Response — 200 OK: { "success": true, "campaign": "SUMMER10" }.

Errors: invalid_campaign (400) — unreachable via the registered route.


POST /coupon-campaigns/{campaign}/generate

Section titled “POST /coupon-campaigns/{campaign}/generate”

Generate more codes for an existing bulk-pool campaign. Inherits the existing usage_limit.

Request body

FieldTypeRequired
quantityint✅ (1 ≤ q ≤ 10000)

Response — 200 OK: same shape as bulk-pool create (complete, partial, generated, collisions, stats, …).

Errors: invalid_params (400), quantity_too_large (400), not_found (404).


DELETE /coupon-campaigns/{campaign}/delete

Section titled “DELETE /coupon-campaigns/{campaign}/delete”

Permanently delete an archived campaign and all associated codes + generation-log rows. The campaign must already be archived — this is enforced server-side.

Response — 200 OK: { "success": true, "campaign": "SUMMER10" }.

Errors: invalid_campaign (400, unreachable via the registered route), not_archived (400).


All three analytics routes accept from / to (YYYY-MM-DD, store-timezone) and default to the last 30 days.

Per-rule application counts, redemption amounts, gross revenue, time series, and per-bucket order-value distributions.

Query

ParamTypeDefaultConstraint
fromdatenow − 30dYYYY-MM-DD
todatenowYYYY-MM-DD
per_pageint251–100
pageint1≥1

Response — 200 OK

{
"rules": [
{
"rule_id": "rule-abc",
"rule_name": "10% off Summer",
"rule_type": "tiered",
"is_active": true,
"application_count": 120,
"order_count": 95,
"total_amount": 412.50,
"total_gross": 3720.00,
"aov_with_rule": 39.16,
"new_customer_pct": 22.1,
"time_series": [ { "date": "2026-04-01", "application_count": 4, "order_count": 3, "total_amount": 15.00, "total_gross": 120.00, "aov": 40.00 } ],
"distribution": [ { "bucket": 30, "label": "30-39", "count": 12 } ]
}
],
"totals": {
"total_orders_with_discounts": 95,
"total_discount_amount": 412.50,
"total_store_orders": 540
},
"pagination": { "page": 1, "per_page": 25, "total_rules": 7 },
"date_range": { "from": "2026-03-19", "to": "2026-04-18", "timezone": "Europe/London" }
}

Errors: rest_invalid_param (400) on malformed from / to.


Per-campaign snapshot (codes issued, redeemed, remaining, utilisation%) and a per-rule daily time series filtered to rules with an active campaign.

Query: from, to (same semantics as above).

Response — 200 OK

{
"campaigns": [
{
"campaign": "SUMMER10",
"rule_id": "rule-abc",
"rule_name": "10% off Summer",
"activation_type": "single_code",
"codes_issued": 1,
"codes_redeemed": 47,
"codes_remaining": 0,
"utilisation_pct": 4700.0,
"last_used_at": "2026-04-15 14:22:01",
"expires_at": null
}
],
"time_series": {
"SUMMER10": [ { "date": "2026-04-01", "redemptions": 4, "discount_total": 25.00 } ]
},
"totals": {
"total_campaigns": 1,
"total_codes_issued": 1,
"total_codes_redeemed": 47,
"total_discount_given": 235.00
},
"date_range": { "from": "2026-03-19", "to": "2026-04-18" },
"timezone": "Europe/London"
}

Stream raw event rows as CSV for the same date window.

Response: text/csv attachment dino_analytics_<from>_to_<to>.csv with columns Order ID,Rule ID,Date,Discount Amount,Gross Revenue,Is New Customer.

Example

Terminal window
# -J (use server filename) requires -O. Combined short form -OJ is portable;
# -O -J or -J -O also work. Some shells glob differently — the combined form
# is safest in zsh.
curl -OJ "https://example.com/wp-json/dino-discounts/v1/analytics/export?from=2026-04-01&to=2026-04-18" \
-H "X-WP-Nonce: $NONCE" \
--cookie "$WP_COOKIE"

Search helpers powering the admin rule-builder pickers. Both are GET and paginate their own way.

Query

ParamTypeDefaultNotes
searchstring""Free-text title / SKU. Empty returns first page.
pageint1
per_pageint20Capped at 100.
includestring""Comma-separated product IDs to hydrate (bypasses search/page).
categorystring""Comma-separated category slugs.
tagstring""Comma-separated tag slugs.
min_pricenumber
max_pricenumber

Response — 200 OK

{
"items": [
{ "id": 42, "name": "Widget — Blue", "price": "9.99", "sku": "WIDGET-BLUE", "images": [{"src": "..."}], "type": "variation", "stock_status": "instock" }
],
"total": 120,
"totalPages": 6
}

Parent-variation names are rendered Parent Product - Variation Label.


Query

ParamTypeRequiredNotes
taxonomystringproduct_cat, product_tag, or any pa_* global attribute taxonomy. Anything else returns invalid_taxonomy.
searchstringFree-text name/slug search (up to 30 results).
includestringComma-separated IDs (product_cat) or slugs (product_tag, pa_*) to resolve by identity.

Response — 200 OK

[ { "id": 10, "slug": "accessories", "name": "Accessories" } ]

Errors: invalid_taxonomy on unsupported taxonomy. The controller builds this WP_Error without an explicit status argument, so WordPress defaults the HTTP response to 500 — treat a 500 on this route as a 400-class validation error.


Read accumulated PerformanceMonitor entries (admin REST spans, engine evaluations).

Response — 200 OK: { "entries": [...], "count": 42 } — or { "entries": [] } if monitoring isn’t compiled in.


Clear the log.

Response — 200 OK: { "success": true }.


Per-admin-user first-run state backing the UX-ONBOARDING welcome panel, the sample-rule activation path, the first-success callout and the “skip setup” dismissal. The three HTTP verbs live under a single register_rest_route() call in OnboardingController::register_routes() (includes/API/OnboardingController.php:149).

Auth: the shared manage_woocommerce capability (via AbstractController::check_permission()) — same as every other admin route on this page.

Storage: per-user WordPress user-meta keyed by dino_discounts_onboarding_state (not the options table) — each admin user has an independent first-run experience. See OnboardingController::USER_META_KEY.

State shape (all fields optional; missing keys fall back to defaults):

KeyTypeNotes
welcome_skippedboolShopper explicitly clicked “skip setup”.
welcome_completedboolWelcome panel finished (either by publishing a rule or explicit done).
first_rule_callout_dismissedboolFirst-publish success callout was closed.
first_publish_tsintUnix seconds — set once when the user first publishes.
activated_tsintUnix seconds — set on first admin-page load (used for ms_since_activation analytics). Preserved across a DELETE.

Unknown keys in a POST body are dropped silently.

Return the current user’s onboarding state, merged over defaults so new keys added in later releases appear with sensible values.

Response — 200 OK

{
"welcome_skipped": false,
"welcome_completed": true,
"first_rule_callout_dismissed": false,
"first_publish_ts": 1713960000,
"activated_ts": 1713800000
}

Persist a partial update. The incoming state object is merged over the stored blob — callers can update a single flag without losing the rest.

Body

FieldTypeRequiredNotes
stateobjectPartial state. Keys outside the table above are ignored.

Example

Terminal window
curl -X POST \
-H "Content-Type: application/json" \
-H "X-WP-Nonce: <nonce>" \
--cookie "wordpress_logged_in_*=..." \
-d '{ "state": { "welcome_completed": true } }' \
"https://example.com/wp-json/dino-discounts/v1/onboarding"

Response — 200 OK: { "success": true, "state": { ... merged state ... } }.

Errors: invalid_data (HTTP 400) if state is missing or not an object.


Reset the current user’s state to defaults. activated_ts is preserved (so analytics stays meaningful across a reset); everything else goes back to defaults.

Response — 200 OK: { "success": true, "state": { ... defaults with preserved activated_ts ... } }.


WooCommerce Blocks cart/checkout themes that consume /wp-json/wc/store/v1/cart get richer Dino-Discounts metadata via the Store API ExtendSchema mechanism. The plugin does not register any custom Store API endpoints — it only attaches data to the existing cart response.

GET /wp-json/wc/store/v1/cart — WooCommerce Blocks standard endpoint.

Auth: standard Store API (cart token / Nonce via wc-store-api-nonce). No manage_woocommerce requirement — this is the shopper-facing cart.

The extension adds extensions["dino-discounts"] to the cart response:

{
"extensions": {
"dino-discounts": {
"discounts": [
{
"rule_id": "rule-abc",
"rule_name": "10% off Summer",
"discount_type": "tiered",
"percent": 10,
"amount": "350",
"is_virtual": true
}
],
"chip_config": {
"dino_dd_rule-abc": { "mode": "show_name", "label": "10% off Summer" }
}
}
}
}

discounts[] — one entry per active Dino virtual coupon on the cart.

FieldTypeNotes
rule_idstringMatches the engine rule id.
rule_namestringFalls back to cart_name or "Discount" when the engine hasn’t populated a label yet.
discount_typestringtiered, bulk, x_for_y, mix_match, etc.
percentnumber0 for fixed-amount discounts.
amountstringMinor units (cents/pence/fils), following the Store API convention. JPY=×1, GBP/USD=×100, BHD=×1000.
is_virtualbooleanAlways true for Dino auto-applied coupons.

chip_config — per-virtual-coupon display config keyed by the full internal coupon code. The code is the verbatim string CartCoupons::COUPON_PREFIX + rule_id — i.e. dino_dd_<rule_id> — not a user-controlled identifier. Example: a rule with id rule-abc produces the key dino_dd_rule-abc. Used by the registerCheckoutFilters.couponName JS filter to decide between hiding the chip (mode: "hidden") or rendering the human label (mode: "show_name"). See includes/Cart/CartCoupons.php for the canonical definition.

If WooCommerce Blocks / Store API is not available (Automattic\WooCommerce\StoreApi\StoreApi class missing, WC < 7.6), the extension silently no-ops — the cart response is unmodified.


All errors follow the standard WordPress REST shape — a WP_Error serialized as JSON:

{
"code": "too_many_rules",
"message": "This store has reached the 200-rule limit. Remove or archive an unused rule before saving.",
"data": { "status": 400 }
}

The HTTP status mirrors data.status. Error codes are stable within a major version and are referenced throughout this doc per endpoint.