{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://schemas.revenexx.com/theme-events.schema.json",
  "title": "revenexx Theme Events",
  "description": "The theme event contract, version theme-events/1. One envelope per thing that happened in a storefront — a page shown, an item added, an order placed — emitted by the theme (@revenexx/cover) and read by whoever listens: the revenexx Tag Manager in the browser today, the Analytics Studio server-side later. Event names follow GA4 because the other vendors map onto it; B2B events GA4 does not know are added. The contract carries NO personal data: every object is closed, the visitor is described by two booleans, and email, name, phone, customer number, company name, address, IP, user/contact/organization ids are forbidden by construction. Transport (non-normative here): nuxtApp.callHook('revenexx:event', envelope) for Nuxt modules and window.dispatchEvent(new CustomEvent('revenexx:event', {detail: envelope})) for theme extensions.",
  "type": "object",
  "additionalProperties": false,
  "required": [
    "schema",
    "event",
    "event_id",
    "occurred_at",
    "market",
    "locale",
    "currency",
    "page",
    "customer"
  ],
  "properties": {
    "schema": {
      "const": "theme-events/1",
      "description": "The contract version this envelope claims. A listener reads only versions it supports."
    },
    "event": {
      "type": "string",
      "enum": [
        "page_view",
        "view_item_list",
        "search",
        "select_item",
        "view_item",
        "add_to_cart",
        "remove_from_cart",
        "view_cart",
        "begin_checkout",
        "add_shipping_info",
        "add_payment_info",
        "purchase",
        "request_quote",
        "add_to_orderlist",
        "punchout_transfer",
        "form_submit",
        "login",
        "sign_up"
      ],
      "description": "What happened. Vocabulary v1."
    },
    "event_id": {
      "type": "string",
      "minLength": 1,
      "maxLength": 128,
      "description": "Unique per event, a UUID v4 — except `purchase`, where it IS the transaction_id so a vendor and the emitter can drop a repeat of the same order."
    },
    "occurred_at": {
      "type": "string",
      "format": "date-time",
      "description": "When it happened, ISO 8601 UTC, in the browser's clock."
    },
    "market": {
      "type": "string",
      "pattern": "^[a-z0-9][a-z0-9_-]*$",
      "maxLength": 64,
      "description": "The market code the storefront runs in (`de`). Empty markets are not allowed: a storefront always runs in one."
    },
    "locale": {
      "type": "string",
      "pattern": "^[a-z]{2,3}(-[A-Za-z0-9]{2,8})*$",
      "description": "The UI locale, BCP 47 (`de-DE`)."
    },
    "currency": {
      "type": "string",
      "pattern": "^[A-Z]{3}$",
      "description": "ISO 4217 currency every amount in this envelope is in."
    },
    "page": {
      "$ref": "#/$defs/page"
    },
    "customer": {
      "$ref": "#/$defs/customer"
    },
    "ecommerce": {
      "$ref": "#/$defs/ecommerce"
    },
    "search": {
      "type": "object",
      "additionalProperties": false,
      "required": [
        "search_term",
        "results_count"
      ],
      "properties": {
        "search_term": {
          "type": "string",
          "maxLength": 256,
          "description": "What the visitor searched for. An emitter replaces a term that looks like an email address or a phone number by `[redacted]`."
        },
        "results_count": {
          "type": "integer",
          "minimum": 0
        }
      }
    },
    "form": {
      "type": "object",
      "additionalProperties": false,
      "required": [
        "form_code"
      ],
      "properties": {
        "form_code": {
          "type": "string",
          "minLength": 1,
          "maxLength": 128,
          "description": "The form's code (its slug). Never a field value — a form is where people type their address."
        },
        "outcome": {
          "type": "string",
          "enum": [
            "accepted",
            "rejected"
          ],
          "description": "Whether the platform accepted the submission."
        }
      }
    },
    "auth": {
      "type": "object",
      "additionalProperties": false,
      "required": [
        "method"
      ],
      "properties": {
        "method": {
          "type": "string",
          "enum": [
            "password",
            "magic_link",
            "otp",
            "sso",
            "punchout",
            "other"
          ]
        }
      }
    },
    "punchout": {
      "type": "object",
      "additionalProperties": false,
      "required": [
        "protocol"
      ],
      "properties": {
        "protocol": {
          "type": "string",
          "enum": [
            "oci",
            "cxml"
          ]
        }
      }
    }
  },
  "allOf": [
    {
      "if": {
        "properties": {
          "event": {
            "const": "page_view"
          }
        },
        "required": [
          "event"
        ]
      },
      "then": {
        "required": [
          "page"
        ]
      }
    },
    {
      "if": {
        "properties": {
          "event": {
            "const": "view_item_list"
          }
        },
        "required": [
          "event"
        ]
      },
      "then": {
        "required": [
          "ecommerce"
        ],
        "properties": {
          "ecommerce": {
            "required": [
              "list_id",
              "items"
            ],
            "properties": {
              "items": {
                "minItems": 1
              }
            }
          }
        }
      }
    },
    {
      "if": {
        "properties": {
          "event": {
            "const": "search"
          }
        },
        "required": [
          "event"
        ]
      },
      "then": {
        "required": [
          "search"
        ]
      }
    },
    {
      "if": {
        "properties": {
          "event": {
            "const": "select_item"
          }
        },
        "required": [
          "event"
        ]
      },
      "then": {
        "required": [
          "ecommerce"
        ],
        "properties": {
          "ecommerce": {
            "required": [
              "items"
            ],
            "properties": {
              "items": {
                "minItems": 1,
                "maxItems": 1
              }
            }
          }
        }
      }
    },
    {
      "if": {
        "properties": {
          "event": {
            "const": "view_item"
          }
        },
        "required": [
          "event"
        ]
      },
      "then": {
        "required": [
          "ecommerce"
        ],
        "properties": {
          "ecommerce": {
            "required": [
              "items"
            ],
            "properties": {
              "items": {
                "minItems": 1,
                "maxItems": 1
              }
            }
          }
        }
      }
    },
    {
      "if": {
        "properties": {
          "event": {
            "const": "add_to_cart"
          }
        },
        "required": [
          "event"
        ]
      },
      "then": {
        "required": [
          "ecommerce"
        ],
        "properties": {
          "ecommerce": {
            "required": [
              "items"
            ],
            "properties": {
              "items": {
                "minItems": 1
              }
            }
          }
        }
      }
    },
    {
      "if": {
        "properties": {
          "event": {
            "const": "remove_from_cart"
          }
        },
        "required": [
          "event"
        ]
      },
      "then": {
        "required": [
          "ecommerce"
        ],
        "properties": {
          "ecommerce": {
            "required": [
              "items"
            ],
            "properties": {
              "items": {
                "minItems": 1
              }
            }
          }
        }
      }
    },
    {
      "if": {
        "properties": {
          "event": {
            "const": "view_cart"
          }
        },
        "required": [
          "event"
        ]
      },
      "then": {
        "required": [
          "ecommerce"
        ],
        "properties": {
          "ecommerce": {
            "required": [
              "items"
            ],
            "properties": {
              "items": {
                "minItems": 0
              }
            }
          }
        }
      }
    },
    {
      "if": {
        "properties": {
          "event": {
            "const": "begin_checkout"
          }
        },
        "required": [
          "event"
        ]
      },
      "then": {
        "required": [
          "ecommerce"
        ],
        "properties": {
          "ecommerce": {
            "required": [
              "items"
            ],
            "properties": {
              "items": {
                "minItems": 1
              }
            }
          }
        }
      }
    },
    {
      "if": {
        "properties": {
          "event": {
            "const": "add_shipping_info"
          }
        },
        "required": [
          "event"
        ]
      },
      "then": {
        "required": [
          "ecommerce"
        ],
        "properties": {
          "ecommerce": {
            "required": [
              "shipping_tier",
              "items"
            ],
            "properties": {
              "items": {
                "minItems": 1
              }
            }
          }
        }
      }
    },
    {
      "if": {
        "properties": {
          "event": {
            "const": "add_payment_info"
          }
        },
        "required": [
          "event"
        ]
      },
      "then": {
        "required": [
          "ecommerce"
        ],
        "properties": {
          "ecommerce": {
            "required": [
              "payment_type",
              "items"
            ],
            "properties": {
              "items": {
                "minItems": 1
              }
            }
          }
        }
      }
    },
    {
      "if": {
        "properties": {
          "event": {
            "const": "purchase"
          }
        },
        "required": [
          "event"
        ]
      },
      "then": {
        "required": [
          "ecommerce"
        ],
        "properties": {
          "ecommerce": {
            "required": [
              "transaction_id",
              "value_net",
              "value_gross",
              "tax",
              "shipping",
              "items"
            ],
            "properties": {
              "items": {
                "minItems": 1
              },
              "value_net": {
                "type": "number",
                "minimum": 0,
                "maximum": 1000000000000.0
              },
              "value_gross": {
                "type": "number",
                "minimum": 0,
                "maximum": 1000000000000.0
              }
            }
          }
        }
      }
    },
    {
      "if": {
        "properties": {
          "event": {
            "const": "request_quote"
          }
        },
        "required": [
          "event"
        ]
      },
      "then": {
        "required": [
          "ecommerce"
        ],
        "properties": {
          "ecommerce": {
            "required": [
              "items"
            ],
            "properties": {
              "items": {
                "minItems": 1
              }
            }
          }
        }
      }
    },
    {
      "if": {
        "properties": {
          "event": {
            "const": "add_to_orderlist"
          }
        },
        "required": [
          "event"
        ]
      },
      "then": {
        "required": [
          "ecommerce"
        ],
        "properties": {
          "ecommerce": {
            "required": [
              "items"
            ],
            "properties": {
              "items": {
                "minItems": 1
              }
            }
          }
        }
      }
    },
    {
      "if": {
        "properties": {
          "event": {
            "const": "punchout_transfer"
          }
        },
        "required": [
          "event"
        ]
      },
      "then": {
        "required": [
          "ecommerce",
          "punchout"
        ],
        "properties": {
          "ecommerce": {
            "required": [
              "value_net",
              "value_gross",
              "items"
            ],
            "properties": {
              "items": {
                "minItems": 1
              }
            }
          }
        }
      }
    },
    {
      "if": {
        "properties": {
          "event": {
            "const": "form_submit"
          }
        },
        "required": [
          "event"
        ]
      },
      "then": {
        "required": [
          "form"
        ]
      }
    },
    {
      "if": {
        "properties": {
          "event": {
            "const": "login"
          }
        },
        "required": [
          "event"
        ]
      },
      "then": {
        "required": [
          "auth"
        ]
      }
    },
    {
      "if": {
        "properties": {
          "event": {
            "const": "sign_up"
          }
        },
        "required": [
          "event"
        ]
      },
      "then": {
        "required": [
          "auth"
        ]
      }
    }
  ],
  "$defs": {
    "item": {
      "type": "object",
      "additionalProperties": false,
      "required": [
        "sku",
        "name",
        "quantity"
      ],
      "description": "One product line. Closed: a field not listed here is refused, so nothing about the buyer can ride along on an item.",
      "properties": {
        "sku": {
          "type": "string",
          "minLength": 1,
          "maxLength": 128,
          "description": "The article number the merchant sells the product under. The primary key every vendor mapping uses as the product id."
        },
        "name": {
          "type": "string",
          "minLength": 1,
          "maxLength": 256,
          "description": "The product name as the storefront shows it."
        },
        "brand": {
          "type": "string",
          "maxLength": 128,
          "description": "The manufacturer or brand, where the product carries one."
        },
        "category_path": {
          "type": "array",
          "maxItems": 5,
          "items": {
            "type": "string",
            "minLength": 1,
            "maxLength": 128
          },
          "description": "The category trail from the root down, as labels a person reads. At most five levels — GA4 accepts five and etracker four, the mappings truncate from the end."
        },
        "variant": {
          "type": "string",
          "maxLength": 128,
          "description": "The chosen variant, as a label (\"rot / XL\")."
        },
        "price_net": {
          "type": [
            "number",
            "null"
          ],
          "minimum": 0,
          "maximum": 1000000000000.0,
          "description": "Unit price before tax. Null when the price is not known to the storefront (on request) — never 0 for unknown."
        },
        "price_gross": {
          "type": [
            "number",
            "null"
          ],
          "minimum": 0,
          "maximum": 1000000000000.0,
          "description": "Unit price including tax. Null when the price is not known to the storefront."
        },
        "quantity": {
          "type": "number",
          "exclusiveMinimum": 0,
          "maximum": 1000000000.0,
          "description": "How many units this line is about. A number, not an integer: cable is sold by the metre."
        },
        "list_position": {
          "type": "integer",
          "minimum": 1,
          "description": "1-based position in the list the item was shown in, where the event comes from a list."
        },
        "list_id": {
          "type": "string",
          "maxLength": 128,
          "description": "The list this item was shown in, when it differs from the event's own list."
        }
      }
    },
    "ecommerce": {
      "type": "object",
      "additionalProperties": false,
      "description": "The commerce part of an event. Which fields are required depends on the event, see the allOf below.",
      "properties": {
        "items": {
          "type": "array",
          "maxItems": 200,
          "items": {
            "$ref": "#/$defs/item"
          }
        },
        "value_net": {
          "type": [
            "number",
            "null"
          ],
          "minimum": 0,
          "maximum": 1000000000000.0,
          "description": "Sum of the event's lines (or the order) before tax."
        },
        "value_gross": {
          "type": [
            "number",
            "null"
          ],
          "minimum": 0,
          "maximum": 1000000000000.0,
          "description": "Sum of the event's lines (or the order) including tax."
        },
        "tax": {
          "type": "number",
          "minimum": 0,
          "maximum": 1000000000000.0,
          "description": "Tax amount of the order."
        },
        "shipping": {
          "type": "number",
          "minimum": 0,
          "maximum": 1000000000000.0,
          "description": "Shipping cost of the order, net."
        },
        "transaction_id": {
          "type": "string",
          "minLength": 1,
          "maxLength": 128,
          "description": "The order number the platform issued. Never the buyer's own purchase-order reference."
        },
        "coupon": {
          "type": "string",
          "maxLength": 64,
          "description": "The promotion code applied, when the buyer entered one."
        },
        "list_id": {
          "type": "string",
          "minLength": 1,
          "maxLength": 128,
          "description": "A stable id of the list shown: `category:<slug-path>`, `search`, `cross-sell`, `orderlist:<id>`."
        },
        "list_name": {
          "type": "string",
          "maxLength": 256,
          "description": "The list as a person reads it (the category label)."
        },
        "shipping_tier": {
          "type": "string",
          "minLength": 1,
          "maxLength": 64,
          "description": "The shipping method code chosen."
        },
        "payment_type": {
          "type": "string",
          "minLength": 1,
          "maxLength": 64,
          "description": "The payment method code chosen."
        }
      }
    },
    "page": {
      "type": "object",
      "additionalProperties": false,
      "required": [
        "path",
        "type"
      ],
      "properties": {
        "path": {
          "type": "string",
          "pattern": "^/[^?#]*$",
          "maxLength": 2048,
          "description": "The route path WITHOUT query string or fragment — a query can carry anything, including an address the buyer typed."
        },
        "type": {
          "type": "string",
          "enum": [
            "home",
            "category",
            "search",
            "product",
            "cart",
            "checkout",
            "confirmation",
            "account",
            "quote",
            "orderlist",
            "punchout",
            "login",
            "register",
            "content",
            "other"
          ],
          "description": "What kind of page this is. Vendors that segment by page kind read this."
        }
      }
    },
    "customer": {
      "type": "object",
      "additionalProperties": false,
      "required": [
        "authenticated",
        "b2b"
      ],
      "description": "What may be said about the visitor: whether they are signed in and whether they buy for a business. Nothing else — no email, name, phone, customer number, company name, contact or organization id. The object is closed so a forbidden field is a validation error, not a convention.",
      "properties": {
        "authenticated": {
          "type": "boolean"
        },
        "b2b": {
          "type": "boolean"
        }
      }
    }
  },
  "x-revenexx-forbidden-fields": [
    "email",
    "e_mail",
    "mail",
    "name_first",
    "first_name",
    "last_name",
    "full_name",
    "phone",
    "telephone",
    "mobile",
    "customer_number",
    "customer_id",
    "contact_id",
    "organization_id",
    "user_id",
    "company",
    "company_name",
    "street",
    "address",
    "postal_code",
    "zip",
    "city",
    "ip",
    "ip_address",
    "birthdate",
    "vat_id"
  ],
  "x-revenexx-vocabulary": [
    "page_view",
    "view_item_list",
    "search",
    "select_item",
    "view_item",
    "add_to_cart",
    "remove_from_cart",
    "view_cart",
    "begin_checkout",
    "add_shipping_info",
    "add_payment_info",
    "purchase",
    "request_quote",
    "add_to_orderlist",
    "punchout_transfer",
    "form_submit",
    "login",
    "sign_up"
  ]
}
