{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://registry.bidit.com.au/spec/schemas/negotiation.json",
  "title": "Negotiation Extension for Agentic Commerce",
  "description": "Request and response shapes for au.com.bidit.shopping.negotiation, version 2026-09-23-draft. See https://registry.bidit.com.au/spec",
  "$defs": {
    "negotiability_answer": {
      "type": "object",
      "description": "The discovery answer for one merchant. Exactly two states: yes or not_yet.",
      "required": ["slug", "name", "domain", "negotiable", "answer"],
      "properties": {
        "slug": { "type": "string" },
        "name": { "type": "string" },
        "domain": { "type": "string" },
        "country": { "type": "string" },
        "currency": { "type": "string" },
        "negotiable": { "type": "boolean" },
        "answer": { "enum": ["yes", "not_yet"] },
        "message": { "type": "string", "description": "Plain-language answer an agent may relay verbatim." },
        "offer": {
          "type": ["object", "null"],
          "description": "Present when negotiable=true.",
          "properties": {
            "surfaces": { "type": "array", "items": { "enum": ["product", "cart"] } },
            "max_attempts_per_product": { "type": ["integer", "null"], "minimum": 1 },
            "counter_offers_enabled": { "type": ["boolean", "null"] },
            "how": { "type": "string" }
          }
        },
        "register_interest": {
          "type": ["object", "null"],
          "description": "Present when answer=not_yet and the index supports demand registration.",
          "properties": {
            "method": { "const": "POST" },
            "url": { "type": "string", "format": "uri" },
            "fields": { "type": "object" }
          }
        }
      }
    },
    "offer_request": {
      "type": "object",
      "required": ["merchant", "product_id", "offer_amount"],
      "properties": {
        "merchant": { "type": "string", "description": "Merchant slug from discovery." },
        "product_id": { "type": "string" },
        "offer_amount": { "type": "number", "exclusiveMinimum": 0, "description": "Per-unit price in the merchant's currency. Agents MUST offer the shopper's number, not a lower one." },
        "quantity": { "type": "integer", "minimum": 1, "default": 1 },
        "session_id": { "type": "string", "description": "Stable id for the shopper conversation; engines count attempts against it." },
        "ask": { "type": "string", "description": "Only after a price is agreed or countered: a non-price request in the shopper's words (delivery, warranty, bundled item)." }
      }
    },
    "offer_response": {
      "type": "object",
      "required": ["status"],
      "properties": {
        "status": { "enum": ["accepted", "countered", "declined", "blocked"] },
        "say": { "type": "string", "description": "The merchant-voiced line; agents SHOULD relay it rather than paraphrase into system language." },
        "agreed_price": { "type": "number" },
        "counter_offer_price": { "type": "number" },
        "attempts_remaining": { "type": "integer", "minimum": 0 },
        "negotiation_closed": { "type": "boolean", "description": "True on accepted and countered: no lower re-offers are possible." },
        "token": { "$ref": "#/$defs/token" },
        "included": {
          "type": "array",
          "description": "Verdicts for items in an 'ask'.",
          "items": {
            "type": "object",
            "required": ["item", "verdict"],
            "properties": {
              "item": { "type": "string" },
              "verdict": { "enum": ["yes", "no_but", "no"] },
              "price": { "type": "number", "description": "For no_but: the quoted price." },
              "yes_if_price": { "type": "number", "description": "For no_but: included free if the shopper raises the deal to this total." }
            }
          }
        }
      }
    },
    "token": {
      "type": ["object", "string"],
      "description": "Proof of an agreed price, redeemable at the merchant's own checkout. May be an opaque string; when an object, it carries its binding.",
      "properties": {
        "value": { "type": "string" },
        "merchant": { "type": "string" },
        "product_id": { "type": "string" },
        "variant_id": { "type": ["string", "integer"] },
        "quantity": { "type": "integer" },
        "agreed_price": { "type": "number" },
        "currency": { "type": "string" },
        "expires_at": { "type": "string", "format": "date-time" },
        "redemption": { "enum": ["ucp_discount", "discount_code"], "description": "How the checkout honours the price." },
        "discount_code": { "type": "string" }
      }
    },
    "register_interest_request": {
      "type": "object",
      "required": ["merchant_slug", "product", "intended_price", "email", "consent"],
      "properties": {
        "merchant_slug": { "type": "string" },
        "product": { "type": "string", "minLength": 2, "maxLength": 300 },
        "intended_price": { "type": "number", "exclusiveMinimum": 0 },
        "ticket_price": { "type": ["number", "null"], "exclusiveMinimum": 0 },
        "email": { "type": "string", "format": "email" },
        "consent": { "const": true, "description": "The shopper's explicit agreement to be contacted about this merchant. MUST be collected before the call; MUST be enforced at storage." },
        "source": { "enum": ["web", "skill", "assistant"] }
      }
    }
  }
}
