{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://snuepi.com/agent-policy.schema.json",
  "title": "Agent Policy v0.2",
  "description": "How a business tells AI agents how to reach it: whether agents may contact it, who must say what, which channel to use, booking rules and limits. Open format: anyone may publish, implement or extend it. Unknown fields are allowed so the format can grow.",
  "type": "object",
  "required": ["agent_policy", "business", "identification", "contact"],
  "properties": {
    "agent_policy": { "type": "string", "pattern": "^0\\.[0-9]+$", "description": "Format version, e.g. \"0.2\"." },
    "spec": { "type": "string" },
    "business": {
      "type": "object",
      "required": ["name"],
      "properties": {
        "name": { "type": "string", "minLength": 1 },
        "organization_number": { "type": ["string", "null"] },
        "country": { "type": ["string", "null"], "pattern": "^[A-Z]{2}$" },
        "page": { "type": ["string", "null"] }
      }
    },
    "identification": {
      "type": "object",
      "required": ["ai_must_identify"],
      "properties": {
        "ai_must_identify": { "type": "boolean", "description": "The agent says it is an AI, in calls and messages." },
        "agent_name_required": { "type": "boolean" },
        "on_behalf_of_required": { "type": "boolean" },
        "signed_requests": { "type": "string" },
        "accepted_proofs_of_principal": { "type": "array", "items": { "type": "string" } },
        "recording_of_staff": { "type": "string", "enum": ["not_allowed", "only_with_consent_asked_at_start", "allowed"] }
      }
    },
    "contact": {
      "type": "object",
      "required": ["agents_may_contact"],
      "properties": {
        "agents_may_contact": { "type": "string", "enum": ["yes", "channels_only", "no"] },
        "phone_calls_from_agents": { "type": "string", "enum": ["accepted", "accepted_in_hours", "not_accepted"] },
        "phone_hours": { "type": "object", "required": ["from", "to"], "properties": { "from": { "type": "string", "pattern": "^[0-2][0-9]:[0-5][0-9]$" }, "to": { "type": "string", "pattern": "^[0-2][0-9]:[0-5][0-9]$" } } },
        "quiet_hours": { "type": ["object", "null"], "required": ["from", "to"], "properties": { "from": { "type": "string", "pattern": "^[0-2][0-9]:[0-5][0-9]$" }, "to": { "type": "string", "pattern": "^[0-2][0-9]:[0-5][0-9]$" } } },
        "max_contacts_per_agent_per_day": { "type": "integer", "minimum": 0, "maximum": 1000 },
        "before_calling": {
          "type": "object",
          "description": "If the agent phones: register the call first and say the code it gets; staff check it on check_page.",
          "properties": { "register": { "type": "string" }, "say_the_code": { "type": "boolean" }, "check_page": { "type": "string" }, "verified_agents_only": { "type": "boolean" } }
        },
        "preferred": { "type": "string" },
        "mcp": { "type": "string" },
        "rest": { "type": "string" },
        "acp": { "type": "object", "properties": { "checkout": { "type": "string" }, "product_feed": { "type": "string" }, "discovery": { "type": "string" } } },
        "ucp": { "type": "string" },
        "email": { "type": "string" },
        "request_page": { "type": "string" }
      }
    },
    "booking": {
      "type": "object",
      "properties": {
        "instant_booking": { "type": "boolean" },
        "who_can_book_directly": { "type": "string", "enum": ["anyone", "customer_confirms_by_email", "verified_businesses_only", "nobody_owner_approves_all"] },
        "currency": { "type": "string", "pattern": "^[A-Z]{3}$" },
        "timezone": { "type": "string" },
        "working_days": { "type": "array", "description": "Days with any opening, 1 = Monday … 7 = Sunday.", "items": { "type": "integer", "minimum": 1, "maximum": 7 } },
        "working_hours": { "type": "object", "description": "Present only when every open day has the same single opening window. Read opening_hours.", "required": ["from", "to"], "properties": { "from": { "type": "string", "pattern": "^([01][0-9]|2[0-3]):[0-5][0-9]$" }, "to": { "type": "string", "pattern": "^([01][0-9]|2[0-3]):[0-5][0-9]$" } } },
        "opening_hours": {
          "type": "array",
          "description": "When bookings can take place, in the business's time zone, like schema.org OpeningHoursSpecification: one entry per window, a day can have several (lunch and dinner). A booking fits entirely inside one window. closes earlier than opens (18:00–02:00) means the window runs past midnight into the next day, as in schema.org; it belongs to the day it opens.",
          "items": {
            "type": "object",
            "required": ["days", "opens", "closes"],
            "properties": {
              "days": { "type": "array", "items": { "type": "integer", "minimum": 1, "maximum": 7 } },
              "opens": { "type": "string", "pattern": "^([01][0-9]|2[0-3]):[0-5][0-9]$" },
              "closes": { "type": "string", "pattern": "^([01][0-9]|2[0-3]):[0-5][0-9]$" }
            }
          }
        },
        "special_hours": {
          "type": "array",
          "description": "Dates that differ from opening_hours (holidays, closures, special hours). closed: true, or the windows in hours. Overrides opening_hours on those dates (windows that start on them; a window past midnight from the day before still ends as published). closes earlier than opens runs past midnight.",
          "items": {
            "type": "object",
            "required": ["from", "to", "closed"],
            "properties": {
              "from": { "type": "string", "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}$" },
              "to": { "type": "string", "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}$" },
              "closed": { "type": "boolean" },
              "hours": { "type": "array", "items": { "type": "object", "required": ["opens", "closes"], "properties": { "opens": { "type": "string", "pattern": "^([01][0-9]|2[0-3]):[0-5][0-9]$" }, "closes": { "type": "string", "pattern": "^([01][0-9]|2[0-3]):[0-5][0-9]$" } } } },
              "name": { "type": "string" }
            }
          }
        },
        "lead_time_hours": { "type": ["number", "null"], "minimum": 0 },
        "cancellation_deadline_hours": { "type": ["number", "null"], "minimum": 0 },
        "must_provide": { "type": "array", "items": { "type": "string" } },
        "one_booking_per_customer": { "type": "boolean" },
        "max_amount_without_owner_ex_vat": { "type": ["number", "null"], "minimum": 0 },
        "max_agent_bookings_per_day": { "type": ["integer", "null"], "minimum": 0 },
        "services_with_fixed_prices": { "type": "integer", "minimum": 0 },
        "deposit": { "type": ["object", "null"], "description": "percent or amount, paid to the business at booking.", "properties": { "percent": { "type": "number", "exclusiveMinimum": 0, "maximum": 100 }, "amount": { "type": "number", "exclusiveMinimum": 0 }, "paid_to": { "type": "string" }, "when": { "type": "string" }, "pay_at": { "type": "string" } } },
        "card_guarantee": { "type": ["object", "null"], "description": "A card the customer saves for the no-show fee; nothing is charged unless they do not come." },
        "no_show_fee_percent": { "type": ["number", "null"], "minimum": 0, "maximum": 100 },
        "resale": { "type": "string", "enum": ["not_allowed", "allowed"] }
      }
    },
    "outreach": {
      "type": "object",
      "properties": {
        "unsolicited_sales_or_marketing": { "type": "string", "enum": ["accepted", "not_accepted"] },
        "data_collection_for_training": { "type": "string", "enum": ["accepted", "not_accepted"] }
      }
    },
    "quotes": {
      "type": "object",
      "properties": {
        "price_source": { "type": "string" },
        "site_visit": { "type": "object", "properties": { "required": { "type": "string", "enum": ["never", "larger_jobs", "always"] }, "bookable": { "type": "boolean" }, "offering_id": { "type": "integer" }, "price_ex_vat": { "type": "number" }, "booking_page": { "type": ["string", "null"] } } }
      }
    },
    "negotiation": {
      "type": "object",
      "properties": { "allowed": { "type": "boolean" }, "max_negotiations_per_sender_per_day": { "type": "integer", "minimum": 0 }, "beyond_limit": { "type": "string" }, "mandate": { "type": ["string", "null"] } }
    },
    "rate_limit": { "type": "object", "properties": { "requests_per_minute": { "type": "integer", "minimum": 1 } } },
    "consumers": { "type": "object", "properties": { "withdrawal_period_days": { "type": "integer", "minimum": 0 }, "early_start_needs_explicit_consent": { "type": "boolean" } } },
    "verified_outcomes": {
      "type": "object",
      "description": "Counts from customers who actually booked or bought, after delivery. Not self-reported.",
      "properties": { "completed_bookings": { "type": "integer", "minimum": 0 }, "positive_feedback": { "type": "integer", "minimum": 0 }, "problems_reported": { "type": "integer", "minimum": 0 }, "source": { "type": "string" } }
    },
    "standards": { "type": "object" },
    "updated_at": { "type": "string", "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}" },
    "signature": { "type": "string", "description": "Optional JWS by the publisher over the policy." }
  }
}
