{
  "components": {
    "schemas": {
      "Address": {
        "additionalProperties": false,
        "description": "A postal address. One shape everywhere in v1, seven keys, so a partner writes exactly one address parser.",
        "properties": {
          "city": {
            "description": "City.",
            "type": [
              "string",
              "null"
            ]
          },
          "country": {
            "description": "Country.",
            "type": [
              "string",
              "null"
            ]
          },
          "line1": {
            "description": "Street address.",
            "type": [
              "string",
              "null"
            ]
          },
          "line2": {
            "description": "Unit, suite or second line.",
            "type": [
              "string",
              "null"
            ]
          },
          "name": {
            "description": "The addressee, when the record stores one of its own. `null` on a customer's address, where the customer's own `name` is the addressee \u2014 a copy here would invent one.",
            "type": [
              "string",
              "null"
            ]
          },
          "postal_code": {
            "description": "Postal or ZIP code. `GET /v1/market`'s `postal_label` gives it the right name for this winery's country.",
            "type": [
              "string",
              "null"
            ]
          },
          "state": {
            "description": "Province or state code. Which of the two it is comes from `GET /v1/market`'s `subdivision_label`.",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "city",
          "country",
          "line1",
          "line2",
          "name",
          "postal_code",
          "state"
        ],
        "type": "object"
      },
      "AllocationItem": {
        "additionalProperties": false,
        "description": "One line of a member's allocation.",
        "properties": {
          "id": {
            "description": "Stable identifier for this line. Present so an event consumer can tell the same row re-delivered from a second row that happens to match \u2014 reconcile on it rather than replacing the parent's whole collection.",
            "pattern": "^aitm_[0-9a-f]{32}$",
            "type": "string"
          },
          "is_addon": {
            "description": "Whether the member chose this as a paid extra on top of their box.",
            "type": "boolean"
          },
          "object": {
            "const": "allocation_item",
            "description": "Type discriminator. Always `\"allocation_item\"` on this resource.",
            "type": "string"
          },
          "product_id": {
            "description": "The product allocated.",
            "pattern": "^prod_[0-9a-f]{32}$",
            "type": [
              "string",
              "null"
            ]
          },
          "quantity": {
            "description": "How many bottles.",
            "type": "integer"
          },
          "unit_price": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/Money"
              },
              {
                "type": "null"
              }
            ],
            "description": "What the member pays per bottle \u2014 set only for add-ons. `null` on a base-box row means the bottle is INCLUDED IN THE TIER PRICE, not that the price is unknown. Sum these and you get the add-ons only; add the tier price for the box."
          }
        },
        "required": [
          "id",
          "is_addon",
          "object",
          "product_id",
          "quantity",
          "unit_price"
        ],
        "type": "object"
      },
      "Availability": {
        "description": "Bookable slots for one experience on one date, computed from the winery's schedule, blackouts, venue hours and live capacity.",
        "properties": {
          "date": {
            "description": "The date asked about.",
            "format": "date",
            "type": "string"
          },
          "experience_id": {
            "description": "The experience asked about.",
            "pattern": "^exp_[0-9a-f]{32}$",
            "type": "string"
          },
          "object": {
            "const": "availability",
            "description": "Type discriminator. Always `\"availability\"` on this resource.",
            "type": "string"
          },
          "scheduling_mode": {
            "description": "`scheduled` or `open`.",
            "enum": [
              "scheduled",
              "open"
            ],
            "type": "string"
          },
          "slots": {
            "description": "The bookable slots, earliest first, with past ones already dropped. **`null`, not `[]`, for an `open` experience** \u2014 one has no fixed slots at all, and an empty list would read as fully booked and stop you selling a day that is completely free.",
            "items": {
              "description": "One bookable slot.",
              "properties": {
                "remaining": {
                  "description": "Guests that can still be booked into it.",
                  "type": "integer"
                },
                "start_at": {
                  "description": "When the slot starts, with the winery's own offset.",
                  "format": "date-time",
                  "type": "string"
                },
                "status": {
                  "description": "How full it is \u2014 `open`, `limited` or `full`.",
                  "type": "string"
                }
              },
              "type": "object"
            },
            "type": [
              "array",
              "null"
            ]
          }
        },
        "type": "object"
      },
      "Booking": {
        "additionalProperties": false,
        "description": "One guest's reservation of a slot. The guest's own confirmation-link token is deliberately absent \u2014 it is a capability that cancels without authenticating, not an identifier.",
        "properties": {
          "checked_in_at": {
            "description": "When the guest arrived, if they have.",
            "format": "date-time",
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "description": "When it was made.",
            "format": "date-time",
            "type": "string"
          },
          "customer_id": {
            "description": "The linked customer, or `null` for a guest with no record. Creating a booking links an existing customer by email but never creates one.",
            "pattern": "^cus_[0-9a-f]{32}$",
            "type": [
              "string",
              "null"
            ]
          },
          "deposit": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Money"
              }
            ],
            "description": "What was actually taken. A real `0` for an experience that does not charge up front."
          },
          "experience_id": {
            "description": "What was booked.",
            "pattern": "^exp_[0-9a-f]{32}$",
            "type": [
              "string",
              "null"
            ]
          },
          "guest": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Guest"
              }
            ],
            "description": "The contact on the booking."
          },
          "id": {
            "description": "The booking's opaque id.",
            "pattern": "^book_[0-9a-f]{32}$",
            "type": "string"
          },
          "notes": {
            "description": "Whatever was recorded with the booking.",
            "type": [
              "string",
              "null"
            ]
          },
          "object": {
            "const": "booking",
            "description": "Type discriminator. Always `\"booking\"` on this resource.",
            "type": "string"
          },
          "paid": {
            "description": "Whether a deposit was captured and not since refunded.",
            "type": "boolean"
          },
          "party_size": {
            "description": "How many guests.",
            "type": "integer"
          },
          "scheduled_at": {
            "description": "When it is booked for, with the winery's own offset.",
            "format": "date-time",
            "type": "string"
          },
          "source": {
            "description": "Where it came from: `api` for one you made, `public` for the winery's own booking page, `staff` or `walkin`.",
            "type": "string"
          },
          "status": {
            "description": "`confirmed`, `waiting`, `arrived`, `cancelled`, `completed` or `no_show`. This vocabulary has grown before and may again \u2014 treat an unfamiliar value as informational rather than an error.",
            "type": "string"
          }
        },
        "required": [
          "checked_in_at",
          "created_at",
          "customer_id",
          "deposit",
          "experience_id",
          "guest",
          "id",
          "notes",
          "object",
          "paid",
          "party_size",
          "scheduled_at",
          "source",
          "status"
        ],
        "type": "object"
      },
      "ClubShipment": {
        "additionalProperties": false,
        "description": "A club release \u2014 the plan that generates one allocation per member. Not a parcel: the parcel is a `fulfillment`.",
        "properties": {
          "created_at": {
            "description": "When the release was created.",
            "format": "date-time",
            "type": "string"
          },
          "generated_at": {
            "description": "When its member allocations were generated. `null` until that has happened.",
            "format": "date-time",
            "type": [
              "string",
              "null"
            ]
          },
          "id": {
            "description": "The release's opaque id. Prefixed `ship_` for historical reasons; the resource is a club release, not a parcel.",
            "pattern": "^ship_[0-9a-f]{32}$",
            "type": "string"
          },
          "name": {
            "description": "What the winery calls this release.",
            "type": "string"
          },
          "object": {
            "const": "club_shipment",
            "description": "Type discriminator. Always `\"club_shipment\"` on this resource.",
            "type": "string"
          },
          "status": {
            "description": "Where the release is in its lifecycle.",
            "type": "string"
          }
        },
        "required": [
          "created_at",
          "generated_at",
          "id",
          "name",
          "object",
          "status"
        ],
        "type": "object"
      },
      "ClubTier": {
        "additionalProperties": false,
        "description": "A wine-club membership tier.",
        "properties": {
          "allow_public_signup": {
            "description": "Whether a shopper can join this tier themselves, rather than by invitation.",
            "type": "boolean"
          },
          "billing_period": {
            "description": "How often that price is charged \u2014 `monthly`, `quarterly`, and so on.",
            "type": "string"
          },
          "bottles_per_shipment": {
            "description": "How many bottles a member receives per release.",
            "type": [
              "integer",
              "null"
            ]
          },
          "created_at": {
            "description": "When the tier was created.",
            "format": "date-time",
            "type": "string"
          },
          "id": {
            "description": "The tier's opaque id.",
            "pattern": "^tier_[0-9a-f]{32}$",
            "type": "string"
          },
          "is_active": {
            "description": "Whether the winery still sells this tier. Inactive tiers are listed too \u2014 a member's `club_tier_id` has to resolve even when the tier is retired.",
            "type": "boolean"
          },
          "member_discount_percent": {
            "description": "The discount members on this tier get on other purchases.",
            "type": [
              "number",
              "null"
            ]
          },
          "name": {
            "description": "The tier's name.",
            "type": "string"
          },
          "object": {
            "const": "club_tier",
            "description": "Type discriminator. Always `\"club_tier\"` on this resource.",
            "type": "string"
          },
          "price": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Money"
              }
            ],
            "description": "What a member pays per billing period."
          },
          "sort_order": {
            "description": "Where the winery places it in its own ordering.",
            "type": "integer"
          }
        },
        "required": [
          "allow_public_signup",
          "billing_period",
          "bottles_per_shipment",
          "created_at",
          "id",
          "is_active",
          "member_discount_percent",
          "name",
          "object",
          "price",
          "sort_order"
        ],
        "type": "object"
      },
      "CodeName": {
        "additionalProperties": false,
        "description": "A code and its display name.",
        "properties": {
          "code": {
            "description": "The stable code to send back to us.",
            "type": "string"
          },
          "name": {
            "description": "How to show it to a person.",
            "type": "string"
          }
        },
        "required": [
          "code",
          "name"
        ],
        "type": "object"
      },
      "Communication": {
        "additionalProperties": false,
        "description": "One entry on a winery's customer timeline: a message the platform sent, or a call or note somebody recorded.\n\nWHAT A RELATED ORDER OR CAMPAIGN WAS is deliberately absent. Internally an entry may point at an order, a booking, an allocation, a shipment, a release, a campaign, a passport or a gift \u2014 and four of those eight are not API resources and have no opaque id at all. A field that resolved for some entries and was `null` for others, for a reason you could not see in the payload, is not something you could write code against. `kind` carries the distinction that matters.",
        "properties": {
          "channel": {
            "description": "How they were reached \u2014 `email`, `sms`, `call` or `note`. `email` and `sms` are messages SENT TO the customer; `call` and `note` are internal records of something a person did or observed.",
            "type": "string"
          },
          "id": {
            "description": "The entry's opaque id. The same id the receipt from `POST /customers/{id}/notes` returns, so a note you wrote can be found again on the timeline you read.",
            "pattern": "^comm_[0-9a-f]{32}$",
            "type": "string"
          },
          "kind": {
            "description": "What the message was: `booking_confirmation`, `order_update`, `campaign`, `release_invite`, `club_selection`, `waitlist_ready`, `manual` (logged by staff), `api_manual` (logged through this API), and others. DELIBERATELY NOT A CLOSED SET \u2014 the winery's messaging grows, and a consumer that switches on a value it knows should not break the day another is added.",
            "type": "string"
          },
          "logged_at": {
            "description": "When it happened. Nullable, unlike every other timestamp here \u2014 this column predates the API and an old imported row may not carry one. Such an entry sorts last.",
            "format": "date-time",
            "type": [
              "string",
              "null"
            ]
          },
          "object": {
            "const": "communication",
            "description": "Type discriminator. Always `\"communication\"` on this resource.",
            "type": "string"
          },
          "source": {
            "description": "Who put this entry on the timeline: `staff` (a person in the winery's admin), `api` (written through this API \u2014 yours, if you are the only integration), or `system` (an automated send). NOT a person's name: reading a customer's history does not hand you the winery's staff roster.",
            "type": "string"
          },
          "status": {
            "description": "`sent`, `failed`, or `logged` for an internal record. READ THIS ONE: `failed` means the message did NOT arrive, and a consumer treating every entry as \"contacted\" gets the opposite of the truth on exactly the rows where being wrong costs the most.",
            "type": "string"
          },
          "summary": {
            "description": "What the entry says. On an `email` or an `sms` this is the subject line; on a `call` or a `note` it is what somebody typed \u2014 which is why it is not called `subject`, since a phone call does not have one. It is the same text you send as `note` to `POST /customers/{id}/notes`.",
            "type": [
              "string",
              "null"
            ]
          },
          "to": {
            "description": "The address or number the message actually went to. May differ from the customer's current `email` or `phone` \u2014 that difference is the whole content of a bounce investigation. `null` on a `call` or a `note`, which went nowhere.",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "channel",
          "id",
          "kind",
          "logged_at",
          "object",
          "source",
          "status",
          "summary",
          "to"
        ],
        "type": "object"
      },
      "Customer": {
        "additionalProperties": false,
        "description": "A customer, including the whole club side of their record. Personal data \u2014 it sits behind `customers:read` for that reason.",
        "properties": {
          "address": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Address"
              }
            ],
            "description": "The customer's own address. Its `name` is always null; this object's `name` is the addressee."
          },
          "club_card_last4": {
            "description": "Last four digits of the card on file. Nothing else about the card crosses this boundary.",
            "type": [
              "string",
              "null"
            ]
          },
          "club_frequency": {
            "description": "How often they are billed, when it differs from their tier's default.",
            "type": [
              "string",
              "null"
            ]
          },
          "club_next_charge_date": {
            "description": "When the club will next charge them.",
            "format": "date",
            "type": [
              "string",
              "null"
            ]
          },
          "club_paused": {
            "description": "Whether the membership is paused. Read it WITH `club_status`: the \"Paused\" a staff member sees is the two together.",
            "type": "boolean"
          },
          "club_prepaid_until": {
            "description": "How far ahead the membership is paid.",
            "format": "date",
            "type": [
              "string",
              "null"
            ]
          },
          "club_resume_on": {
            "description": "When a paused membership resumes.",
            "format": "date",
            "type": [
              "string",
              "null"
            ]
          },
          "club_since": {
            "description": "The day they joined the club.",
            "format": "date",
            "type": [
              "string",
              "null"
            ]
          },
          "club_skip_next": {
            "description": "Whether they have asked to skip the next release.",
            "type": "boolean"
          },
          "club_status": {
            "description": "`none`, `pending`, `active` or `past_due`.",
            "enum": [
              "none",
              "pending",
              "active",
              "past_due"
            ],
            "type": "string"
          },
          "club_tier_id": {
            "description": "Their club tier. Resolve it against `GET /v1/club/tiers`.",
            "pattern": "^tier_[0-9a-f]{32}$",
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "description": "When the customer record was created.",
            "format": "date-time",
            "type": "string"
          },
          "date_of_birth": {
            "description": "Date of birth, where the winery has captured it.",
            "format": "date",
            "type": [
              "string",
              "null"
            ]
          },
          "email": {
            "description": "Email address.",
            "type": [
              "string",
              "null"
            ]
          },
          "email_opt_in": {
            "description": "Whether they opted in to marketing email.",
            "type": "boolean"
          },
          "email_opt_out": {
            "description": "Whether they have opted OUT of marketing email. A true here overrides everything else.",
            "type": "boolean"
          },
          "fulfillment_method": {
            "description": "How this customer takes their wine \u2014 `ship` or `pickup`.",
            "type": "string"
          },
          "has_member_account": {
            "description": "Whether they have a free login account on the winery's site. Independent of club membership \u2014 these two booleans are not a hierarchy.",
            "type": "boolean"
          },
          "id": {
            "description": "The customer's opaque id.",
            "pattern": "^cus_[0-9a-f]{32}$",
            "type": "string"
          },
          "is_club_member": {
            "description": "Whether they are in the paid wine club.",
            "type": "boolean"
          },
          "is_senior": {
            "description": "Whether the winery's senior discount applies.",
            "type": "boolean"
          },
          "is_tour_group": {
            "description": "Whether this record is a tour operator or group rather than a person.",
            "type": "boolean"
          },
          "loyalty_points": {
            "description": "Their current loyalty balance. Read-only: points are earned and spent by the winery's own systems.",
            "type": "integer"
          },
          "member_account_since": {
            "description": "The day that free account was created.",
            "format": "date",
            "type": [
              "string",
              "null"
            ]
          },
          "name": {
            "description": "The customer's name.",
            "type": "string"
          },
          "object": {
            "const": "customer",
            "description": "Type discriminator. Always `\"customer\"` on this resource.",
            "type": "string"
          },
          "phone": {
            "description": "Phone number.",
            "type": [
              "string",
              "null"
            ]
          },
          "sms_opt_in": {
            "description": "Whether they opted in to marketing SMS.",
            "type": "boolean"
          },
          "tags": {
            "description": "The winery's own CRM tag names, e.g. `[\"VIP\"]`. Always a list; a customer with none has `[]`. Writable, but SETTING IT REPLACES THE WHOLE SET \u2014 send what the customer should end up with, and read this first if you mean to add one. A name the winery has not created is refused: tags segment wine releases, so the vocabulary is theirs to define.",
            "items": {
              "maxLength": 40,
              "type": "string"
            },
            "type": "array"
          },
          "tax_exempt": {
            "description": "Whether they are exempt from sales tax.",
            "type": "boolean"
          },
          "tax_exempt_certificate": {
            "description": "Their exemption certificate reference.",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "address",
          "club_card_last4",
          "club_frequency",
          "club_next_charge_date",
          "club_paused",
          "club_prepaid_until",
          "club_resume_on",
          "club_since",
          "club_skip_next",
          "club_status",
          "club_tier_id",
          "created_at",
          "date_of_birth",
          "email",
          "email_opt_in",
          "email_opt_out",
          "fulfillment_method",
          "has_member_account",
          "id",
          "is_club_member",
          "is_senior",
          "is_tour_group",
          "loyalty_points",
          "member_account_since",
          "name",
          "object",
          "phone",
          "sms_opt_in",
          "tags",
          "tax_exempt",
          "tax_exempt_certificate"
        ],
        "type": "object"
      },
      "CustomerNote": {
        "additionalProperties": false,
        "description": "One entry written onto a winery's customer timeline \u2014 the record that somebody was contacted. The same shape `GET /customers/{id}/communications` returns, plus the original `note` field.",
        "properties": {
          "channel": {
            "description": "`note` or `call` \u2014 what you did. This API cannot record an email or an SMS: such a row asserts that a message was sent, and the winery's timeline is what they would consult when arguing about whether it was.",
            "type": "string"
          },
          "id": {
            "description": "The entry's opaque id. The same id the receipt from `POST /customers/{id}/notes` returns, so a note you wrote can be found again on the timeline you read.",
            "pattern": "^comm_[0-9a-f]{32}$",
            "type": "string"
          },
          "kind": {
            "description": "What the message was: `booking_confirmation`, `order_update`, `campaign`, `release_invite`, `club_selection`, `waitlist_ready`, `manual` (logged by staff), `api_manual` (logged through this API), and others. DELIBERATELY NOT A CLOSED SET \u2014 the winery's messaging grows, and a consumer that switches on a value it knows should not break the day another is added.",
            "type": "string"
          },
          "logged_at": {
            "description": "When it happened. Nullable, unlike every other timestamp here \u2014 this column predates the API and an old imported row may not carry one. Such an entry sorts last.",
            "format": "date-time",
            "type": [
              "string",
              "null"
            ]
          },
          "note": {
            "description": "What was recorded, as it appears on the winery's customer page. Truncated to 200 characters, the same limit the admin screen applies. IDENTICAL TO `summary` \u2014 kept because it was published in v1 and nothing published in v1 is withdrawn. Read `summary`.",
            "type": [
              "string",
              "null"
            ]
          },
          "object": {
            "const": "communication",
            "description": "Type discriminator. Always `\"communication\"` on this resource.",
            "type": "string"
          },
          "source": {
            "description": "Who put this entry on the timeline: `staff` (a person in the winery's admin), `api` (written through this API \u2014 yours, if you are the only integration), or `system` (an automated send). NOT a person's name: reading a customer's history does not hand you the winery's staff roster.",
            "type": "string"
          },
          "status": {
            "description": "`sent`, `failed`, or `logged` for an internal record. READ THIS ONE: `failed` means the message did NOT arrive, and a consumer treating every entry as \"contacted\" gets the opposite of the truth on exactly the rows where being wrong costs the most.",
            "type": "string"
          },
          "summary": {
            "description": "What the entry says. On an `email` or an `sms` this is the subject line; on a `call` or a `note` it is what somebody typed \u2014 which is why it is not called `subject`, since a phone call does not have one. It is the same text you send as `note` to `POST /customers/{id}/notes`.",
            "type": [
              "string",
              "null"
            ]
          },
          "to": {
            "description": "The address or number the message actually went to. May differ from the customer's current `email` or `phone` \u2014 that difference is the whole content of a bounce investigation. `null` on a `call` or a `note`, which went nowhere.",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "channel",
          "id",
          "kind",
          "logged_at",
          "note",
          "object",
          "source",
          "status",
          "summary",
          "to"
        ],
        "type": "object"
      },
      "Error": {
        "additionalProperties": false,
        "description": "Every error, at every status, arrives wrapped in this one key \u2014 so a caller can tell an error from a resource without consulting the status code twice, and a success payload can never be mistaken for one.",
        "properties": {
          "error": {
            "$ref": "#/components/schemas/ErrorBody"
          }
        },
        "required": [
          "error"
        ],
        "type": "object"
      },
      "ErrorBody": {
        "additionalProperties": false,
        "description": "`type` is what to branch on; `message` is written for a human reading a log. `param` and `detail` are present only when they carry something \u2014 never as nulls.",
        "properties": {
          "detail": {
            "description": "Extra structure for the few types that carry it."
          },
          "message": {
            "type": "string"
          },
          "param": {
            "description": "The query parameter or body field at fault, when one thing is.",
            "type": "string"
          },
          "request_id": {
            "description": "Also returned in the `X-Request-Id` header, on successes too. Quote it in a support request and we can find the one log line. We mint it; an inbound `X-Request-Id` is ignored.",
            "pattern": "^req_[0-9a-f]{16}$",
            "type": "string"
          },
          "type": {
            "description": "The stable machine-readable error type.",
            "enum": [
              "authentication_failed",
              "compliance_blocked",
              "conflict",
              "feature_not_enabled",
              "idempotency_mismatch",
              "internal",
              "invalid_request",
              "method_not_allowed",
              "not_found",
              "permission_denied",
              "rate_limited",
              "subscription_inactive"
            ],
            "type": "string"
          }
        },
        "required": [
          "type",
          "message",
          "request_id"
        ],
        "type": "object"
      },
      "Experience": {
        "additionalProperties": false,
        "description": "A bookable offering \u2014 a tasting flight or a tour. The raw schedule is deliberately not published: ask `/availability` instead, which runs the same code the winery's own booking page does.",
        "properties": {
          "cancellation_window_hours": {
            "description": "How long before the booking a cancellation still refunds.",
            "type": "integer"
          },
          "capacity_per_slot": {
            "description": "Maximum guests in one slot.",
            "type": "integer"
          },
          "created_at": {
            "description": "When it was created.",
            "format": "date-time",
            "type": "string"
          },
          "description": {
            "description": "Free text, as the winery wrote it.",
            "type": [
              "string",
              "null"
            ]
          },
          "duration_minutes": {
            "description": "How long it runs.",
            "type": "integer"
          },
          "id": {
            "description": "The experience's opaque id.",
            "pattern": "^exp_[0-9a-f]{32}$",
            "type": "string"
          },
          "is_active": {
            "description": "Inactive experiences stay listed here and cannot be booked.",
            "type": "boolean"
          },
          "kind": {
            "description": "`tasting` or `tour`.",
            "enum": [
              "tasting",
              "tour"
            ],
            "type": "string"
          },
          "name": {
            "description": "What the winery calls it.",
            "type": "string"
          },
          "object": {
            "const": "experience",
            "description": "Type discriminator. Always `\"experience\"` on this resource.",
            "type": "string"
          },
          "price_per_guest": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Money"
              }
            ],
            "description": "PER GUEST, not per booking \u2014 a party of four at a $25 tasting is $100. Named for what it is because a money object cannot say so."
          },
          "requires_prepayment": {
            "description": "Charges at booking time. `POST /bookings` refuses these \u2014 card details cannot be collected through this API.",
            "type": "boolean"
          },
          "scheduling_mode": {
            "description": "`scheduled` (fixed slots \u2014 ask `/availability` for them) or `open` (any time within the venue's hours).",
            "enum": [
              "scheduled",
              "open"
            ],
            "type": "string"
          },
          "walk_in_only": {
            "description": "Cannot be reserved in advance, by you or by the winery's own booking page.",
            "type": "boolean"
          }
        },
        "required": [
          "cancellation_window_hours",
          "capacity_per_slot",
          "created_at",
          "description",
          "duration_minutes",
          "id",
          "is_active",
          "kind",
          "name",
          "object",
          "price_per_guest",
          "requires_prepayment",
          "scheduling_mode",
          "walk_in_only"
        ],
        "type": "object"
      },
      "Fulfillment": {
        "additionalProperties": false,
        "description": "One parcel in the pack queue. Shop orders and club allocations both arrive here in the same shape \u2014 which is what lets a 3PL read one feed.",
        "properties": {
          "adult_signature_required": {
            "description": "Whether the carrier must take an adult signature.",
            "type": "boolean"
          },
          "allocation_id": {
            "description": "The club allocation behind it, for a `club` fulfillment.",
            "pattern": "^alloc_[0-9a-f]{32}$",
            "type": [
              "string",
              "null"
            ]
          },
          "bottles": {
            "description": "How many bottles are in it.",
            "type": [
              "integer",
              "null"
            ]
          },
          "carrier": {
            "description": "The carrier code, once shipped.",
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "description": "When it entered the queue.",
            "format": "date-time",
            "type": "string"
          },
          "customer_id": {
            "description": "Who it is for.",
            "pattern": "^cus_[0-9a-f]{32}$",
            "type": [
              "string",
              "null"
            ]
          },
          "email": {
            "description": "Contact email for the recipient.",
            "type": [
              "string",
              "null"
            ]
          },
          "fulfilled_at": {
            "description": "When it shipped or was collected.",
            "format": "date-time",
            "type": [
              "string",
              "null"
            ]
          },
          "fulfillment_method": {
            "description": "`ship` or `pickup`.",
            "type": "string"
          },
          "id": {
            "description": "The fulfillment's opaque id.",
            "pattern": "^ful_[0-9a-f]{32}$",
            "type": "string"
          },
          "kind": {
            "description": "`shop` for an online order, `club` for a club allocation.",
            "enum": [
              "shop",
              "club"
            ],
            "type": "string"
          },
          "object": {
            "const": "fulfillment",
            "description": "Type discriminator. Always `\"fulfillment\"` on this resource.",
            "type": "string"
          },
          "order_id": {
            "description": "The shop order behind it, for a `shop` fulfillment.",
            "pattern": "^ord_[0-9a-f]{32}$",
            "type": [
              "string",
              "null"
            ]
          },
          "phone": {
            "description": "Contact phone for the recipient.",
            "type": [
              "string",
              "null"
            ]
          },
          "ship_to": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Address"
              }
            ],
            "description": "Where the parcel is going. Its `name` is the addressee \u2014 there is no second name field to disagree with it."
          },
          "status": {
            "description": "`unfulfilled`, `shipped`, `picked_up` or `cancelled` today. Not validated as a closed set \u2014 the workflow has grown before.",
            "type": "string"
          },
          "tracking_number": {
            "description": "The tracking number, once shipped.",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "adult_signature_required",
          "allocation_id",
          "bottles",
          "carrier",
          "created_at",
          "customer_id",
          "email",
          "fulfilled_at",
          "fulfillment_method",
          "id",
          "kind",
          "object",
          "order_id",
          "phone",
          "ship_to",
          "status",
          "tracking_number"
        ],
        "type": "object"
      },
      "Guest": {
        "description": "The contact on a booking \u2014 a SNAPSHOT taken when it was made, which does not track the linked customer record afterwards.",
        "properties": {
          "email": {
            "description": "Contact email.",
            "type": [
              "string",
              "null"
            ]
          },
          "name": {
            "description": "Who the booking is for.",
            "type": "string"
          },
          "phone": {
            "description": "Contact phone.",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "type": "object"
      },
      "InventoryLevel": {
        "additionalProperties": false,
        "description": "What is on hand for one product, right now.",
        "properties": {
          "bottles_on_hand": {
            "description": "Bottles in stock. `null` for a bundle, which holds no stock of its own.",
            "type": [
              "integer",
              "null"
            ]
          },
          "in_stock": {
            "description": "Whether it can be sold right now \u2014 the signal that still works when `bottles_on_hand` is null.",
            "type": "boolean"
          },
          "is_bundle": {
            "description": "Whether it is a pack made of other products.",
            "type": "boolean"
          },
          "name": {
            "description": "The product's name, for the same reason.",
            "type": "string"
          },
          "object": {
            "const": "inventory_level",
            "description": "Type discriminator. Always `\"inventory_level\"` on this resource.",
            "type": "string"
          },
          "product_id": {
            "description": "The product this level is for. The only id on the object \u2014 stock is not a row you can fetch on its own.",
            "pattern": "^prod_[0-9a-f]{32}$",
            "type": "string"
          },
          "sku": {
            "description": "The winery's stock code, so a human can reconcile against a warehouse.",
            "type": [
              "string",
              "null"
            ]
          },
          "track_inventory": {
            "description": "Whether stock is counted for this product.",
            "type": "boolean"
          }
        },
        "required": [
          "bottles_on_hand",
          "in_stock",
          "is_bundle",
          "name",
          "object",
          "product_id",
          "sku",
          "track_inventory"
        ],
        "type": "object"
      },
      "InventoryMovement": {
        "additionalProperties": false,
        "description": "One entry in the stock ledger. Every count in `/v1/inventory` is the sum of these.",
        "properties": {
          "change_bottles": {
            "description": "How stock changed. Negative for anything leaving.",
            "type": "integer"
          },
          "cost": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/Money"
              },
              {
                "type": "null"
              }
            ],
            "description": "The unit cost carried by this movement. `null` means genuinely unknown \u2014 an old movement, or a product with no cost set \u2014 never zero."
          },
          "created_at": {
            "description": "When the movement was recorded.",
            "format": "date-time",
            "type": "string"
          },
          "id": {
            "description": "The movement's opaque id.",
            "pattern": "^invm_[0-9a-f]{32}$",
            "type": "string"
          },
          "note": {
            "description": "What the person who recorded it wrote.",
            "type": [
              "string",
              "null"
            ]
          },
          "object": {
            "const": "inventory_movement",
            "description": "Type discriminator. Always `\"inventory_movement\"` on this resource.",
            "type": "string"
          },
          "product_id": {
            "description": "The product that moved.",
            "pattern": "^prod_[0-9a-f]{32}$",
            "type": [
              "string",
              "null"
            ]
          },
          "reason": {
            "description": "Why it moved \u2014 a sale, a restock, an adjustment, wastage, an import, a bottling run.",
            "type": "string"
          }
        },
        "required": [
          "change_bottles",
          "cost",
          "created_at",
          "id",
          "note",
          "object",
          "product_id",
          "reason"
        ],
        "type": "object"
      },
      "Market": {
        "additionalProperties": false,
        "description": "The winery's market configuration \u2014 the one call that lets a partner write a single integration for wineries in different countries.",
        "properties": {
          "carriers": {
            "description": "The carriers valid for new shipments in this market.",
            "items": {
              "$ref": "#/components/schemas/CodeName"
            },
            "type": "array"
          },
          "checkout_age": {
            "description": "The age the checkout asks a shopper to confirm they are over. NOT a legal drinking age: it is one constant per country, and real drinking ages vary by subdivision. Do not enforce it as law.",
            "type": "integer"
          },
          "country": {
            "description": "The winery's country. Everything else on this object follows from it.",
            "type": "string"
          },
          "currency": {
            "description": "ISO 4217 code that every `money` object from this winery carries. Read from the winery, not from its country's default \u2014 the two can differ.",
            "type": "string"
          },
          "object": {
            "const": "market",
            "description": "Type discriminator. Always `\"market\"` on this resource.",
            "type": "string"
          },
          "postal_label": {
            "description": "What to call the postal field \u2014 `Postal code`, `ZIP code`.",
            "type": "string"
          },
          "ship_dob_required": {
            "description": "Whether a shipped order must carry the recipient's date of birth.",
            "type": "boolean"
          },
          "shippable_subdivisions": {
            "description": "The subset of `subdivisions` this winery may ship to TODAY, by code \u2014 permits, licences and rules already applied. It answers \"may this winery ship here at all\", not \"may it ship THIS order to THIS person\": a per-shipment bottle cap or an annual per-customer cap can still refuse at checkout, which is what `POST /v1/shipping/quote` is for.",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "shipping_enabled": {
            "description": "Whether this winery ships at all. `false` means pickup only, and every quote will refuse.",
            "type": "boolean"
          },
          "subdivision_label": {
            "description": "What to call a subdivision to this winery's customers \u2014 `Province`, `State`, and so on.",
            "type": "string"
          },
          "subdivisions": {
            "description": "Every subdivision in this winery's country, whether or not it may ship there.",
            "items": {
              "$ref": "#/components/schemas/CodeName"
            },
            "type": "array"
          },
          "timezone": {
            "description": "IANA zone name. Every timestamp in this API carries this zone's offset, and a naive timestamp you send is read in it.",
            "type": "string"
          }
        },
        "required": [
          "carriers",
          "checkout_age",
          "country",
          "currency",
          "object",
          "postal_label",
          "ship_dob_required",
          "shippable_subdivisions",
          "shipping_enabled",
          "subdivision_label",
          "subdivisions",
          "timezone"
        ],
        "type": "object"
      },
      "MemberAllocation": {
        "additionalProperties": false,
        "description": "One member's share of a club release.",
        "properties": {
          "carrier": {
            "description": "The carrier code, once shipped.",
            "type": [
              "string",
              "null"
            ]
          },
          "club_tier_id": {
            "description": "The tier they were on for this release.",
            "pattern": "^tier_[0-9a-f]{32}$",
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "description": "When the allocation was generated.",
            "format": "date-time",
            "type": "string"
          },
          "customer_id": {
            "description": "The member receiving it.",
            "pattern": "^cus_[0-9a-f]{32}$",
            "type": [
              "string",
              "null"
            ]
          },
          "fulfilled_at": {
            "description": "When it shipped or was picked up.",
            "format": "date-time",
            "type": [
              "string",
              "null"
            ]
          },
          "fulfillment_id": {
            "description": "The parcel this allocation is on.",
            "pattern": "^ful_[0-9a-f]{32}$",
            "type": [
              "string",
              "null"
            ]
          },
          "fulfillment_method": {
            "description": "`ship` or `pickup` for this allocation.",
            "type": "string"
          },
          "id": {
            "description": "The allocation's opaque id.",
            "pattern": "^alloc_[0-9a-f]{32}$",
            "type": "string"
          },
          "items": {
            "description": "What this member was allocated.",
            "items": {
              "$ref": "#/components/schemas/AllocationItem"
            },
            "type": "array"
          },
          "object": {
            "const": "member_allocation",
            "description": "Type discriminator. Always `\"member_allocation\"` on this resource.",
            "type": "string"
          },
          "ship_to": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Address"
              }
            ],
            "description": "Where it is going, when it ships."
          },
          "shipment_id": {
            "description": "The release this belongs to.",
            "pattern": "^ship_[0-9a-f]{32}$",
            "type": [
              "string",
              "null"
            ]
          },
          "status": {
            "description": "Where this allocation is in the pack-and-ship workflow.",
            "type": "string"
          },
          "tracking_number": {
            "description": "The tracking number, once shipped.",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "carrier",
          "club_tier_id",
          "created_at",
          "customer_id",
          "fulfilled_at",
          "fulfillment_id",
          "fulfillment_method",
          "id",
          "items",
          "object",
          "ship_to",
          "shipment_id",
          "status",
          "tracking_number"
        ],
        "type": "object"
      },
      "Money": {
        "additionalProperties": false,
        "description": "An amount of money. A `null` in place of this object means the amount is UNKNOWN \u2014 it never means zero. Where zero is the real answer, you get `amount_cents: 0`.",
        "properties": {
          "amount_cents": {
            "description": "The amount in the currency's minor unit. An integer, always \u2014 no floats cross this boundary.",
            "type": "integer"
          },
          "currency": {
            "description": "ISO 4217 code, e.g. `CAD` or `USD`. The winery's own charging currency, which `GET /v1/market` also reports.",
            "type": "string"
          }
        },
        "required": [
          "amount_cents",
          "currency"
        ],
        "type": "object"
      },
      "Order": {
        "additionalProperties": false,
        "description": "An order from the winery's online shop.",
        "properties": {
          "adult_signature_required": {
            "description": "Whether the carrier must take an adult signature on delivery.",
            "type": "boolean"
          },
          "carrier": {
            "description": "The carrier code, once shipped.",
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "description": "When the order was placed.",
            "format": "date-time",
            "type": "string"
          },
          "customer_id": {
            "description": "The customer record, when the order is attached to one.",
            "pattern": "^cus_[0-9a-f]{32}$",
            "type": [
              "string",
              "null"
            ]
          },
          "discount_percent": {
            "description": "The order-level discount rate applied.",
            "type": [
              "number",
              "null"
            ]
          },
          "email": {
            "description": "The buyer's email.",
            "type": [
              "string",
              "null"
            ]
          },
          "fulfilled_at": {
            "description": "When it shipped or was collected.",
            "format": "date-time",
            "type": [
              "string",
              "null"
            ]
          },
          "fulfillment_method": {
            "description": "`ship` or `pickup`.",
            "type": "string"
          },
          "has_physical_items": {
            "description": "Whether anything on the order actually needs to move \u2014 false for a gift-card-only order.",
            "type": "boolean"
          },
          "id": {
            "description": "The order's opaque id.",
            "pattern": "^ord_[0-9a-f]{32}$",
            "type": "string"
          },
          "items": {
            "description": "What was ordered.",
            "items": {
              "$ref": "#/components/schemas/OrderItem"
            },
            "type": "array"
          },
          "loyalty_points_redeemed": {
            "description": "Loyalty points spent on this order.",
            "type": "integer"
          },
          "object": {
            "const": "order",
            "description": "Type discriminator. Always `\"order\"` on this resource.",
            "type": "string"
          },
          "order_number": {
            "description": "The short human-facing number printed on the customer's receipt and emails. Use it when talking to a person; use `id` for everything else. It is not a capability key and cannot be used to fetch anything.",
            "type": "string"
          },
          "paid_at": {
            "description": "When payment completed.",
            "format": "date-time",
            "type": [
              "string",
              "null"
            ]
          },
          "payment": {
            "allOf": [
              {
                "$ref": "#/components/schemas/OrderPayment"
              }
            ],
            "description": "How it was paid."
          },
          "phone": {
            "description": "The buyer's phone.",
            "type": [
              "string",
              "null"
            ]
          },
          "refund_reference": {
            "description": "The processor's reference for a refund, if one was issued.",
            "type": [
              "string",
              "null"
            ]
          },
          "refunded_at": {
            "description": "When it was refunded.",
            "format": "date-time",
            "type": [
              "string",
              "null"
            ]
          },
          "ship_dob": {
            "description": "The recipient's date of birth, where the market requires it for a shipped order.",
            "format": "date",
            "type": [
              "string",
              "null"
            ]
          },
          "ship_to": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Address"
              }
            ],
            "description": "Where it is going."
          },
          "status": {
            "description": "Where the order is in its lifecycle. Not a fixed list \u2014 an order workflow gains states.",
            "type": "string"
          },
          "tax_breakdown": {
            "description": "The taxes charged, line by line, as they appear on the receipt.",
            "items": {
              "$ref": "#/components/schemas/TaxRow"
            },
            "type": "array"
          },
          "totals": {
            "allOf": [
              {
                "$ref": "#/components/schemas/OrderTotals"
              }
            ],
            "description": "The order's money."
          },
          "tracking_number": {
            "description": "The tracking number, once shipped.",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "adult_signature_required",
          "carrier",
          "created_at",
          "customer_id",
          "discount_percent",
          "email",
          "fulfilled_at",
          "fulfillment_method",
          "has_physical_items",
          "id",
          "items",
          "loyalty_points_redeemed",
          "object",
          "order_number",
          "paid_at",
          "payment",
          "phone",
          "refund_reference",
          "refunded_at",
          "ship_dob",
          "ship_to",
          "status",
          "tax_breakdown",
          "totals",
          "tracking_number"
        ],
        "type": "object"
      },
      "OrderItem": {
        "additionalProperties": false,
        "description": "One line of an online order.",
        "properties": {
          "id": {
            "description": "Stable identifier for this line. Present so an event consumer can tell the same row re-delivered from a second row that happens to match \u2014 reconcile on it rather than replacing the parent's whole collection.",
            "pattern": "^oitm_[0-9a-f]{32}$",
            "type": "string"
          },
          "is_gift_card": {
            "description": "Whether this line sold a gift card rather than a physical product.",
            "type": "boolean"
          },
          "list_price": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Money"
              }
            ],
            "description": "What it would have cost without discounts."
          },
          "name": {
            "description": "The product's name as it was at the time of the order \u2014 a later rename does not rewrite history.",
            "type": "string"
          },
          "object": {
            "const": "order_item",
            "description": "Type discriminator. Always `\"order_item\"` on this resource.",
            "type": "string"
          },
          "product_id": {
            "description": "The product ordered.",
            "pattern": "^prod_[0-9a-f]{32}$",
            "type": [
              "string",
              "null"
            ]
          },
          "quantity": {
            "description": "How many.",
            "type": "integer"
          },
          "unit_price": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Money"
              }
            ],
            "description": "What was actually charged per unit."
          }
        },
        "required": [
          "id",
          "is_gift_card",
          "list_price",
          "name",
          "object",
          "product_id",
          "quantity",
          "unit_price"
        ],
        "type": "object"
      },
      "OrderPayment": {
        "additionalProperties": false,
        "description": "How an online order was paid.",
        "properties": {
          "card_last4": {
            "description": "Last four digits of the card used.",
            "type": [
              "string",
              "null"
            ]
          },
          "processor": {
            "description": "Which processor took the money.",
            "type": [
              "string",
              "null"
            ]
          },
          "reference": {
            "description": "The processor's own reference for the charge \u2014 what to search for in their dashboard.",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "card_last4",
          "processor",
          "reference"
        ],
        "type": "object"
      },
      "OrderQuote": {
        "description": "What a cart will total, before you charge for it. The same numbers the order will carry \u2014 `total.amount_cents` is exactly what `POST /orders` expects in `external_payment.amount_cents`.",
        "properties": {
          "fulfillment_method": {
            "description": "`ship` or `pickup`, as asked.",
            "type": "string"
          },
          "object": {
            "const": "order_quote",
            "description": "Type discriminator. Always `\"order_quote\"` on this resource.",
            "type": "string"
          },
          "tax_breakdown": {
            "description": "One row per tax line, named as the winery's own invoice names it. `amount_cents` is bare here \u2014 the currency is established by `totals`.",
            "items": {
              "allOf": [
                {
                  "$ref": "#/components/schemas/TaxRow"
                }
              ],
              "description": "One tax line."
            },
            "type": "array"
          },
          "totals": {
            "description": "Every line of the arithmetic, so a partner can show a breakdown rather than one number.",
            "properties": {
              "discount": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/Money"
                  }
                ],
                "description": "What came off."
              },
              "shipping": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/Money"
                  }
                ],
                "description": "Carriage. Zero for a pickup."
              },
              "subtotal": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/Money"
                  }
                ],
                "description": "Before discount, shipping and tax."
              },
              "tax": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/Money"
                  }
                ],
                "description": "The sum of `tax_breakdown`."
              },
              "total": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/Money"
                  }
                ],
                "description": "What to charge."
              }
            },
            "type": "object"
          }
        },
        "type": "object"
      },
      "OrderTotals": {
        "additionalProperties": false,
        "description": "An order's money, already computed. Do not re-derive it.",
        "properties": {
          "discount": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Money"
              }
            ],
            "description": "Total discount applied."
          },
          "shipping": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Money"
              }
            ],
            "description": "Shipping charged."
          },
          "subtotal": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Money"
              }
            ],
            "description": "Before shipping, discount and tax."
          },
          "tax": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Money"
              }
            ],
            "description": "Total tax charged."
          },
          "total": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Money"
              }
            ],
            "description": "What the customer paid."
          }
        },
        "required": [
          "discount",
          "shipping",
          "subtotal",
          "tax",
          "total"
        ],
        "type": "object"
      },
      "Product": {
        "additionalProperties": false,
        "description": "A product in the winery's catalog. Bottles, bundles and non-wine items all arrive through this one shape.",
        "properties": {
          "abv": {
            "description": "Alcohol by volume, as a percentage.",
            "type": [
              "number",
              "null"
            ]
          },
          "bottles_on_hand": {
            "description": "Bottles in stock. `null` for a bundle \u2014 a pack holds no stock of its own and its availability is component-derived, so `0` would read as sold out. Use `in_stock` for buyability.",
            "type": [
              "integer",
              "null"
            ]
          },
          "category": {
            "description": "The winery's own category string. No fixed vocabulary.",
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "description": "When the product was created.",
            "format": "date-time",
            "type": "string"
          },
          "group_id": {
            "description": "The group this product belongs to. Resolve it against `GET /v1/product-groups`.",
            "pattern": "^pgrp_[0-9a-f]{32}$",
            "type": [
              "string",
              "null"
            ]
          },
          "id": {
            "description": "The product's opaque id.",
            "pattern": "^prod_[0-9a-f]{32}$",
            "type": "string"
          },
          "in_stock": {
            "description": "Whether this product can be sold right now. True for an untracked product, component-derived for a bundle, and `bottles_on_hand > 0` for an ordinary tracked bottle \u2014 the same predicate the winery's own shop gates on.",
            "type": "boolean"
          },
          "is_active": {
            "description": "Whether the winery still sells it.",
            "type": "boolean"
          },
          "is_bundle": {
            "description": "Whether this is a pack made of other products.",
            "type": "boolean"
          },
          "name": {
            "description": "The product's name, as the winery writes it.",
            "type": "string"
          },
          "object": {
            "const": "product",
            "description": "Type discriminator. Always `\"product\"` on this resource.",
            "type": "string"
          },
          "price": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Money"
              }
            ],
            "description": "The list price. For a bundle this is the live sum of its components, not the pack's own (zero) price column."
          },
          "region": {
            "description": "Region or appellation.",
            "type": [
              "string",
              "null"
            ]
          },
          "sale_ends_on": {
            "description": "The last day the sale price applies.",
            "format": "date",
            "type": [
              "string",
              "null"
            ]
          },
          "sale_price": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/Money"
              },
              {
                "type": "null"
              }
            ],
            "description": "The sale price, but only while a sale is actually live on the winery's clock. `null` when nothing is on sale \u2014 never the list price repeated."
          },
          "sku": {
            "description": "The winery's own stock code, if it uses one.",
            "type": [
              "string",
              "null"
            ]
          },
          "sold_online": {
            "description": "Whether the winery offers it in its online shop.",
            "type": "boolean"
          },
          "taxable": {
            "description": "Whether tax is charged on it.",
            "type": "boolean"
          },
          "track_inventory": {
            "description": "Whether stock is counted for this product at all.",
            "type": "boolean"
          },
          "updated_at": {
            "description": "When it was last edited. This is the column behind `?updated_since=`, which only this resource supports.",
            "format": "date-time",
            "type": "string"
          },
          "varietal": {
            "description": "Grape varietal.",
            "type": [
              "string",
              "null"
            ]
          },
          "vintage": {
            "description": "Vintage year.",
            "type": [
              "integer",
              "null"
            ]
          },
          "volume_ml": {
            "description": "Bottle volume in millilitres. `null` for a bundle: a pack is not a 750ml bottle, and there is no honest single answer.",
            "type": [
              "integer",
              "null"
            ]
          }
        },
        "required": [
          "abv",
          "bottles_on_hand",
          "category",
          "created_at",
          "group_id",
          "id",
          "in_stock",
          "is_active",
          "is_bundle",
          "name",
          "object",
          "price",
          "region",
          "sale_ends_on",
          "sale_price",
          "sku",
          "sold_online",
          "taxable",
          "track_inventory",
          "updated_at",
          "varietal",
          "vintage",
          "volume_ml"
        ],
        "type": "object"
      },
      "ProductGroup": {
        "additionalProperties": false,
        "description": "A category in the winery's own product structure \u2014 what a storefront builds navigation from.",
        "properties": {
          "created_at": {
            "description": "When the group was created.",
            "format": "date-time",
            "type": "string"
          },
          "id": {
            "description": "The group's opaque id \u2014 what a product's `group_id` refers to.",
            "pattern": "^pgrp_[0-9a-f]{32}$",
            "type": "string"
          },
          "name": {
            "description": "The group's name, as the winery writes it.",
            "type": "string"
          },
          "object": {
            "const": "product_group",
            "description": "Type discriminator. Always `\"product_group\"` on this resource.",
            "type": "string"
          },
          "sort_order": {
            "description": "Where the winery places it in its own ordering.",
            "type": "integer"
          }
        },
        "required": [
          "created_at",
          "id",
          "name",
          "object",
          "sort_order"
        ],
        "type": "object"
      },
      "Sale": {
        "additionalProperties": false,
        "description": "A register sale: a tasting-room transaction with its lines, tenders and refunds. Read-only in v1.",
        "properties": {
          "created_at": {
            "description": "When the sale was started.",
            "format": "date-time",
            "type": "string"
          },
          "customer_id": {
            "description": "The customer, when the sale was attached to one.",
            "pattern": "^cus_[0-9a-f]{32}$",
            "type": [
              "string",
              "null"
            ]
          },
          "discount_code": {
            "description": "The order-level discount code applied.",
            "type": [
              "string",
              "null"
            ]
          },
          "discount_percent": {
            "description": "The order-level discount rate applied.",
            "type": [
              "number",
              "null"
            ]
          },
          "id": {
            "description": "The sale's opaque id.",
            "pattern": "^sale_[0-9a-f]{32}$",
            "type": "string"
          },
          "is_tab": {
            "description": "Whether this was run as a tab rather than a single sale.",
            "type": "boolean"
          },
          "line_items": {
            "description": "What was sold.",
            "items": {
              "$ref": "#/components/schemas/SaleLineItem"
            },
            "type": "array"
          },
          "location_name": {
            "description": "The tasting room or till it was rung at, by name. There is no location id in v1, which is also why `?location_name=` filters by this string.",
            "type": [
              "string",
              "null"
            ]
          },
          "loyalty_points_redeemed": {
            "description": "Loyalty points spent on this sale.",
            "type": "integer"
          },
          "name": {
            "description": "What the tab was called, for a named tab.",
            "type": [
              "string",
              "null"
            ]
          },
          "object": {
            "const": "sale",
            "description": "Type discriminator. Always `\"sale\"` on this resource.",
            "type": "string"
          },
          "offline": {
            "description": "Whether the register rang it offline and synced it later. Worth knowing when reconciling: it reached the books after the moment it was paid.",
            "type": "boolean"
          },
          "paid_at": {
            "description": "When it was paid. `null` on an open tab.",
            "format": "date-time",
            "type": [
              "string",
              "null"
            ]
          },
          "payments": {
            "description": "How it was paid \u2014 one row per tender.",
            "items": {
              "$ref": "#/components/schemas/SalePayment"
            },
            "type": "array"
          },
          "refunds": {
            "description": "Refunds issued against it.",
            "items": {
              "$ref": "#/components/schemas/SaleRefund"
            },
            "type": "array"
          },
          "status": {
            "description": "Where the sale stands \u2014 an open tab and a completed sale differ here.",
            "type": "string"
          },
          "tax_breakdown": {
            "description": "The taxes charged, line by line.",
            "items": {
              "$ref": "#/components/schemas/TaxRow"
            },
            "type": "array"
          },
          "totals": {
            "allOf": [
              {
                "$ref": "#/components/schemas/SaleTotals"
              }
            ],
            "description": "The sale's money."
          }
        },
        "required": [
          "created_at",
          "customer_id",
          "discount_code",
          "discount_percent",
          "id",
          "is_tab",
          "line_items",
          "location_name",
          "loyalty_points_redeemed",
          "name",
          "object",
          "offline",
          "paid_at",
          "payments",
          "refunds",
          "status",
          "tax_breakdown",
          "totals"
        ],
        "type": "object"
      },
      "SaleLineItem": {
        "additionalProperties": false,
        "description": "One line of a register sale.",
        "properties": {
          "comped": {
            "description": "Whether the line was comped.",
            "type": "boolean"
          },
          "discount_percent": {
            "description": "The discount applied to this line.",
            "type": [
              "number",
              "null"
            ]
          },
          "discountable": {
            "description": "Whether discounts apply to it \u2014 deposits and fees usually do not.",
            "type": "boolean"
          },
          "id": {
            "description": "Stable identifier for this line. Present so an event consumer can tell the same row re-delivered from a second row that happens to match \u2014 reconcile on it rather than replacing the parent's whole collection.",
            "pattern": "^sitm_[0-9a-f]{32}$",
            "type": "string"
          },
          "is_auto_added": {
            "description": "Whether the register added this automatically, the way a bottle deposit rides along with a bottle.",
            "type": "boolean"
          },
          "is_gift_card": {
            "description": "Whether this line sold a gift card.",
            "type": "boolean"
          },
          "list_price": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Money"
              }
            ],
            "description": "What it would have cost undiscounted."
          },
          "name": {
            "description": "The product's name at the time of the sale.",
            "type": "string"
          },
          "object": {
            "const": "sale_line_item",
            "description": "Type discriminator. Always `\"sale_line_item\"` on this resource.",
            "type": "string"
          },
          "pour_label": {
            "description": "What the winery calls that pour \u2014 `5oz`, `Taste`.",
            "type": [
              "string",
              "null"
            ]
          },
          "pour_volume_ml": {
            "description": "For a by-the-glass line, the pour size in millilitres.",
            "type": [
              "integer",
              "null"
            ]
          },
          "product_id": {
            "description": "The product sold.",
            "pattern": "^prod_[0-9a-f]{32}$",
            "type": [
              "string",
              "null"
            ]
          },
          "quantity": {
            "description": "How many units, or how many pours.",
            "type": "integer"
          },
          "taxable": {
            "description": "Whether tax was charged on this line.",
            "type": "boolean"
          },
          "unit_price": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Money"
              }
            ],
            "description": "What was actually charged per unit."
          },
          "waived_quantity": {
            "description": "How many of the quantity were waived, as a tasting fee is when a guest buys a bottle.",
            "type": "integer"
          }
        },
        "required": [
          "comped",
          "discount_percent",
          "discountable",
          "id",
          "is_auto_added",
          "is_gift_card",
          "list_price",
          "name",
          "object",
          "pour_label",
          "pour_volume_ml",
          "product_id",
          "quantity",
          "taxable",
          "unit_price",
          "waived_quantity"
        ],
        "type": "object"
      },
      "SalePayment": {
        "additionalProperties": false,
        "description": "One payment against a register sale. A sale can have several \u2014 a split tender is two rows, not one.",
        "properties": {
          "amount": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Money"
              }
            ],
            "description": "The amount taken, excluding tip."
          },
          "card_last4": {
            "description": "Last four digits of the card.",
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "description": "When it was taken.",
            "format": "date-time",
            "type": "string"
          },
          "id": {
            "description": "Stable identifier for this line. Present so an event consumer can tell the same row re-delivered from a second row that happens to match \u2014 reconcile on it rather than replacing the parent's whole collection.",
            "pattern": "^pay_[0-9a-f]{32}$",
            "type": "string"
          },
          "method": {
            "description": "How it was paid \u2014 `cash`, `card`, `gift_card`, and so on.",
            "type": "string"
          },
          "object": {
            "const": "payment",
            "description": "Type discriminator. Always `\"payment\"` on this resource.",
            "type": "string"
          },
          "processor": {
            "description": "Which processor handled it.",
            "type": [
              "string",
              "null"
            ]
          },
          "processor_reference": {
            "description": "The processor's own reference.",
            "type": [
              "string",
              "null"
            ]
          },
          "status": {
            "description": "Where the payment stands.",
            "type": "string"
          },
          "tip": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/Money"
              },
              {
                "type": "null"
              }
            ],
            "description": "The tip taken with it."
          }
        },
        "required": [
          "amount",
          "card_last4",
          "created_at",
          "id",
          "method",
          "object",
          "processor",
          "processor_reference",
          "status",
          "tip"
        ],
        "type": "object"
      },
      "SaleRefund": {
        "additionalProperties": false,
        "description": "A refund against a register sale.",
        "properties": {
          "amount": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Money"
              }
            ],
            "description": "The amount refunded."
          },
          "created_at": {
            "description": "When the refund was issued.",
            "format": "date-time",
            "type": "string"
          },
          "id": {
            "description": "Stable identifier for this line. Present so an event consumer can tell the same row re-delivered from a second row that happens to match \u2014 reconcile on it rather than replacing the parent's whole collection.",
            "pattern": "^rfnd_[0-9a-f]{32}$",
            "type": "string"
          },
          "method": {
            "description": "How it was refunded. It matches the original tender: a card sale cannot be refunded as cash.",
            "type": "string"
          },
          "object": {
            "const": "refund",
            "description": "Type discriminator. Always `\"refund\"` on this resource.",
            "type": "string"
          },
          "reason": {
            "description": "Why, as the staff member recorded it.",
            "type": [
              "string",
              "null"
            ]
          },
          "restock": {
            "description": "Whether the bottles went back into stock.",
            "type": "boolean"
          }
        },
        "required": [
          "amount",
          "created_at",
          "id",
          "method",
          "object",
          "reason",
          "restock"
        ],
        "type": "object"
      },
      "SaleTotals": {
        "additionalProperties": false,
        "description": "A register sale's money, already computed.",
        "properties": {
          "balance": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/Money"
              },
              {
                "type": "null"
              }
            ],
            "description": "What is still outstanding \u2014 non-zero on an open tab."
          },
          "change": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/Money"
              },
              {
                "type": "null"
              }
            ],
            "description": "Change given, on a cash sale."
          },
          "comp": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Money"
              }
            ],
            "description": "Total comped."
          },
          "discount": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Money"
              }
            ],
            "description": "Total discount applied."
          },
          "subtotal": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Money"
              }
            ],
            "description": "Before discount, comp and tax."
          },
          "tax": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Money"
              }
            ],
            "description": "Total tax charged."
          },
          "tendered": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/Money"
              },
              {
                "type": "null"
              }
            ],
            "description": "Cash handed over, on a cash sale."
          },
          "tip": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Money"
              }
            ],
            "description": "Tip taken."
          },
          "total": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Money"
              }
            ],
            "description": "What was owed."
          }
        },
        "required": [
          "balance",
          "change",
          "comp",
          "discount",
          "subtotal",
          "tax",
          "tendered",
          "tip",
          "total"
        ],
        "type": "object"
      },
      "ShippingQuote": {
        "additionalProperties": false,
        "description": "The answer to \"may I ship this here, and what does it cost?\" \u2014 asked before you take the money.",
        "properties": {
          "adult_signature_required": {
            "description": "Whether an adult signature will be required on delivery.",
            "type": "boolean"
          },
          "note": {
            "description": "A note the winery attaches to shipments to this destination. Empty string when there is none, never null.",
            "type": "string"
          },
          "object": {
            "const": "shipping_quote",
            "description": "Type discriminator. Always `\"shipping_quote\"` on this resource.",
            "type": "string"
          },
          "reasons": {
            "description": "Why not, when `shippable` is false \u2014 the winery's own compliance wording, which you may show a shopper as written. Always an array, empty when the destination is permitted.",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "shippable": {
            "description": "Whether this winery may ship this to this destination today. `false` is a successful answer to the question, not an error \u2014 check it before rendering `shipping`.",
            "type": "boolean"
          },
          "shipping": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/Money"
              },
              {
                "type": "null"
              }
            ],
            "description": "What the shipment would cost, with the winery's free-shipping threshold already applied. `null` whenever `shippable` is false: there is no honest price for a shipment that will not happen, and `0` would read as free."
          }
        },
        "required": [
          "adult_signature_required",
          "note",
          "object",
          "reasons",
          "shippable",
          "shipping"
        ],
        "type": "object"
      },
      "TaxRow": {
        "additionalProperties": false,
        "description": "One line of a tax breakdown, as it appears on the receipt.",
        "properties": {
          "amount_cents": {
            "description": "The tax charged, in cents. Bare cents rather than a money object: the parent's `totals.total.currency` already establishes the currency for the document.",
            "type": [
              "integer",
              "null"
            ]
          },
          "code": {
            "description": "The tax's short code, e.g. `GST`.",
            "type": [
              "string",
              "null"
            ]
          },
          "label": {
            "description": "How the winery names this tax on a receipt.",
            "type": [
              "string",
              "null"
            ]
          },
          "rate": {
            "description": "The rate applied, as a percentage.",
            "type": [
              "number",
              "null"
            ]
          }
        },
        "required": [
          "amount_cents",
          "code",
          "label",
          "rate"
        ],
        "type": "object"
      }
    },
    "securitySchemes": {
      "apiKey": {
        "description": "An API key the winery issues in Admin \u2192 Setup \u2192 API keys, sent as `Authorization: Bearer sk_live_\u2026`. Keys are shown once, at creation; we store only a hash, so a lost key is replaced rather than recovered. A key is bound to one winery and carries a fixed set of scopes.",
        "scheme": "bearer",
        "type": "http"
      }
    }
  },
  "info": {
    "description": "v1 is **read-only** and **frozen**: a field that ships here is never removed or retyped, and new ones are only ever added. Writes, webhooks and an OAuth flow for multi-winery apps are later phases.\n\n**Authentication.** One header: `Authorization: Bearer sk_live_\u2026`. The key identifies the winery \u2014 there is no account id in any path \u2014 and carries the scopes its admin granted it. There are no cookies and no CSRF token.\n\n**Money** is always `{\"amount_cents\": 4200, \"currency\": \"CAD\"}`, never a float and never a bare integer. `null` means unknown; it never means zero.\n\n**Timestamps** are ISO-8601 in the winery's own time zone, with an offset. `GET /market` tells you which zone that is.\n\n**Ids** are opaque, prefixed strings \u2014 `prod_\u2026`, `cus_\u2026`, `ord_\u2026`. Store them whole; the prefix is how a mis-routed id is refused at the boundary instead of resolving to the wrong object. Integer primary keys never cross this boundary.\n\n**Lists** are cursor-paginated. Send back `next_cursor` verbatim; there is no page number, and an unrecognised query parameter is a 400 rather than being ignored.\n\n**Errors** all share one shape and always carry a real status code and a `request_id`.\n\n**Rate limits** are per key. A throttled request answers 429 with `Retry-After`.\n",
    "summary": "Read a winery's catalog, customers, club, orders, sales, stock and pack queue.",
    "title": "SipsPOS Public API",
    "version": "1"
  },
  "openapi": "3.1.0",
  "paths": {
    "/bookings": {
      "get": {
        "description": "Filter by `status`, `experience_id` and `created_since`. There is deliberately no `date` filter: this list is paged by when each booking was MADE, and a filter on when it is SCHEDULED would page in an order that looks broken.\n\nRequires the `bookings:read` scope. A key without it answers 403 `permission_denied`, naming the scope and this endpoint.",
        "operationId": "list_bookings",
        "parameters": [
          {
            "description": "How many rows to return, 1\u2013200 (default 50). Out-of-range and non-integer values are refused rather than clamped: quietly reading `limit=1000` as 200 teaches a caller its request worked.",
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "default": 50,
              "maximum": 200,
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "description": "The `next_cursor` from the previous page, sent back verbatim. Cursors are signed and bound to the filters and ordering they were minted under \u2014 replaying one against a differently filtered list is refused, not silently resumed part-way down a wider list. There is no page number or offset.",
            "in": "query",
            "name": "cursor",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Only bookings MADE at or after this instant \u2014 not ones scheduled after it.",
            "in": "query",
            "name": "created_since",
            "required": false,
            "schema": {
              "format": "date-time",
              "type": "string"
            }
          },
          {
            "description": "Only bookings for this experience. An id that resolves to nothing is a 404, not an empty page.",
            "in": "query",
            "name": "experience_id",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Only bookings in this status. NOT validated against a fixed list \u2014 this vocabulary has grown before, and refusing an unfamiliar value would turn a future status into a wall.",
            "in": "query",
            "name": "status",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "additionalProperties": false,
                  "properties": {
                    "data": {
                      "items": {
                        "$ref": "#/components/schemas/Booking"
                      },
                      "type": "array"
                    },
                    "has_more": {
                      "description": "Whether another page follows.",
                      "type": "boolean"
                    },
                    "next_cursor": {
                      "description": "Send this back as `?cursor=` for the next page. `null` on the last page \u2014 always present, never absent, so a generated type has no optional field here.",
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "object": {
                      "const": "list",
                      "type": "string"
                    }
                  },
                  "required": [
                    "object",
                    "data",
                    "has_more",
                    "next_cursor"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "Success."
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`invalid_request` \u2014 That request wasn't valid. Check the parameters and try again."
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`authentication_failed` \u2014 Missing or invalid API key. Send it as 'Authorization: Bearer sk_live_\u2026'."
          },
          "402": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`subscription_inactive` \u2014 This winery's SipsPOS subscription isn't active, so its API access is paused."
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`permission_denied` \u2014 This API key doesn't have permission to do that. `feature_not_enabled` \u2014 That feature isn't enabled on this winery's plan."
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`rate_limited` \u2014 You've sent too many requests. Wait for the window in Retry-After, then try again."
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`internal` \u2014 An unexpected error occurred. Our team has been notified; if it persists, include this request's id when you contact support."
          }
        },
        "summary": "List bookings",
        "tags": [
          "Bookings"
        ]
      },
      "post": {
        "description": "Creates a confirmed booking, `201`, recorded with `source: \"api\"` so the winery can tell it from one their own staff took.\n\n**It cannot take payment.** An experience with `requires_prepayment` is refused: card details cannot be collected through this API, and writing the booking unpaid would put a guest on the sheet whom the refund window then reasons about incorrectly. Send those guests to the winery's own booking page.\n\n**It cannot book a time the winery does not offer.** For a `scheduled` experience the time must be one `/availability` lists. The winery's own staff may book off-schedule; a partner may not.\n\nA `guest.email` matching an existing customer links to them. An unknown one does NOT create a customer \u2014 use `POST /customers` if you mean to.\n\nRequires an `Idempotency-Key` header.\n\nRequires the `bookings:write` scope. A key without it answers 403 `permission_denied`, naming the scope and this endpoint.",
        "operationId": "create_booking",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "additionalProperties": false,
                "properties": {
                  "experience_id": {
                    "description": "What to book. Required. An experience that requires prepayment, is inactive, or is walk-in only is refused with the reason.",
                    "pattern": "^exp_[0-9a-f]{32}$",
                    "type": "string"
                  },
                  "guest": {
                    "additionalProperties": false,
                    "description": "Who the booking is for. `name` is required. An `email` matching an existing customer links to them; an unknown one does not create a customer record.",
                    "properties": {
                      "email": {
                        "format": "email",
                        "type": "string"
                      },
                      "name": {
                        "type": "string"
                      },
                      "phone": {
                        "type": "string"
                      }
                    },
                    "required": [
                      "name"
                    ],
                    "type": "object"
                  },
                  "notes": {
                    "description": "Anything the winery should know \u2014 access needs, an occasion, a preferred table.",
                    "type": "string"
                  },
                  "party_size": {
                    "description": "How many guests. Required, and checked against the slot's remaining capacity.",
                    "minimum": 1,
                    "type": "integer"
                  },
                  "scheduled_at": {
                    "description": "`YYYY-MM-DDTHH:MM`, IN THE WINERY'S OWN TIME ZONE \u2014 read it from `GET /market`. No offset: a booking is an appointment at a place, and the place has one clock. For a `scheduled` experience this must be a slot `/availability` lists. Required.",
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}(:\\d{2})?$",
                    "type": "string"
                  }
                },
                "required": [
                  "experience_id",
                  "scheduled_at",
                  "party_size",
                  "guest"
                ],
                "type": "object"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Booking"
                }
              }
            },
            "description": "Success."
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`invalid_request` \u2014 That request wasn't valid. Check the parameters and try again."
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`authentication_failed` \u2014 Missing or invalid API key. Send it as 'Authorization: Bearer sk_live_\u2026'."
          },
          "402": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`subscription_inactive` \u2014 This winery's SipsPOS subscription isn't active, so its API access is paused."
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`permission_denied` \u2014 This API key doesn't have permission to do that. `feature_not_enabled` \u2014 That feature isn't enabled on this winery's plan."
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`rate_limited` \u2014 You've sent too many requests. Wait for the window in Retry-After, then try again."
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`internal` \u2014 An unexpected error occurred. Our team has been notified; if it persists, include this request's id when you contact support."
          }
        },
        "summary": "Take a booking",
        "tags": [
          "Bookings"
        ]
      }
    },
    "/bookings/{booking_id}/cancel": {
      "post": {
        "description": "Cancels and refunds per the experience's own cancellation window. Idempotent: cancelling an already-cancelled booking returns it unchanged.\n\n**Takes no body.** There is no override \u2014 the winery's staff can refund outside their window because they know something the system does not, and an API parameter that moves money on that basis is one your bug could pull. Sending any body is a 400 rather than a silently ignored field.\n\nRequires an `Idempotency-Key` header.\n\nRequires the `bookings:write` scope. A key without it answers 403 `permission_denied`, naming the scope and this endpoint.",
        "operationId": "cancel_booking_route",
        "parameters": [
          {
            "description": "The resource's opaque id, `book_` followed by 32 hex characters. An id belonging to another winery answers 404, never 403 \u2014 \"not found\" and \"not yours\" are deliberately the same answer.",
            "in": "path",
            "name": "booking_id",
            "required": true,
            "schema": {
              "pattern": "^book_[0-9a-f]{32}$",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Booking"
                }
              }
            },
            "description": "Success."
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`authentication_failed` \u2014 Missing or invalid API key. Send it as 'Authorization: Bearer sk_live_\u2026'."
          },
          "402": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`subscription_inactive` \u2014 This winery's SipsPOS subscription isn't active, so its API access is paused."
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`permission_denied` \u2014 This API key doesn't have permission to do that. `feature_not_enabled` \u2014 That feature isn't enabled on this winery's plan."
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`not_found` \u2014 We couldn't find that."
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`rate_limited` \u2014 You've sent too many requests. Wait for the window in Retry-After, then try again."
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`internal` \u2014 An unexpected error occurred. Our team has been notified; if it persists, include this request's id when you contact support."
          }
        },
        "summary": "Cancel a booking",
        "tags": [
          "Bookings"
        ]
      }
    },
    "/club/members": {
      "get": {
        "description": "Customers who are in the club. The same rows `/v1/customers` returns, narrowed to the three membership states \u2014 a customer who is not a member is not a club member of any kind, so this endpoint has no `none`.\n\nRequires the `customers:read` scope. A key without it answers 403 `permission_denied`, naming the scope and this endpoint.",
        "operationId": "list_club_members",
        "parameters": [
          {
            "description": "How many rows to return, 1\u2013200 (default 50). Out-of-range and non-integer values are refused rather than clamped: quietly reading `limit=1000` as 200 teaches a caller its request worked.",
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "default": 50,
              "maximum": 200,
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "description": "The `next_cursor` from the previous page, sent back verbatim. Cursors are signed and bound to the filters and ordering they were minted under \u2014 replaying one against a differently filtered list is refused, not silently resumed part-way down a wider list. There is no page number or offset.",
            "in": "query",
            "name": "cursor",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Only members in this state. `none` is refused here and the refusal points at `/v1/customers?club_status=none`.",
            "in": "query",
            "name": "club_status",
            "required": false,
            "schema": {
              "enum": [
                "pending",
                "active",
                "past_due"
              ],
              "type": "string"
            }
          },
          {
            "description": "Only members on this tier. A tier id that belongs to another winery, or to nothing, is a 404 \u2014 an empty page would read as \"this tier has no members\", which is a different fact.",
            "in": "query",
            "name": "tier_id",
            "required": false,
            "schema": {
              "pattern": "^tier_[0-9a-f]{32}$",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "additionalProperties": false,
                  "properties": {
                    "data": {
                      "items": {
                        "$ref": "#/components/schemas/Customer"
                      },
                      "type": "array"
                    },
                    "has_more": {
                      "description": "Whether another page follows.",
                      "type": "boolean"
                    },
                    "next_cursor": {
                      "description": "Send this back as `?cursor=` for the next page. `null` on the last page \u2014 always present, never absent, so a generated type has no optional field here.",
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "object": {
                      "const": "list",
                      "type": "string"
                    }
                  },
                  "required": [
                    "object",
                    "data",
                    "has_more",
                    "next_cursor"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "Success."
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`invalid_request` \u2014 That request wasn't valid. Check the parameters and try again."
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`authentication_failed` \u2014 Missing or invalid API key. Send it as 'Authorization: Bearer sk_live_\u2026'."
          },
          "402": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`subscription_inactive` \u2014 This winery's SipsPOS subscription isn't active, so its API access is paused."
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`permission_denied` \u2014 This API key doesn't have permission to do that. `feature_not_enabled` \u2014 That feature isn't enabled on this winery's plan."
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`not_found` \u2014 We couldn't find that."
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`rate_limited` \u2014 You've sent too many requests. Wait for the window in Retry-After, then try again."
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`internal` \u2014 An unexpected error occurred. Our team has been notified; if it persists, include this request's id when you contact support."
          }
        },
        "summary": "List club members",
        "tags": [
          "Club"
        ]
      }
    },
    "/club/shipments": {
      "get": {
        "description": "The winery's club releases (shipments), newest first. Each one's allocations \u2014 who gets what \u2014 hang off it.\n\nRequires the `customers:read` scope. A key without it answers 403 `permission_denied`, naming the scope and this endpoint.",
        "operationId": "list_club_shipments",
        "parameters": [
          {
            "description": "How many rows to return, 1\u2013200 (default 50). Out-of-range and non-integer values are refused rather than clamped: quietly reading `limit=1000` as 200 teaches a caller its request worked.",
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "default": 50,
              "maximum": 200,
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "description": "The `next_cursor` from the previous page, sent back verbatim. Cursors are signed and bound to the filters and ordering they were minted under \u2014 replaying one against a differently filtered list is refused, not silently resumed part-way down a wider list. There is no page number or offset.",
            "in": "query",
            "name": "cursor",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "additionalProperties": false,
                  "properties": {
                    "data": {
                      "items": {
                        "$ref": "#/components/schemas/ClubShipment"
                      },
                      "type": "array"
                    },
                    "has_more": {
                      "description": "Whether another page follows.",
                      "type": "boolean"
                    },
                    "next_cursor": {
                      "description": "Send this back as `?cursor=` for the next page. `null` on the last page \u2014 always present, never absent, so a generated type has no optional field here.",
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "object": {
                      "const": "list",
                      "type": "string"
                    }
                  },
                  "required": [
                    "object",
                    "data",
                    "has_more",
                    "next_cursor"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "Success."
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`authentication_failed` \u2014 Missing or invalid API key. Send it as 'Authorization: Bearer sk_live_\u2026'."
          },
          "402": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`subscription_inactive` \u2014 This winery's SipsPOS subscription isn't active, so its API access is paused."
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`permission_denied` \u2014 This API key doesn't have permission to do that. `feature_not_enabled` \u2014 That feature isn't enabled on this winery's plan."
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`rate_limited` \u2014 You've sent too many requests. Wait for the window in Retry-After, then try again."
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`internal` \u2014 An unexpected error occurred. Our team has been notified; if it persists, include this request's id when you contact support."
          }
        },
        "summary": "List club releases",
        "tags": [
          "Club"
        ]
      }
    },
    "/club/shipments/{shipment_id}": {
      "get": {
        "description": "Requires the `customers:read` scope. A key without it answers 403 `permission_denied`, naming the scope and this endpoint.",
        "operationId": "get_club_shipment",
        "parameters": [
          {
            "description": "The resource's opaque id, `ship_` followed by 32 hex characters. An id belonging to another winery answers 404, never 403 \u2014 \"not found\" and \"not yours\" are deliberately the same answer.",
            "in": "path",
            "name": "shipment_id",
            "required": true,
            "schema": {
              "pattern": "^ship_[0-9a-f]{32}$",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ClubShipment"
                }
              }
            },
            "description": "Success."
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`authentication_failed` \u2014 Missing or invalid API key. Send it as 'Authorization: Bearer sk_live_\u2026'."
          },
          "402": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`subscription_inactive` \u2014 This winery's SipsPOS subscription isn't active, so its API access is paused."
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`permission_denied` \u2014 This API key doesn't have permission to do that. `feature_not_enabled` \u2014 That feature isn't enabled on this winery's plan."
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`not_found` \u2014 We couldn't find that."
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`rate_limited` \u2014 You've sent too many requests. Wait for the window in Retry-After, then try again."
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`internal` \u2014 An unexpected error occurred. Our team has been notified; if it persists, include this request's id when you contact support."
          }
        },
        "summary": "Read one club release",
        "tags": [
          "Club"
        ]
      }
    },
    "/club/shipments/{shipment_id}/allocations": {
      "get": {
        "description": "One row per member in this release: what they were allocated, what it was priced at, and which parcel it is on.\n\nRequires the `customers:read` scope. A key without it answers 403 `permission_denied`, naming the scope and this endpoint.",
        "operationId": "list_shipment_allocations",
        "parameters": [
          {
            "description": "The resource's opaque id, `ship_` followed by 32 hex characters. An id belonging to another winery answers 404, never 403 \u2014 \"not found\" and \"not yours\" are deliberately the same answer.",
            "in": "path",
            "name": "shipment_id",
            "required": true,
            "schema": {
              "pattern": "^ship_[0-9a-f]{32}$",
              "type": "string"
            }
          },
          {
            "description": "How many rows to return, 1\u2013200 (default 50). Out-of-range and non-integer values are refused rather than clamped: quietly reading `limit=1000` as 200 teaches a caller its request worked.",
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "default": 50,
              "maximum": 200,
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "description": "The `next_cursor` from the previous page, sent back verbatim. Cursors are signed and bound to the filters and ordering they were minted under \u2014 replaying one against a differently filtered list is refused, not silently resumed part-way down a wider list. There is no page number or offset.",
            "in": "query",
            "name": "cursor",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "additionalProperties": false,
                  "properties": {
                    "data": {
                      "items": {
                        "$ref": "#/components/schemas/MemberAllocation"
                      },
                      "type": "array"
                    },
                    "has_more": {
                      "description": "Whether another page follows.",
                      "type": "boolean"
                    },
                    "next_cursor": {
                      "description": "Send this back as `?cursor=` for the next page. `null` on the last page \u2014 always present, never absent, so a generated type has no optional field here.",
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "object": {
                      "const": "list",
                      "type": "string"
                    }
                  },
                  "required": [
                    "object",
                    "data",
                    "has_more",
                    "next_cursor"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "Success."
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`authentication_failed` \u2014 Missing or invalid API key. Send it as 'Authorization: Bearer sk_live_\u2026'."
          },
          "402": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`subscription_inactive` \u2014 This winery's SipsPOS subscription isn't active, so its API access is paused."
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`permission_denied` \u2014 This API key doesn't have permission to do that. `feature_not_enabled` \u2014 That feature isn't enabled on this winery's plan."
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`not_found` \u2014 We couldn't find that."
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`rate_limited` \u2014 You've sent too many requests. Wait for the window in Retry-After, then try again."
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`internal` \u2014 An unexpected error occurred. Our team has been notified; if it persists, include this request's id when you contact support."
          }
        },
        "summary": "List a release's member allocations",
        "tags": [
          "Club"
        ]
      }
    },
    "/club/tiers": {
      "get": {
        "description": "The winery's membership tiers, including inactive ones \u2014 a member's `club_tier_id` has to resolve even when the tier they are on is no longer sold.\n\nRequires the `customers:read` scope. A key without it answers 403 `permission_denied`, naming the scope and this endpoint.",
        "operationId": "list_club_tiers",
        "parameters": [
          {
            "description": "How many rows to return, 1\u2013200 (default 50). Out-of-range and non-integer values are refused rather than clamped: quietly reading `limit=1000` as 200 teaches a caller its request worked.",
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "default": 50,
              "maximum": 200,
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "description": "The `next_cursor` from the previous page, sent back verbatim. Cursors are signed and bound to the filters and ordering they were minted under \u2014 replaying one against a differently filtered list is refused, not silently resumed part-way down a wider list. There is no page number or offset.",
            "in": "query",
            "name": "cursor",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "additionalProperties": false,
                  "properties": {
                    "data": {
                      "items": {
                        "$ref": "#/components/schemas/ClubTier"
                      },
                      "type": "array"
                    },
                    "has_more": {
                      "description": "Whether another page follows.",
                      "type": "boolean"
                    },
                    "next_cursor": {
                      "description": "Send this back as `?cursor=` for the next page. `null` on the last page \u2014 always present, never absent, so a generated type has no optional field here.",
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "object": {
                      "const": "list",
                      "type": "string"
                    }
                  },
                  "required": [
                    "object",
                    "data",
                    "has_more",
                    "next_cursor"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "Success."
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`authentication_failed` \u2014 Missing or invalid API key. Send it as 'Authorization: Bearer sk_live_\u2026'."
          },
          "402": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`subscription_inactive` \u2014 This winery's SipsPOS subscription isn't active, so its API access is paused."
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`permission_denied` \u2014 This API key doesn't have permission to do that. `feature_not_enabled` \u2014 That feature isn't enabled on this winery's plan."
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`rate_limited` \u2014 You've sent too many requests. Wait for the window in Retry-After, then try again."
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`internal` \u2014 An unexpected error occurred. Our team has been notified; if it persists, include this request's id when you contact support."
          }
        },
        "summary": "List club tiers",
        "tags": [
          "Club"
        ]
      }
    },
    "/customers": {
      "get": {
        "description": "Requires the `customers:read` scope. A key without it answers 403 `permission_denied`, naming the scope and this endpoint.",
        "operationId": "list_customers",
        "parameters": [
          {
            "description": "How many rows to return, 1\u2013200 (default 50). Out-of-range and non-integer values are refused rather than clamped: quietly reading `limit=1000` as 200 teaches a caller its request worked.",
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "default": 50,
              "maximum": 200,
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "description": "The `next_cursor` from the previous page, sent back verbatim. Cursors are signed and bound to the filters and ordering they were minted under \u2014 replaying one against a differently filtered list is refused, not silently resumed part-way down a wider list. There is no page number or offset.",
            "in": "query",
            "name": "cursor",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Only customers in this club state. `none` is everyone who is not a club member, which is why this endpoint takes four values and `/club/members` takes three.",
            "in": "query",
            "name": "club_status",
            "required": false,
            "schema": {
              "enum": [
                "none",
                "pending",
                "active",
                "past_due"
              ],
              "type": "string"
            }
          },
          {
            "description": "The customer with this email address, compared case-insensitively for equality. Not a search: `%` and `_` are literal characters here, not wildcards.",
            "in": "query",
            "name": "email",
            "required": false,
            "schema": {
              "format": "email",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "additionalProperties": false,
                  "properties": {
                    "data": {
                      "items": {
                        "$ref": "#/components/schemas/Customer"
                      },
                      "type": "array"
                    },
                    "has_more": {
                      "description": "Whether another page follows.",
                      "type": "boolean"
                    },
                    "next_cursor": {
                      "description": "Send this back as `?cursor=` for the next page. `null` on the last page \u2014 always present, never absent, so a generated type has no optional field here.",
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "object": {
                      "const": "list",
                      "type": "string"
                    }
                  },
                  "required": [
                    "object",
                    "data",
                    "has_more",
                    "next_cursor"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "Success."
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`invalid_request` \u2014 That request wasn't valid. Check the parameters and try again."
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`authentication_failed` \u2014 Missing or invalid API key. Send it as 'Authorization: Bearer sk_live_\u2026'."
          },
          "402": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`subscription_inactive` \u2014 This winery's SipsPOS subscription isn't active, so its API access is paused."
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`permission_denied` \u2014 This API key doesn't have permission to do that. `feature_not_enabled` \u2014 That feature isn't enabled on this winery's plan."
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`rate_limited` \u2014 You've sent too many requests. Wait for the window in Retry-After, then try again."
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`internal` \u2014 An unexpected error occurred. Our team has been notified; if it persists, include this request's id when you contact support."
          }
        },
        "summary": "List customers",
        "tags": [
          "Customers"
        ]
      },
      "post": {
        "description": "Creates a customer and returns it, `201`. There is deliberately no dedupe against an existing email \u2014 `Customer.email` is not unique in this schema, so a create that quietly returned an existing record would sometimes edit somebody else's customer. Search with `GET /customers?email=` first if you want upsert semantics; only you can decide what a match means.\n\nRequires an `Idempotency-Key` header, which is what makes a retry after a timeout safe rather than a duplicate.\n\nRequires the `customers:write` scope. A key without it answers 403 `permission_denied`, naming the scope and this endpoint.",
        "operationId": "create_customer",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "additionalProperties": false,
                "properties": {
                  "address": {
                    "description": "Nested postal address, in the same shape `GET /customers` returns. Omitted keys are left alone; an explicit `null` clears one.",
                    "oneOf": [
                      {
                        "additionalProperties": false,
                        "properties": {
                          "city": {
                            "description": "City or town.",
                            "maxLength": 120,
                            "type": "string"
                          },
                          "country": {
                            "description": "ISO 3166-1 alpha-2 country code.",
                            "maxLength": 60,
                            "type": "string"
                          },
                          "line1": {
                            "description": "Street address.",
                            "maxLength": 200,
                            "type": "string"
                          },
                          "line2": {
                            "description": "Unit, suite or second address line.",
                            "maxLength": 200,
                            "type": "string"
                          },
                          "postal_code": {
                            "description": "Postal or ZIP code.",
                            "maxLength": 20,
                            "type": "string"
                          },
                          "state": {
                            "description": "Province or state. Use the subdivision code `GET /market` publishes for this winery's country.",
                            "maxLength": 80,
                            "type": "string"
                          }
                        },
                        "type": "object"
                      },
                      {
                        "type": "null"
                      }
                    ]
                  },
                  "date_of_birth": {
                    "description": "`YYYY-MM-DD`. Used for the legal-age check on shipped orders.",
                    "oneOf": [
                      {
                        "format": "date",
                        "type": "string"
                      },
                      {
                        "type": "null"
                      }
                    ]
                  },
                  "email": {
                    "description": "Not unique in this schema and not verified \u2014 `GET /customers?email=` may legitimately return several rows.",
                    "oneOf": [
                      {
                        "maxLength": 150,
                        "type": "string"
                      },
                      {
                        "type": "null"
                      }
                    ]
                  },
                  "email_opt_in": {
                    "description": "Explicit consent to marketing email.",
                    "oneOf": [
                      {
                        "type": "boolean"
                      },
                      {
                        "type": "null"
                      }
                    ]
                  },
                  "email_opt_out": {
                    "description": "Suppresses marketing email. Transactional mail still sends.",
                    "oneOf": [
                      {
                        "type": "boolean"
                      },
                      {
                        "type": "null"
                      }
                    ]
                  },
                  "fulfillment_method": {
                    "description": "Whether this customer's orders default to pickup or shipping.",
                    "oneOf": [
                      {
                        "enum": [
                          "pickup",
                          "ship"
                        ],
                        "type": "string"
                      },
                      {
                        "type": "null"
                      }
                    ]
                  },
                  "is_senior": {
                    "description": "Eligible for this winery's senior discount.",
                    "oneOf": [
                      {
                        "type": "boolean"
                      },
                      {
                        "type": "null"
                      }
                    ]
                  },
                  "is_tour_group": {
                    "description": "This 'customer' is a tour group rather than a person.",
                    "oneOf": [
                      {
                        "type": "boolean"
                      },
                      {
                        "type": "null"
                      }
                    ]
                  },
                  "name": {
                    "description": "The customer's full name. Required.",
                    "maxLength": 150,
                    "type": "string"
                  },
                  "phone": {
                    "description": "Contact phone.",
                    "oneOf": [
                      {
                        "maxLength": 40,
                        "type": "string"
                      },
                      {
                        "type": "null"
                      }
                    ]
                  },
                  "sms_opt_in": {
                    "description": "Explicit consent to marketing SMS.",
                    "oneOf": [
                      {
                        "type": "boolean"
                      },
                      {
                        "type": "null"
                      }
                    ]
                  },
                  "tags": {
                    "description": "The winery's own CRM tag names, e.g. `[\"VIP\"]`. SETTING THIS REPLACES THE WHOLE SET \u2014 send the tags the customer should end up with, not the ones to add, and `GET` first if you mean to add one. `[]` removes them all. A name this winery has not created is refused: tags segment wine releases, so the vocabulary is the winery's to define.",
                    "oneOf": [
                      {
                        "items": {
                          "maxLength": 40,
                          "type": "string"
                        },
                        "type": "array"
                      },
                      {
                        "type": "null"
                      }
                    ]
                  },
                  "tax_exempt": {
                    "description": "Exempt from sales tax. Most jurisdictions want a certificate recorded alongside.",
                    "oneOf": [
                      {
                        "type": "boolean"
                      },
                      {
                        "type": "null"
                      }
                    ]
                  },
                  "tax_exempt_certificate": {
                    "description": "The exemption certificate number on file.",
                    "oneOf": [
                      {
                        "maxLength": 64,
                        "type": "string"
                      },
                      {
                        "type": "null"
                      }
                    ]
                  }
                },
                "required": [
                  "name"
                ],
                "type": "object"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Customer"
                }
              }
            },
            "description": "Success."
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`invalid_request` \u2014 That request wasn't valid. Check the parameters and try again."
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`authentication_failed` \u2014 Missing or invalid API key. Send it as 'Authorization: Bearer sk_live_\u2026'."
          },
          "402": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`subscription_inactive` \u2014 This winery's SipsPOS subscription isn't active, so its API access is paused."
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`permission_denied` \u2014 This API key doesn't have permission to do that. `feature_not_enabled` \u2014 That feature isn't enabled on this winery's plan."
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`rate_limited` \u2014 You've sent too many requests. Wait for the window in Retry-After, then try again."
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`internal` \u2014 An unexpected error occurred. Our team has been notified; if it persists, include this request's id when you contact support."
          }
        },
        "summary": "Create a customer",
        "tags": [
          "Customers"
        ]
      }
    },
    "/customers/{customer_id}": {
      "get": {
        "description": "Requires the `customers:read` scope. A key without it answers 403 `permission_denied`, naming the scope and this endpoint.",
        "operationId": "get_customer",
        "parameters": [
          {
            "description": "The resource's opaque id, `cus_` followed by 32 hex characters. An id belonging to another winery answers 404, never 403 \u2014 \"not found\" and \"not yours\" are deliberately the same answer.",
            "in": "path",
            "name": "customer_id",
            "required": true,
            "schema": {
              "pattern": "^cus_[0-9a-f]{32}$",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Customer"
                }
              }
            },
            "description": "Success."
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`authentication_failed` \u2014 Missing or invalid API key. Send it as 'Authorization: Bearer sk_live_\u2026'."
          },
          "402": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`subscription_inactive` \u2014 This winery's SipsPOS subscription isn't active, so its API access is paused."
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`permission_denied` \u2014 This API key doesn't have permission to do that. `feature_not_enabled` \u2014 That feature isn't enabled on this winery's plan."
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`not_found` \u2014 We couldn't find that."
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`rate_limited` \u2014 You've sent too many requests. Wait for the window in Retry-After, then try again."
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`internal` \u2014 An unexpected error occurred. Our team has been notified; if it persists, include this request's id when you contact support."
          }
        },
        "summary": "Read one customer",
        "tags": [
          "Customers"
        ]
      },
      "patch": {
        "description": "Send only the fields you are changing. `PATCH` rather than `PUT` because this resource publishes fields it will not accept back \u2014 club state and loyalty points are maintained elsewhere \u2014 so 'here is the whole object' could not mean what it says.\n\nNo `Idempotency-Key` is required: assigning the same values twice already has the same outcome.\n\nRequires the `customers:write` scope. A key without it answers 403 `permission_denied`, naming the scope and this endpoint.",
        "operationId": "update_customer",
        "parameters": [
          {
            "description": "The resource's opaque id, `cus_` followed by 32 hex characters. An id belonging to another winery answers 404, never 403 \u2014 \"not found\" and \"not yours\" are deliberately the same answer.",
            "in": "path",
            "name": "customer_id",
            "required": true,
            "schema": {
              "pattern": "^cus_[0-9a-f]{32}$",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "additionalProperties": false,
                "properties": {
                  "address": {
                    "description": "Nested postal address, in the same shape `GET /customers` returns. Omitted keys are left alone; an explicit `null` clears one.",
                    "oneOf": [
                      {
                        "additionalProperties": false,
                        "properties": {
                          "city": {
                            "description": "City or town.",
                            "maxLength": 120,
                            "type": "string"
                          },
                          "country": {
                            "description": "ISO 3166-1 alpha-2 country code.",
                            "maxLength": 60,
                            "type": "string"
                          },
                          "line1": {
                            "description": "Street address.",
                            "maxLength": 200,
                            "type": "string"
                          },
                          "line2": {
                            "description": "Unit, suite or second address line.",
                            "maxLength": 200,
                            "type": "string"
                          },
                          "postal_code": {
                            "description": "Postal or ZIP code.",
                            "maxLength": 20,
                            "type": "string"
                          },
                          "state": {
                            "description": "Province or state. Use the subdivision code `GET /market` publishes for this winery's country.",
                            "maxLength": 80,
                            "type": "string"
                          }
                        },
                        "type": "object"
                      },
                      {
                        "type": "null"
                      }
                    ]
                  },
                  "date_of_birth": {
                    "description": "`YYYY-MM-DD`. Used for the legal-age check on shipped orders.",
                    "oneOf": [
                      {
                        "format": "date",
                        "type": "string"
                      },
                      {
                        "type": "null"
                      }
                    ]
                  },
                  "email": {
                    "description": "Not unique in this schema and not verified \u2014 `GET /customers?email=` may legitimately return several rows.",
                    "oneOf": [
                      {
                        "maxLength": 150,
                        "type": "string"
                      },
                      {
                        "type": "null"
                      }
                    ]
                  },
                  "email_opt_in": {
                    "description": "Explicit consent to marketing email.",
                    "oneOf": [
                      {
                        "type": "boolean"
                      },
                      {
                        "type": "null"
                      }
                    ]
                  },
                  "email_opt_out": {
                    "description": "Suppresses marketing email. Transactional mail still sends.",
                    "oneOf": [
                      {
                        "type": "boolean"
                      },
                      {
                        "type": "null"
                      }
                    ]
                  },
                  "fulfillment_method": {
                    "description": "Whether this customer's orders default to pickup or shipping.",
                    "oneOf": [
                      {
                        "enum": [
                          "pickup",
                          "ship"
                        ],
                        "type": "string"
                      },
                      {
                        "type": "null"
                      }
                    ]
                  },
                  "is_senior": {
                    "description": "Eligible for this winery's senior discount.",
                    "oneOf": [
                      {
                        "type": "boolean"
                      },
                      {
                        "type": "null"
                      }
                    ]
                  },
                  "is_tour_group": {
                    "description": "This 'customer' is a tour group rather than a person.",
                    "oneOf": [
                      {
                        "type": "boolean"
                      },
                      {
                        "type": "null"
                      }
                    ]
                  },
                  "name": {
                    "description": "The customer's full name. Required.",
                    "oneOf": [
                      {
                        "maxLength": 150,
                        "type": "string"
                      },
                      {
                        "type": "null"
                      }
                    ]
                  },
                  "phone": {
                    "description": "Contact phone.",
                    "oneOf": [
                      {
                        "maxLength": 40,
                        "type": "string"
                      },
                      {
                        "type": "null"
                      }
                    ]
                  },
                  "sms_opt_in": {
                    "description": "Explicit consent to marketing SMS.",
                    "oneOf": [
                      {
                        "type": "boolean"
                      },
                      {
                        "type": "null"
                      }
                    ]
                  },
                  "tags": {
                    "description": "The winery's own CRM tag names, e.g. `[\"VIP\"]`. SETTING THIS REPLACES THE WHOLE SET \u2014 send the tags the customer should end up with, not the ones to add, and `GET` first if you mean to add one. `[]` removes them all. A name this winery has not created is refused: tags segment wine releases, so the vocabulary is the winery's to define.",
                    "oneOf": [
                      {
                        "items": {
                          "maxLength": 40,
                          "type": "string"
                        },
                        "type": "array"
                      },
                      {
                        "type": "null"
                      }
                    ]
                  },
                  "tax_exempt": {
                    "description": "Exempt from sales tax. Most jurisdictions want a certificate recorded alongside.",
                    "oneOf": [
                      {
                        "type": "boolean"
                      },
                      {
                        "type": "null"
                      }
                    ]
                  },
                  "tax_exempt_certificate": {
                    "description": "The exemption certificate number on file.",
                    "oneOf": [
                      {
                        "maxLength": 64,
                        "type": "string"
                      },
                      {
                        "type": "null"
                      }
                    ]
                  }
                },
                "type": "object"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Customer"
                }
              }
            },
            "description": "Success."
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`invalid_request` \u2014 That request wasn't valid. Check the parameters and try again."
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`authentication_failed` \u2014 Missing or invalid API key. Send it as 'Authorization: Bearer sk_live_\u2026'."
          },
          "402": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`subscription_inactive` \u2014 This winery's SipsPOS subscription isn't active, so its API access is paused."
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`permission_denied` \u2014 This API key doesn't have permission to do that. `feature_not_enabled` \u2014 That feature isn't enabled on this winery's plan."
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`not_found` \u2014 We couldn't find that."
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`rate_limited` \u2014 You've sent too many requests. Wait for the window in Retry-After, then try again."
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`internal` \u2014 An unexpected error occurred. Our team has been notified; if it persists, include this request's id when you contact support."
          }
        },
        "summary": "Update a customer",
        "tags": [
          "Customers"
        ]
      }
    },
    "/customers/{customer_id}/communications": {
      "get": {
        "description": "Everything the winery has sent this customer, plus the calls and notes their staff logged, newest first \u2014 the read half of `POST /customers/{id}/notes`. Without it you can record that you phoned somebody and cannot check whether the winery already did, which is how a retention workflow contacts a member twice.\n\n`status` is the field worth reading carefully: `failed` means the message did not arrive.\n\nThere are no filters. The list is already one customer's and it is newest-first, so \"have we contacted them lately\" is answered by the first page; anything you send in the query string is a `400` naming it, rather than a page you would wrongly believe was narrowed.\n\nTHIS IS PER-CUSTOMER AND THERE IS NO GLOBAL FEED, which has a cost worth knowing before you design around it: a \"who have we contacted this week\" view is one request per customer. If you need that sweep, tell us \u2014 the answer is a different endpoint, not this one called in a loop.\n\nRequires the `customers:read` scope. A key without it answers 403 `permission_denied`, naming the scope and this endpoint.",
        "operationId": "list_customer_communications",
        "parameters": [
          {
            "description": "The resource's opaque id, `cus_` followed by 32 hex characters. An id belonging to another winery answers 404, never 403 \u2014 \"not found\" and \"not yours\" are deliberately the same answer.",
            "in": "path",
            "name": "customer_id",
            "required": true,
            "schema": {
              "pattern": "^cus_[0-9a-f]{32}$",
              "type": "string"
            }
          },
          {
            "description": "How many rows to return, 1\u2013200 (default 50). Out-of-range and non-integer values are refused rather than clamped: quietly reading `limit=1000` as 200 teaches a caller its request worked.",
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "default": 50,
              "maximum": 200,
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "description": "The `next_cursor` from the previous page, sent back verbatim. Cursors are signed and bound to the filters and ordering they were minted under \u2014 replaying one against a differently filtered list is refused, not silently resumed part-way down a wider list. There is no page number or offset.",
            "in": "query",
            "name": "cursor",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "additionalProperties": false,
                  "properties": {
                    "data": {
                      "items": {
                        "$ref": "#/components/schemas/Communication"
                      },
                      "type": "array"
                    },
                    "has_more": {
                      "description": "Whether another page follows.",
                      "type": "boolean"
                    },
                    "next_cursor": {
                      "description": "Send this back as `?cursor=` for the next page. `null` on the last page \u2014 always present, never absent, so a generated type has no optional field here.",
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "object": {
                      "const": "list",
                      "type": "string"
                    }
                  },
                  "required": [
                    "object",
                    "data",
                    "has_more",
                    "next_cursor"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "Success."
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`authentication_failed` \u2014 Missing or invalid API key. Send it as 'Authorization: Bearer sk_live_\u2026'."
          },
          "402": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`subscription_inactive` \u2014 This winery's SipsPOS subscription isn't active, so its API access is paused."
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`permission_denied` \u2014 This API key doesn't have permission to do that. `feature_not_enabled` \u2014 That feature isn't enabled on this winery's plan."
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`not_found` \u2014 We couldn't find that."
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`rate_limited` \u2014 You've sent too many requests. Wait for the window in Retry-After, then try again."
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`internal` \u2014 An unexpected error occurred. Our team has been notified; if it persists, include this request's id when you contact support."
          }
        },
        "summary": "Read a customer's communication history",
        "tags": [
          "Customers"
        ]
      }
    },
    "/customers/{customer_id}/notes": {
      "post": {
        "description": "Records that somebody was contacted, onto the winery's own customer timeline \u2014 the same list their staff write to, so your entry sits beside theirs rather than in a parallel record only you can see.\n\n`channel` is `note` or `call`, and deliberately nothing else. An `email` or `sms` entry asserts that a message was *sent*, and that timeline is what a winery consults in a deliverability dispute; if you sent your own email, log a note saying so.\n\nRequires an `Idempotency-Key` header: this creates a row, so a retry after a timeout would otherwise claim a second call.\n\nRequires the `customers:write` scope. A key without it answers 403 `permission_denied`, naming the scope and this endpoint.",
        "operationId": "create_customer_note",
        "parameters": [
          {
            "description": "The resource's opaque id, `cus_` followed by 32 hex characters. An id belonging to another winery answers 404, never 403 \u2014 \"not found\" and \"not yours\" are deliberately the same answer.",
            "in": "path",
            "name": "customer_id",
            "required": true,
            "schema": {
              "pattern": "^cus_[0-9a-f]{32}$",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "additionalProperties": false,
                "properties": {
                  "channel": {
                    "default": "note",
                    "description": "`note` or `call`. Not `email` or `sms`: those assert a message was sent, and this API will not write a claim nobody can verify.",
                    "enum": [
                      "note",
                      "call"
                    ],
                    "type": "string"
                  },
                  "note": {
                    "description": "What you did, as the winery will read it on the customer page. Longer text is truncated to 200 characters, the same limit their own screen applies.",
                    "maxLength": 200,
                    "type": "string"
                  }
                },
                "required": [
                  "note"
                ],
                "type": "object"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CustomerNote"
                }
              }
            },
            "description": "Success."
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`invalid_request` \u2014 That request wasn't valid. Check the parameters and try again."
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`authentication_failed` \u2014 Missing or invalid API key. Send it as 'Authorization: Bearer sk_live_\u2026'."
          },
          "402": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`subscription_inactive` \u2014 This winery's SipsPOS subscription isn't active, so its API access is paused."
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`permission_denied` \u2014 This API key doesn't have permission to do that. `feature_not_enabled` \u2014 That feature isn't enabled on this winery's plan."
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`not_found` \u2014 We couldn't find that."
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`rate_limited` \u2014 You've sent too many requests. Wait for the window in Retry-After, then try again."
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`internal` \u2014 An unexpected error occurred. Our team has been notified; if it persists, include this request's id when you contact support."
          }
        },
        "summary": "Log a note or call against a customer",
        "tags": [
          "Customers"
        ]
      }
    },
    "/experiences": {
      "get": {
        "description": "Tasting flights and tours. Inactive and walk-in-only ones are listed too \u2014 both flags are on the payload, so filtering them out is your call rather than a hidden one.\n\nThe raw schedule is not published. Ask `/availability` for a date instead: it runs the same code the winery's own booking page does, over the schedule, blackouts, venue hours and live capacity.\n\nRequires the `bookings:read` scope. A key without it answers 403 `permission_denied`, naming the scope and this endpoint.",
        "operationId": "list_experiences",
        "parameters": [
          {
            "description": "How many rows to return, 1\u2013200 (default 50). Out-of-range and non-integer values are refused rather than clamped: quietly reading `limit=1000` as 200 teaches a caller its request worked.",
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "default": 50,
              "maximum": 200,
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "description": "The `next_cursor` from the previous page, sent back verbatim. Cursors are signed and bound to the filters and ordering they were minted under \u2014 replaying one against a differently filtered list is refused, not silently resumed part-way down a wider list. There is no page number or offset.",
            "in": "query",
            "name": "cursor",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Only active, or only inactive, experiences.",
            "in": "query",
            "name": "is_active",
            "required": false,
            "schema": {
              "type": "boolean"
            }
          },
          {
            "description": "Only experiences of this kind.",
            "in": "query",
            "name": "kind",
            "required": false,
            "schema": {
              "enum": [
                "tasting",
                "tour"
              ],
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "additionalProperties": false,
                  "properties": {
                    "data": {
                      "items": {
                        "$ref": "#/components/schemas/Experience"
                      },
                      "type": "array"
                    },
                    "has_more": {
                      "description": "Whether another page follows.",
                      "type": "boolean"
                    },
                    "next_cursor": {
                      "description": "Send this back as `?cursor=` for the next page. `null` on the last page \u2014 always present, never absent, so a generated type has no optional field here.",
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "object": {
                      "const": "list",
                      "type": "string"
                    }
                  },
                  "required": [
                    "object",
                    "data",
                    "has_more",
                    "next_cursor"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "Success."
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`invalid_request` \u2014 That request wasn't valid. Check the parameters and try again."
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`authentication_failed` \u2014 Missing or invalid API key. Send it as 'Authorization: Bearer sk_live_\u2026'."
          },
          "402": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`subscription_inactive` \u2014 This winery's SipsPOS subscription isn't active, so its API access is paused."
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`permission_denied` \u2014 This API key doesn't have permission to do that. `feature_not_enabled` \u2014 That feature isn't enabled on this winery's plan."
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`rate_limited` \u2014 You've sent too many requests. Wait for the window in Retry-After, then try again."
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`internal` \u2014 An unexpected error occurred. Our team has been notified; if it persists, include this request's id when you contact support."
          }
        },
        "summary": "List bookable experiences",
        "tags": [
          "Bookings"
        ]
      }
    },
    "/experiences/{experience_id}/availability": {
      "get": {
        "description": "Past slots are already dropped, so asking for today mid-afternoon returns only what can still be booked.\n\n**`slots` is `null`, not `[]`, for an `open` experience.** An open experience has no fixed slots \u2014 a guest books any time inside the venue's hours \u2014 and an empty list would read as fully booked, stopping you selling a day that is completely free.\n\nRequires the `bookings:read` scope. A key without it answers 403 `permission_denied`, naming the scope and this endpoint.",
        "operationId": "experience_availability",
        "parameters": [
          {
            "description": "The resource's opaque id, `exp_` followed by 32 hex characters. An id belonging to another winery answers 404, never 403 \u2014 \"not found\" and \"not yours\" are deliberately the same answer.",
            "in": "path",
            "name": "experience_id",
            "required": true,
            "schema": {
              "pattern": "^exp_[0-9a-f]{32}$",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Availability"
                }
              }
            },
            "description": "Success."
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`authentication_failed` \u2014 Missing or invalid API key. Send it as 'Authorization: Bearer sk_live_\u2026'."
          },
          "402": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`subscription_inactive` \u2014 This winery's SipsPOS subscription isn't active, so its API access is paused."
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`permission_denied` \u2014 This API key doesn't have permission to do that. `feature_not_enabled` \u2014 That feature isn't enabled on this winery's plan."
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`not_found` \u2014 We couldn't find that."
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`rate_limited` \u2014 You've sent too many requests. Wait for the window in Retry-After, then try again."
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`internal` \u2014 An unexpected error occurred. Our team has been notified; if it persists, include this request's id when you contact support."
          }
        },
        "summary": "Bookable slots for one date",
        "tags": [
          "Bookings"
        ]
      }
    },
    "/fulfillments": {
      "get": {
        "description": "What is waiting to ship, what is on its way, and where each parcel is going \u2014 the 3PL surface. Shop orders and club allocations arrive here through the same shape.\n\nRequires the `fulfillment:read` scope. A key without it answers 403 `permission_denied`, naming the scope and this endpoint.",
        "operationId": "list_fulfillments",
        "parameters": [
          {
            "description": "How many rows to return, 1\u2013200 (default 50). Out-of-range and non-integer values are refused rather than clamped: quietly reading `limit=1000` as 200 teaches a caller its request worked.",
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "default": 50,
              "maximum": 200,
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "description": "The `next_cursor` from the previous page, sent back verbatim. Cursors are signed and bound to the filters and ordering they were minted under \u2014 replaying one against a differently filtered list is refused, not silently resumed part-way down a wider list. There is no page number or offset.",
            "in": "query",
            "name": "cursor",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "`shop` for an online order, `club` for a club allocation. Validated, unlike `status`: a typo here would answer 200 with an empty page, which a nightly integration reads as \"nothing to ship today\".",
            "in": "query",
            "name": "kind",
            "required": false,
            "schema": {
              "enum": [
                "shop",
                "club"
              ],
              "type": "string"
            }
          },
          {
            "description": "Only fulfillments in this state \u2014 `unfulfilled`, `shipped`, `picked_up` or `cancelled` today. Not validated against that list on purpose: it has grown before and a future state must not turn an existing filter into a 400.",
            "in": "query",
            "name": "status",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "additionalProperties": false,
                  "properties": {
                    "data": {
                      "items": {
                        "$ref": "#/components/schemas/Fulfillment"
                      },
                      "type": "array"
                    },
                    "has_more": {
                      "description": "Whether another page follows.",
                      "type": "boolean"
                    },
                    "next_cursor": {
                      "description": "Send this back as `?cursor=` for the next page. `null` on the last page \u2014 always present, never absent, so a generated type has no optional field here.",
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "object": {
                      "const": "list",
                      "type": "string"
                    }
                  },
                  "required": [
                    "object",
                    "data",
                    "has_more",
                    "next_cursor"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "Success."
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`invalid_request` \u2014 That request wasn't valid. Check the parameters and try again."
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`authentication_failed` \u2014 Missing or invalid API key. Send it as 'Authorization: Bearer sk_live_\u2026'."
          },
          "402": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`subscription_inactive` \u2014 This winery's SipsPOS subscription isn't active, so its API access is paused."
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`permission_denied` \u2014 This API key doesn't have permission to do that. `feature_not_enabled` \u2014 That feature isn't enabled on this winery's plan."
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`rate_limited` \u2014 You've sent too many requests. Wait for the window in Retry-After, then try again."
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`internal` \u2014 An unexpected error occurred. Our team has been notified; if it persists, include this request's id when you contact support."
          }
        },
        "summary": "List the pack queue",
        "tags": [
          "Fulfillment"
        ]
      }
    },
    "/fulfillments/{fulfillment_id}": {
      "get": {
        "description": "Requires the `fulfillment:read` scope. A key without it answers 403 `permission_denied`, naming the scope and this endpoint.",
        "operationId": "get_fulfillment",
        "parameters": [
          {
            "description": "The resource's opaque id, `ful_` followed by 32 hex characters. An id belonging to another winery answers 404, never 403 \u2014 \"not found\" and \"not yours\" are deliberately the same answer.",
            "in": "path",
            "name": "fulfillment_id",
            "required": true,
            "schema": {
              "pattern": "^ful_[0-9a-f]{32}$",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Fulfillment"
                }
              }
            },
            "description": "Success."
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`authentication_failed` \u2014 Missing or invalid API key. Send it as 'Authorization: Bearer sk_live_\u2026'."
          },
          "402": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`subscription_inactive` \u2014 This winery's SipsPOS subscription isn't active, so its API access is paused."
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`permission_denied` \u2014 This API key doesn't have permission to do that. `feature_not_enabled` \u2014 That feature isn't enabled on this winery's plan."
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`not_found` \u2014 We couldn't find that."
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`rate_limited` \u2014 You've sent too many requests. Wait for the window in Retry-After, then try again."
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`internal` \u2014 An unexpected error occurred. Our team has been notified; if it persists, include this request's id when you contact support."
          }
        },
        "summary": "Read one fulfillment",
        "tags": [
          "Fulfillment"
        ]
      }
    },
    "/fulfillments/{fulfillment_id}/ship": {
      "post": {
        "description": "The write half of the 3PL surface: read the pack queue, ship the parcel, post the tracking back. The carrier must be one this winery's market recognises \u2014 `GET /market` lists them. Only an unfulfilled shipment can be marked shipped; anything else is a 400 saying so.\n\nRequires an `Idempotency-Key` header. Retry with the same key and you get the original response back (with `Idempotent-Replay: true`) rather than a second attempt \u2014 which matters here because a second attempt would be refused as already-shipped, and that reads like your tracking number was rejected.\n\nRequires the `fulfillment:write` scope. A key without it answers 403 `permission_denied`, naming the scope and this endpoint.",
        "operationId": "ship_fulfillment",
        "parameters": [
          {
            "description": "The resource's opaque id, `ful_` followed by 32 hex characters. An id belonging to another winery answers 404, never 403 \u2014 \"not found\" and \"not yours\" are deliberately the same answer.",
            "in": "path",
            "name": "fulfillment_id",
            "required": true,
            "schema": {
              "pattern": "^ful_[0-9a-f]{32}$",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "additionalProperties": false,
                "properties": {
                  "carrier": {
                    "description": "The carrier code. Must be one this winery's market recognises \u2014 `GET /market` lists them with their display names. Required.",
                    "type": "string"
                  },
                  "tracking_number": {
                    "description": "The carrier's tracking number, as printed on the label. Required, and not validated against the carrier's own format \u2014 a rejected number you can see beats a shipment we refused to record.",
                    "type": "string"
                  }
                },
                "required": [
                  "carrier",
                  "tracking_number"
                ],
                "type": "object"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Fulfillment"
                }
              }
            },
            "description": "Success."
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`invalid_request` \u2014 That request wasn't valid. Check the parameters and try again."
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`authentication_failed` \u2014 Missing or invalid API key. Send it as 'Authorization: Bearer sk_live_\u2026'."
          },
          "402": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`subscription_inactive` \u2014 This winery's SipsPOS subscription isn't active, so its API access is paused."
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`permission_denied` \u2014 This API key doesn't have permission to do that. `feature_not_enabled` \u2014 That feature isn't enabled on this winery's plan."
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`not_found` \u2014 We couldn't find that."
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`rate_limited` \u2014 You've sent too many requests. Wait for the window in Retry-After, then try again."
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`internal` \u2014 An unexpected error occurred. Our team has been notified; if it persists, include this request's id when you contact support."
          }
        },
        "summary": "Mark a parcel shipped, with carrier and tracking",
        "tags": [
          "Fulfillment"
        ]
      }
    },
    "/inventory": {
      "get": {
        "description": "Every product, including untracked ones and bundles \u2014 both carry the flag that says so, and hiding them here would make this endpoint and `/v1/products` disagree about which products exist.\n\nRequires the `inventory:read` scope. A key without it answers 403 `permission_denied`, naming the scope and this endpoint.",
        "operationId": "list_inventory",
        "parameters": [
          {
            "description": "How many rows to return, 1\u2013200 (default 50). Out-of-range and non-integer values are refused rather than clamped: quietly reading `limit=1000` as 200 teaches a caller its request worked.",
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "default": 50,
              "maximum": 200,
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "description": "The `next_cursor` from the previous page, sent back verbatim. Cursors are signed and bound to the filters and ordering they were minted under \u2014 replaying one against a differently filtered list is refused, not silently resumed part-way down a wider list. There is no page number or offset.",
            "in": "query",
            "name": "cursor",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Incremental sync: rows created or edited at or after this timestamp. Sending it changes the walk to oldest-edit-first \u2014 the only ordering in which a row edited while you are paging is still delivered by the same walk. Naive values are read in the winery's time zone.",
            "in": "query",
            "name": "updated_since",
            "required": false,
            "schema": {
              "format": "date-time",
              "type": "string"
            }
          },
          {
            "description": "Just this product's stock record. An id that resolves to nothing is a 404, not an empty page: \"no stock record\" is something a stock feed acts on.",
            "in": "query",
            "name": "product_id",
            "required": false,
            "schema": {
              "pattern": "^prod_[0-9a-f]{32}$",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "additionalProperties": false,
                  "properties": {
                    "data": {
                      "items": {
                        "$ref": "#/components/schemas/InventoryLevel"
                      },
                      "type": "array"
                    },
                    "has_more": {
                      "description": "Whether another page follows.",
                      "type": "boolean"
                    },
                    "next_cursor": {
                      "description": "Send this back as `?cursor=` for the next page. `null` on the last page \u2014 always present, never absent, so a generated type has no optional field here.",
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "object": {
                      "const": "list",
                      "type": "string"
                    }
                  },
                  "required": [
                    "object",
                    "data",
                    "has_more",
                    "next_cursor"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "Success."
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`invalid_request` \u2014 That request wasn't valid. Check the parameters and try again."
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`authentication_failed` \u2014 Missing or invalid API key. Send it as 'Authorization: Bearer sk_live_\u2026'."
          },
          "402": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`subscription_inactive` \u2014 This winery's SipsPOS subscription isn't active, so its API access is paused."
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`permission_denied` \u2014 This API key doesn't have permission to do that. `feature_not_enabled` \u2014 That feature isn't enabled on this winery's plan."
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`not_found` \u2014 We couldn't find that."
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`rate_limited` \u2014 You've sent too many requests. Wait for the window in Retry-After, then try again."
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`internal` \u2014 An unexpected error occurred. Our team has been notified; if it persists, include this request's id when you contact support."
          }
        },
        "summary": "List bottles on hand, per product",
        "tags": [
          "Inventory"
        ]
      }
    },
    "/inventory/adjustments": {
      "post": {
        "description": "Records one stock movement and returns it, `201`. The body carries a signed `change_bottles` \u2014 a delta, not a new total. Absolute would suit a stocktake better, but it composes wrongly: posting 'set to 40' while the tasting room sells two bottles silently discards the sale. Two deltas both land.\n\n**It refuses to drive stock below zero rather than clamping**, which is where it differs from the winery's own receive screen. A human who clamps sees the result; a script does not, and would go on believing a mirror that had quietly drifted. The 400 names the current level so you can decide what to do.\n\n`reason` is restricted to the four a partner may write. `sale`, `refund`, `tasting`, `allocation` and `bottling` also exist in the ledger, and each is written by the system that did the thing \u2014 a partner posting one would make the winery's inventory reports describe a transaction that never happened.\n\nRequires an `Idempotency-Key` header. Retry with the same key and you get the original movement back rather than a second one.\n\nRequires the `inventory:write` scope. A key without it answers 403 `permission_denied`, naming the scope and this endpoint.",
        "operationId": "create_inventory_adjustment",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "additionalProperties": false,
                "properties": {
                  "change_bottles": {
                    "description": "Signed: positive adds, negative removes. Required, and `0` is refused rather than recorded as a movement that did nothing.",
                    "type": "integer"
                  },
                  "note": {
                    "description": "Free text kept on the ledger row \u2014 a PO number, a breakage description, whatever the winery will want to read six months from now.",
                    "maxLength": 255,
                    "type": "string"
                  },
                  "product_id": {
                    "description": "The product whose stock is moving. Required. Another winery's id is a 404, and so is a bundle's \u2014 a pack holds no stock of its own.",
                    "pattern": "^prod_[0-9a-f]{32}$",
                    "type": "string"
                  },
                  "reason": {
                    "description": "Why the stock moved. Required. This is the subset a partner may write; the ledger's other reasons belong to the systems that produce them.",
                    "enum": [
                      "restock",
                      "adjustment",
                      "wastage",
                      "import"
                    ],
                    "type": "string"
                  }
                },
                "required": [
                  "product_id",
                  "change_bottles",
                  "reason"
                ],
                "type": "object"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InventoryMovement"
                }
              }
            },
            "description": "Success."
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`invalid_request` \u2014 That request wasn't valid. Check the parameters and try again."
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`authentication_failed` \u2014 Missing or invalid API key. Send it as 'Authorization: Bearer sk_live_\u2026'."
          },
          "402": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`subscription_inactive` \u2014 This winery's SipsPOS subscription isn't active, so its API access is paused."
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`permission_denied` \u2014 This API key doesn't have permission to do that. `feature_not_enabled` \u2014 That feature isn't enabled on this winery's plan."
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`rate_limited` \u2014 You've sent too many requests. Wait for the window in Retry-After, then try again."
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`internal` \u2014 An unexpected error occurred. Our team has been notified; if it persists, include this request's id when you contact support."
          }
        },
        "summary": "Move stock, and say why",
        "tags": [
          "Inventory"
        ]
      }
    },
    "/inventory/movements": {
      "get": {
        "description": "The ledger behind the counts: every restock, adjustment, wastage, sale and import, newest first.\n\nRequires the `inventory:read` scope. A key without it answers 403 `permission_denied`, naming the scope and this endpoint.",
        "operationId": "list_inventory_movements",
        "parameters": [
          {
            "description": "How many rows to return, 1\u2013200 (default 50). Out-of-range and non-integer values are refused rather than clamped: quietly reading `limit=1000` as 200 teaches a caller its request worked.",
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "default": 50,
              "maximum": 200,
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "description": "The `next_cursor` from the previous page, sent back verbatim. Cursors are signed and bound to the filters and ordering they were minted under \u2014 replaying one against a differently filtered list is refused, not silently resumed part-way down a wider list. There is no page number or offset.",
            "in": "query",
            "name": "cursor",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Only movements recorded at or after this timestamp, inclusive. Naive values are read in the winery's own time zone.",
            "in": "query",
            "name": "created_since",
            "required": false,
            "schema": {
              "format": "date-time",
              "type": "string"
            }
          },
          {
            "description": "Only movements for this product. An id that resolves to nothing is a 404, for the same reason it is on `/v1/inventory`.",
            "in": "query",
            "name": "product_id",
            "required": false,
            "schema": {
              "pattern": "^prod_[0-9a-f]{32}$",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "additionalProperties": false,
                  "properties": {
                    "data": {
                      "items": {
                        "$ref": "#/components/schemas/InventoryMovement"
                      },
                      "type": "array"
                    },
                    "has_more": {
                      "description": "Whether another page follows.",
                      "type": "boolean"
                    },
                    "next_cursor": {
                      "description": "Send this back as `?cursor=` for the next page. `null` on the last page \u2014 always present, never absent, so a generated type has no optional field here.",
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "object": {
                      "const": "list",
                      "type": "string"
                    }
                  },
                  "required": [
                    "object",
                    "data",
                    "has_more",
                    "next_cursor"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "Success."
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`invalid_request` \u2014 That request wasn't valid. Check the parameters and try again."
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`authentication_failed` \u2014 Missing or invalid API key. Send it as 'Authorization: Bearer sk_live_\u2026'."
          },
          "402": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`subscription_inactive` \u2014 This winery's SipsPOS subscription isn't active, so its API access is paused."
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`permission_denied` \u2014 This API key doesn't have permission to do that. `feature_not_enabled` \u2014 That feature isn't enabled on this winery's plan."
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`not_found` \u2014 We couldn't find that."
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`rate_limited` \u2014 You've sent too many requests. Wait for the window in Retry-After, then try again."
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`internal` \u2014 An unexpected error occurred. Our team has been notified; if it persists, include this request's id when you contact support."
          }
        },
        "summary": "List stock movements",
        "tags": [
          "Inventory"
        ]
      }
    },
    "/market": {
      "get": {
        "description": "Everything a partner needs to stop writing country conditionals: the currency its money is quoted in, its IANA time zone, whether a destination is a province or a state and what to call the postal field, the legal drinking age, the carriers it ships with, and which subdivisions it may ship to today. Read it once at startup. It is the only endpoint that describes the winery rather than its data.\n\nRequires the `market:read` scope. A key without it answers 403 `permission_denied`, naming the scope and this endpoint.",
        "operationId": "get_market",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Market"
                }
              }
            },
            "description": "Success."
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`authentication_failed` \u2014 Missing or invalid API key. Send it as 'Authorization: Bearer sk_live_\u2026'."
          },
          "402": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`subscription_inactive` \u2014 This winery's SipsPOS subscription isn't active, so its API access is paused."
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`permission_denied` \u2014 This API key doesn't have permission to do that. `feature_not_enabled` \u2014 That feature isn't enabled on this winery's plan."
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`rate_limited` \u2014 You've sent too many requests. Wait for the window in Retry-After, then try again."
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`internal` \u2014 An unexpected error occurred. Our team has been notified; if it persists, include this request's id when you contact support."
          }
        },
        "summary": "Read this winery's market configuration",
        "tags": [
          "Market"
        ]
      }
    },
    "/orders": {
      "get": {
        "description": "Requires the `orders:read` scope. A key without it answers 403 `permission_denied`, naming the scope and this endpoint.",
        "operationId": "list_orders",
        "parameters": [
          {
            "description": "How many rows to return, 1\u2013200 (default 50). Out-of-range and non-integer values are refused rather than clamped: quietly reading `limit=1000` as 200 teaches a caller its request worked.",
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "default": 50,
              "maximum": 200,
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "description": "The `next_cursor` from the previous page, sent back verbatim. Cursors are signed and bound to the filters and ordering they were minted under \u2014 replaying one against a differently filtered list is refused, not silently resumed part-way down a wider list. There is no page number or offset.",
            "in": "query",
            "name": "cursor",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Only orders created at or after this timestamp, inclusive. This is a range filter on an immutable column, not a sync cursor: an order edited after you read it will not reappear. Naive values are read in the winery's own time zone (see `GET /market`).",
            "in": "query",
            "name": "created_since",
            "required": false,
            "schema": {
              "format": "date-time",
              "type": "string"
            }
          },
          {
            "description": "Only orders in this state. Deliberately not validated against a fixed list \u2014 an order workflow gains states, and a partner filtering on one it already knows should not start getting 400s the day another is added.",
            "in": "query",
            "name": "status",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "additionalProperties": false,
                  "properties": {
                    "data": {
                      "items": {
                        "$ref": "#/components/schemas/Order"
                      },
                      "type": "array"
                    },
                    "has_more": {
                      "description": "Whether another page follows.",
                      "type": "boolean"
                    },
                    "next_cursor": {
                      "description": "Send this back as `?cursor=` for the next page. `null` on the last page \u2014 always present, never absent, so a generated type has no optional field here.",
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "object": {
                      "const": "list",
                      "type": "string"
                    }
                  },
                  "required": [
                    "object",
                    "data",
                    "has_more",
                    "next_cursor"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "Success."
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`invalid_request` \u2014 That request wasn't valid. Check the parameters and try again."
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`authentication_failed` \u2014 Missing or invalid API key. Send it as 'Authorization: Bearer sk_live_\u2026'."
          },
          "402": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`subscription_inactive` \u2014 This winery's SipsPOS subscription isn't active, so its API access is paused."
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`permission_denied` \u2014 This API key doesn't have permission to do that. `feature_not_enabled` \u2014 That feature isn't enabled on this winery's plan."
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`rate_limited` \u2014 You've sent too many requests. Wait for the window in Retry-After, then try again."
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`internal` \u2014 An unexpected error occurred. Our team has been notified; if it persists, include this request's id when you contact support."
          }
        },
        "summary": "List online-shop orders",
        "tags": [
          "Orders"
        ]
      },
      "post": {
        "description": "Creates a paid order, `201`. **You must be the merchant of record**: `external_payment` is required and is v1's only payment model. We capture nothing and record your reference, so the winery's own processor has no record of this order \u2014 `payment_processor` is `external` to say so.\n\n**`amount_cents` must equal the total we compute.** Price the cart with `POST /orders/quote` first: tax is per-province and per-product and shipping comes from the winery's own rules, so a guessed total is refused and the message names both numbers. The refusal happens before anything is written.\n\nEverything else is the winery's own checkout: compliance (permits, per-shipment and annual caps), the legal-age check on a shipped order, stock and tax all run exactly as they do for a shopper on their site. **This API cannot create an order the shop itself would refuse** \u2014 a blocked shipment is `422 compliance_blocked` with a `detail` array naming each reason.\n\nRequires an `Idempotency-Key` header. Separately, one `external_payment.reference` places at most one order.\n\nRequires the `orders:write` scope. A key without it answers 403 `permission_denied`, naming the scope and this endpoint.",
        "operationId": "create_order",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "additionalProperties": false,
                "properties": {
                  "contact": {
                    "additionalProperties": false,
                    "description": "Who is buying. `name` and `email` are both required \u2014 the email is where the confirmation and any gift card go.",
                    "properties": {
                      "email": {
                        "format": "email",
                        "type": "string"
                      },
                      "name": {
                        "type": "string"
                      },
                      "phone": {
                        "type": "string"
                      }
                    },
                    "required": [
                      "name",
                      "email"
                    ],
                    "type": "object"
                  },
                  "external_payment": {
                    "additionalProperties": false,
                    "description": "The payment YOU took. `reference` is what the winery reconciles against; `amount_cents` must equal the total from `POST /orders/quote`; `currency` is optional but must match the winery's if sent.",
                    "properties": {
                      "amount_cents": {
                        "minimum": 0,
                        "type": "integer"
                      },
                      "currency": {
                        "type": "string"
                      },
                      "reference": {
                        "type": "string"
                      }
                    },
                    "required": [
                      "reference",
                      "amount_cents"
                    ],
                    "type": "object"
                  },
                  "fulfillment_method": {
                    "description": "`ship` or `pickup`. Required.",
                    "enum": [
                      "ship",
                      "pickup"
                    ],
                    "type": "string"
                  },
                  "items": {
                    "description": "What is being bought. Required. A product the shop does not sell online is refused, the same rule the winery's own storefront applies.",
                    "items": {
                      "additionalProperties": false,
                      "properties": {
                        "product_id": {
                          "pattern": "^prod_[0-9a-f]{32}$",
                          "type": "string"
                        },
                        "quantity": {
                          "minimum": 1,
                          "type": "integer"
                        }
                      },
                      "required": [
                        "product_id",
                        "quantity"
                      ],
                      "type": "object"
                    },
                    "minItems": 1,
                    "type": "array"
                  },
                  "ship_dob": {
                    "description": "The buyer's date of birth, `YYYY-MM-DD`. Required by markets that mandate an age check on shipped wine (the US does; Canada does not), unless the customer already has one on file.",
                    "format": "date",
                    "type": "string"
                  },
                  "ship_to": {
                    "additionalProperties": false,
                    "description": "Destination. Required for `ship`, and refused for `pickup`. **`postal_code`, not `postal`** \u2014 the same key this API returns, so an address you read from us writes straight back.",
                    "properties": {
                      "city": {
                        "type": "string"
                      },
                      "country": {
                        "type": "string"
                      },
                      "line1": {
                        "type": "string"
                      },
                      "line2": {
                        "type": "string"
                      },
                      "postal_code": {
                        "type": "string"
                      },
                      "state": {
                        "type": "string"
                      }
                    },
                    "required": [
                      "line1",
                      "city",
                      "state"
                    ],
                    "type": "object"
                  }
                },
                "required": [
                  "fulfillment_method",
                  "contact",
                  "items",
                  "external_payment"
                ],
                "type": "object"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Order"
                }
              }
            },
            "description": "Success."
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`invalid_request` \u2014 That request wasn't valid. Check the parameters and try again."
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`authentication_failed` \u2014 Missing or invalid API key. Send it as 'Authorization: Bearer sk_live_\u2026'."
          },
          "402": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`subscription_inactive` \u2014 This winery's SipsPOS subscription isn't active, so its API access is paused."
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`permission_denied` \u2014 This API key doesn't have permission to do that. `feature_not_enabled` \u2014 That feature isn't enabled on this winery's plan."
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`rate_limited` \u2014 You've sent too many requests. Wait for the window in Retry-After, then try again."
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`internal` \u2014 An unexpected error occurred. Our team has been notified; if it persists, include this request's id when you contact support."
          }
        },
        "summary": "Place an order you have already charged for",
        "tags": [
          "Orders"
        ]
      }
    },
    "/orders/quote": {
      "post": {
        "description": "What this cart will total, with the same per-line tax breakdown the order will carry. Creates nothing, and needs only `orders:read` \u2014 asking what something costs is a question.\n\nThis is not `POST /shipping/quote`. That answers *may I ship these bottles to this person, and what would carriage cost*, priced off a subtotal you supply. This one prices REAL products: it reads each one's price, sale state and `taxable` flag.\n\nIt deliberately runs no compliance check \u2014 use `POST /shipping/quote` for that. `POST /orders` enforces both regardless of what either said earlier.\n\nRequires the `orders:read` scope. A key without it answers 403 `permission_denied`, naming the scope and this endpoint.",
        "operationId": "quote_order",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "additionalProperties": false,
                "properties": {
                  "fulfillment_method": {
                    "description": "`ship` or `pickup`. Required.",
                    "enum": [
                      "ship",
                      "pickup"
                    ],
                    "type": "string"
                  },
                  "items": {
                    "description": "What is being bought. Required. A product the shop does not sell online is refused, the same rule the winery's own storefront applies.",
                    "items": {
                      "additionalProperties": false,
                      "properties": {
                        "product_id": {
                          "pattern": "^prod_[0-9a-f]{32}$",
                          "type": "string"
                        },
                        "quantity": {
                          "minimum": 1,
                          "type": "integer"
                        }
                      },
                      "required": [
                        "product_id",
                        "quantity"
                      ],
                      "type": "object"
                    },
                    "minItems": 1,
                    "type": "array"
                  },
                  "ship_to": {
                    "additionalProperties": false,
                    "description": "Destination. Required for `ship`, and refused for `pickup`. **`postal_code`, not `postal`** \u2014 the same key this API returns, so an address you read from us writes straight back.",
                    "properties": {
                      "city": {
                        "type": "string"
                      },
                      "country": {
                        "type": "string"
                      },
                      "line1": {
                        "type": "string"
                      },
                      "line2": {
                        "type": "string"
                      },
                      "postal_code": {
                        "type": "string"
                      },
                      "state": {
                        "type": "string"
                      }
                    },
                    "required": [
                      "line1",
                      "city",
                      "state"
                    ],
                    "type": "object"
                  }
                },
                "required": [
                  "fulfillment_method",
                  "items"
                ],
                "type": "object"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OrderQuote"
                }
              }
            },
            "description": "Success."
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`invalid_request` \u2014 That request wasn't valid. Check the parameters and try again."
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`authentication_failed` \u2014 Missing or invalid API key. Send it as 'Authorization: Bearer sk_live_\u2026'."
          },
          "402": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`subscription_inactive` \u2014 This winery's SipsPOS subscription isn't active, so its API access is paused."
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`permission_denied` \u2014 This API key doesn't have permission to do that. `feature_not_enabled` \u2014 That feature isn't enabled on this winery's plan."
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`rate_limited` \u2014 You've sent too many requests. Wait for the window in Retry-After, then try again."
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`internal` \u2014 An unexpected error occurred. Our team has been notified; if it persists, include this request's id when you contact support."
          }
        },
        "summary": "Price a cart before you charge for it",
        "tags": [
          "Orders"
        ]
      }
    },
    "/orders/{order_id}": {
      "get": {
        "description": "Requires the `orders:read` scope. A key without it answers 403 `permission_denied`, naming the scope and this endpoint.",
        "operationId": "get_order",
        "parameters": [
          {
            "description": "The resource's opaque id, `ord_` followed by 32 hex characters. An id belonging to another winery answers 404, never 403 \u2014 \"not found\" and \"not yours\" are deliberately the same answer.",
            "in": "path",
            "name": "order_id",
            "required": true,
            "schema": {
              "pattern": "^ord_[0-9a-f]{32}$",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Order"
                }
              }
            },
            "description": "Success."
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`authentication_failed` \u2014 Missing or invalid API key. Send it as 'Authorization: Bearer sk_live_\u2026'."
          },
          "402": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`subscription_inactive` \u2014 This winery's SipsPOS subscription isn't active, so its API access is paused."
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`permission_denied` \u2014 This API key doesn't have permission to do that. `feature_not_enabled` \u2014 That feature isn't enabled on this winery's plan."
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`not_found` \u2014 We couldn't find that."
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`rate_limited` \u2014 You've sent too many requests. Wait for the window in Retry-After, then try again."
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`internal` \u2014 An unexpected error occurred. Our team has been notified; if it persists, include this request's id when you contact support."
          }
        },
        "summary": "Read one order",
        "tags": [
          "Orders"
        ]
      }
    },
    "/product-groups": {
      "get": {
        "description": "What a product's `group_id` resolves to \u2014 the winery's own category structure, which is what a storefront builds navigation from. A winery has a handful, so this takes no filters and the whole set fits in one page.\n\nRequires the `catalog:read` scope. A key without it answers 403 `permission_denied`, naming the scope and this endpoint.",
        "operationId": "list_product_groups",
        "parameters": [
          {
            "description": "How many rows to return, 1\u2013200 (default 50). Out-of-range and non-integer values are refused rather than clamped: quietly reading `limit=1000` as 200 teaches a caller its request worked.",
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "default": 50,
              "maximum": 200,
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "description": "The `next_cursor` from the previous page, sent back verbatim. Cursors are signed and bound to the filters and ordering they were minted under \u2014 replaying one against a differently filtered list is refused, not silently resumed part-way down a wider list. There is no page number or offset.",
            "in": "query",
            "name": "cursor",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "additionalProperties": false,
                  "properties": {
                    "data": {
                      "items": {
                        "$ref": "#/components/schemas/ProductGroup"
                      },
                      "type": "array"
                    },
                    "has_more": {
                      "description": "Whether another page follows.",
                      "type": "boolean"
                    },
                    "next_cursor": {
                      "description": "Send this back as `?cursor=` for the next page. `null` on the last page \u2014 always present, never absent, so a generated type has no optional field here.",
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "object": {
                      "const": "list",
                      "type": "string"
                    }
                  },
                  "required": [
                    "object",
                    "data",
                    "has_more",
                    "next_cursor"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "Success."
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`authentication_failed` \u2014 Missing or invalid API key. Send it as 'Authorization: Bearer sk_live_\u2026'."
          },
          "402": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`subscription_inactive` \u2014 This winery's SipsPOS subscription isn't active, so its API access is paused."
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`permission_denied` \u2014 This API key doesn't have permission to do that. `feature_not_enabled` \u2014 That feature isn't enabled on this winery's plan."
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`rate_limited` \u2014 You've sent too many requests. Wait for the window in Retry-After, then try again."
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`internal` \u2014 An unexpected error occurred. Our team has been notified; if it persists, include this request's id when you contact support."
          }
        },
        "summary": "List product groups",
        "tags": [
          "Catalog"
        ]
      }
    },
    "/products": {
      "get": {
        "description": "Requires the `catalog:read` scope. A key without it answers 403 `permission_denied`, naming the scope and this endpoint.",
        "operationId": "list_products",
        "parameters": [
          {
            "description": "How many rows to return, 1\u2013200 (default 50). Out-of-range and non-integer values are refused rather than clamped: quietly reading `limit=1000` as 200 teaches a caller its request worked.",
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "default": 50,
              "maximum": 200,
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "description": "The `next_cursor` from the previous page, sent back verbatim. Cursors are signed and bound to the filters and ordering they were minted under \u2014 replaying one against a differently filtered list is refused, not silently resumed part-way down a wider list. There is no page number or offset.",
            "in": "query",
            "name": "cursor",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Incremental sync: rows created or edited at or after this timestamp. Sending it changes the walk to oldest-edit-first \u2014 the only ordering in which a row edited while you are paging is still delivered by the same walk. Naive values are read in the winery's time zone.",
            "in": "query",
            "name": "updated_since",
            "required": false,
            "schema": {
              "format": "date-time",
              "type": "string"
            }
          },
          {
            "description": "Only products in this category, matched exactly. The winery's own category names \u2014 read them off the products themselves; there is no fixed vocabulary.",
            "in": "query",
            "name": "category",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "`true` for the products the winery offers in its online shop, `false` for the rest. Omit for both.",
            "in": "query",
            "name": "sold_online",
            "required": false,
            "schema": {
              "type": "boolean"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "additionalProperties": false,
                  "properties": {
                    "data": {
                      "items": {
                        "$ref": "#/components/schemas/Product"
                      },
                      "type": "array"
                    },
                    "has_more": {
                      "description": "Whether another page follows.",
                      "type": "boolean"
                    },
                    "next_cursor": {
                      "description": "Send this back as `?cursor=` for the next page. `null` on the last page \u2014 always present, never absent, so a generated type has no optional field here.",
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "object": {
                      "const": "list",
                      "type": "string"
                    }
                  },
                  "required": [
                    "object",
                    "data",
                    "has_more",
                    "next_cursor"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "Success."
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`invalid_request` \u2014 That request wasn't valid. Check the parameters and try again."
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`authentication_failed` \u2014 Missing or invalid API key. Send it as 'Authorization: Bearer sk_live_\u2026'."
          },
          "402": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`subscription_inactive` \u2014 This winery's SipsPOS subscription isn't active, so its API access is paused."
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`permission_denied` \u2014 This API key doesn't have permission to do that. `feature_not_enabled` \u2014 That feature isn't enabled on this winery's plan."
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`rate_limited` \u2014 You've sent too many requests. Wait for the window in Retry-After, then try again."
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`internal` \u2014 An unexpected error occurred. Our team has been notified; if it persists, include this request's id when you contact support."
          }
        },
        "summary": "List products",
        "tags": [
          "Catalog"
        ]
      },
      "post": {
        "description": "Creates a product and returns it, `201`. A new product is not listed in the shop until you set `sold_online`, so a half-built product left behind by a failing import never reaches a customer.\n\n`sku` is not checked for uniqueness, because this schema does not require it and the winery's own staff can create duplicates in the admin. An API stricter than the product behind it is one you cannot fix through the API.\n\nRequires an `Idempotency-Key` header.\n\nRequires the `catalog:write` scope. A key without it answers 403 `permission_denied`, naming the scope and this endpoint.",
        "operationId": "create_product",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "additionalProperties": false,
                "properties": {
                  "abv": {
                    "description": "Alcohol by volume, as a percentage.",
                    "oneOf": [
                      {
                        "maximum": 100,
                        "minimum": 0,
                        "type": "number"
                      },
                      {
                        "type": "null"
                      }
                    ]
                  },
                  "category": {
                    "description": "What kind of thing this is.",
                    "oneOf": [
                      {
                        "enum": [
                          "wine",
                          "merch",
                          "food",
                          "other"
                        ],
                        "type": "string"
                      },
                      {
                        "type": "null"
                      }
                    ]
                  },
                  "group_id": {
                    "description": "The `prgp_\u2026` id of the product group this belongs to, or `null` for none.",
                    "oneOf": [
                      {
                        "pattern": "^pgrp_[0-9a-f]{32}$",
                        "type": "string"
                      },
                      {
                        "type": "null"
                      }
                    ]
                  },
                  "is_active": {
                    "description": "Inactive products stay on historical orders but leave the register and shop.",
                    "oneOf": [
                      {
                        "type": "boolean"
                      },
                      {
                        "type": "null"
                      }
                    ]
                  },
                  "name": {
                    "description": "The product name as it appears on the register and the shop. Required.",
                    "maxLength": 180,
                    "type": "string"
                  },
                  "price": {
                    "description": "List price, in the same money object `GET /products` returns. Not writable on a bundle, whose price is the live sum of its components.",
                    "oneOf": [
                      {
                        "additionalProperties": false,
                        "properties": {
                          "amount_cents": {
                            "minimum": 0,
                            "type": "integer"
                          },
                          "currency": {
                            "description": "Optional. If sent it must match the winery's own currency \u2014 a mismatch is a 400 rather than a silent conversion.",
                            "type": "string"
                          }
                        },
                        "required": [
                          "amount_cents"
                        ],
                        "type": "object"
                      },
                      {
                        "type": "null"
                      }
                    ]
                  },
                  "region": {
                    "description": "Growing region, free text.",
                    "oneOf": [
                      {
                        "maxLength": 120,
                        "type": "string"
                      },
                      {
                        "type": "null"
                      }
                    ]
                  },
                  "sku": {
                    "description": "Your own stock code. Not unique in this schema and not generated for you.",
                    "oneOf": [
                      {
                        "maxLength": 60,
                        "type": "string"
                      },
                      {
                        "type": "null"
                      }
                    ]
                  },
                  "sold_online": {
                    "description": "Listed in the online shop.",
                    "oneOf": [
                      {
                        "type": "boolean"
                      },
                      {
                        "type": "null"
                      }
                    ]
                  },
                  "taxable": {
                    "description": "Sales tax applies.",
                    "oneOf": [
                      {
                        "type": "boolean"
                      },
                      {
                        "type": "null"
                      }
                    ]
                  },
                  "track_inventory": {
                    "description": "Whether stock is counted for this product. Turning it off does not zero the count.",
                    "oneOf": [
                      {
                        "type": "boolean"
                      },
                      {
                        "type": "null"
                      }
                    ]
                  },
                  "varietal": {
                    "description": "Grape varietal, free text.",
                    "oneOf": [
                      {
                        "maxLength": 120,
                        "type": "string"
                      },
                      {
                        "type": "null"
                      }
                    ]
                  },
                  "vintage": {
                    "description": "Vintage year.",
                    "oneOf": [
                      {
                        "maximum": 2200,
                        "minimum": 1800,
                        "type": "integer"
                      },
                      {
                        "type": "null"
                      }
                    ]
                  },
                  "volume_ml": {
                    "description": "Bottle size in millilitres.",
                    "oneOf": [
                      {
                        "maximum": 100000,
                        "minimum": 1,
                        "type": "integer"
                      },
                      {
                        "type": "null"
                      }
                    ]
                  }
                },
                "required": [
                  "name"
                ],
                "type": "object"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Product"
                }
              }
            },
            "description": "Success."
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`invalid_request` \u2014 That request wasn't valid. Check the parameters and try again."
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`authentication_failed` \u2014 Missing or invalid API key. Send it as 'Authorization: Bearer sk_live_\u2026'."
          },
          "402": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`subscription_inactive` \u2014 This winery's SipsPOS subscription isn't active, so its API access is paused."
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`permission_denied` \u2014 This API key doesn't have permission to do that. `feature_not_enabled` \u2014 That feature isn't enabled on this winery's plan."
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`rate_limited` \u2014 You've sent too many requests. Wait for the window in Retry-After, then try again."
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`internal` \u2014 An unexpected error occurred. Our team has been notified; if it persists, include this request's id when you contact support."
          }
        },
        "summary": "Create a product",
        "tags": [
          "Catalog"
        ]
      }
    },
    "/products/{product_id}": {
      "get": {
        "description": "Requires the `catalog:read` scope. A key without it answers 403 `permission_denied`, naming the scope and this endpoint.",
        "operationId": "get_product",
        "parameters": [
          {
            "description": "The resource's opaque id, `prod_` followed by 32 hex characters. An id belonging to another winery answers 404, never 403 \u2014 \"not found\" and \"not yours\" are deliberately the same answer.",
            "in": "path",
            "name": "product_id",
            "required": true,
            "schema": {
              "pattern": "^prod_[0-9a-f]{32}$",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Product"
                }
              }
            },
            "description": "Success."
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`authentication_failed` \u2014 Missing or invalid API key. Send it as 'Authorization: Bearer sk_live_\u2026'."
          },
          "402": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`subscription_inactive` \u2014 This winery's SipsPOS subscription isn't active, so its API access is paused."
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`permission_denied` \u2014 This API key doesn't have permission to do that. `feature_not_enabled` \u2014 That feature isn't enabled on this winery's plan."
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`not_found` \u2014 We couldn't find that."
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`rate_limited` \u2014 You've sent too many requests. Wait for the window in Retry-After, then try again."
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`internal` \u2014 An unexpected error occurred. Our team has been notified; if it persists, include this request's id when you contact support."
          }
        },
        "summary": "Read one product",
        "tags": [
          "Catalog"
        ]
      },
      "patch": {
        "description": "Send only the fields you are changing. `price` is refused on a bundle: a pack's price is the live sum of its components, so the column you would be setting is never read back.\n\nNo `Idempotency-Key` is required.\n\nRequires the `catalog:write` scope. A key without it answers 403 `permission_denied`, naming the scope and this endpoint.",
        "operationId": "update_product",
        "parameters": [
          {
            "description": "The resource's opaque id, `prod_` followed by 32 hex characters. An id belonging to another winery answers 404, never 403 \u2014 \"not found\" and \"not yours\" are deliberately the same answer.",
            "in": "path",
            "name": "product_id",
            "required": true,
            "schema": {
              "pattern": "^prod_[0-9a-f]{32}$",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "additionalProperties": false,
                "properties": {
                  "abv": {
                    "description": "Alcohol by volume, as a percentage.",
                    "oneOf": [
                      {
                        "maximum": 100,
                        "minimum": 0,
                        "type": "number"
                      },
                      {
                        "type": "null"
                      }
                    ]
                  },
                  "category": {
                    "description": "What kind of thing this is.",
                    "oneOf": [
                      {
                        "enum": [
                          "wine",
                          "merch",
                          "food",
                          "other"
                        ],
                        "type": "string"
                      },
                      {
                        "type": "null"
                      }
                    ]
                  },
                  "group_id": {
                    "description": "The `prgp_\u2026` id of the product group this belongs to, or `null` for none.",
                    "oneOf": [
                      {
                        "pattern": "^pgrp_[0-9a-f]{32}$",
                        "type": "string"
                      },
                      {
                        "type": "null"
                      }
                    ]
                  },
                  "is_active": {
                    "description": "Inactive products stay on historical orders but leave the register and shop.",
                    "oneOf": [
                      {
                        "type": "boolean"
                      },
                      {
                        "type": "null"
                      }
                    ]
                  },
                  "name": {
                    "description": "The product name as it appears on the register and the shop. Required.",
                    "oneOf": [
                      {
                        "maxLength": 180,
                        "type": "string"
                      },
                      {
                        "type": "null"
                      }
                    ]
                  },
                  "price": {
                    "description": "List price, in the same money object `GET /products` returns. Not writable on a bundle, whose price is the live sum of its components.",
                    "oneOf": [
                      {
                        "additionalProperties": false,
                        "properties": {
                          "amount_cents": {
                            "minimum": 0,
                            "type": "integer"
                          },
                          "currency": {
                            "description": "Optional. If sent it must match the winery's own currency \u2014 a mismatch is a 400 rather than a silent conversion.",
                            "type": "string"
                          }
                        },
                        "required": [
                          "amount_cents"
                        ],
                        "type": "object"
                      },
                      {
                        "type": "null"
                      }
                    ]
                  },
                  "region": {
                    "description": "Growing region, free text.",
                    "oneOf": [
                      {
                        "maxLength": 120,
                        "type": "string"
                      },
                      {
                        "type": "null"
                      }
                    ]
                  },
                  "sku": {
                    "description": "Your own stock code. Not unique in this schema and not generated for you.",
                    "oneOf": [
                      {
                        "maxLength": 60,
                        "type": "string"
                      },
                      {
                        "type": "null"
                      }
                    ]
                  },
                  "sold_online": {
                    "description": "Listed in the online shop.",
                    "oneOf": [
                      {
                        "type": "boolean"
                      },
                      {
                        "type": "null"
                      }
                    ]
                  },
                  "taxable": {
                    "description": "Sales tax applies.",
                    "oneOf": [
                      {
                        "type": "boolean"
                      },
                      {
                        "type": "null"
                      }
                    ]
                  },
                  "track_inventory": {
                    "description": "Whether stock is counted for this product. Turning it off does not zero the count.",
                    "oneOf": [
                      {
                        "type": "boolean"
                      },
                      {
                        "type": "null"
                      }
                    ]
                  },
                  "varietal": {
                    "description": "Grape varietal, free text.",
                    "oneOf": [
                      {
                        "maxLength": 120,
                        "type": "string"
                      },
                      {
                        "type": "null"
                      }
                    ]
                  },
                  "vintage": {
                    "description": "Vintage year.",
                    "oneOf": [
                      {
                        "maximum": 2200,
                        "minimum": 1800,
                        "type": "integer"
                      },
                      {
                        "type": "null"
                      }
                    ]
                  },
                  "volume_ml": {
                    "description": "Bottle size in millilitres.",
                    "oneOf": [
                      {
                        "maximum": 100000,
                        "minimum": 1,
                        "type": "integer"
                      },
                      {
                        "type": "null"
                      }
                    ]
                  }
                },
                "type": "object"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Product"
                }
              }
            },
            "description": "Success."
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`invalid_request` \u2014 That request wasn't valid. Check the parameters and try again."
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`authentication_failed` \u2014 Missing or invalid API key. Send it as 'Authorization: Bearer sk_live_\u2026'."
          },
          "402": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`subscription_inactive` \u2014 This winery's SipsPOS subscription isn't active, so its API access is paused."
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`permission_denied` \u2014 This API key doesn't have permission to do that. `feature_not_enabled` \u2014 That feature isn't enabled on this winery's plan."
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`not_found` \u2014 We couldn't find that."
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`rate_limited` \u2014 You've sent too many requests. Wait for the window in Retry-After, then try again."
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`internal` \u2014 An unexpected error occurred. Our team has been notified; if it persists, include this request's id when you contact support."
          }
        },
        "summary": "Update a product",
        "tags": [
          "Catalog"
        ]
      }
    },
    "/sales": {
      "get": {
        "description": "Tasting-room and register transactions, with their line items, payments and refunds. Read-only in v1.\n\nRequires the `sales:read` scope. A key without it answers 403 `permission_denied`, naming the scope and this endpoint.",
        "operationId": "list_sales",
        "parameters": [
          {
            "description": "How many rows to return, 1\u2013200 (default 50). Out-of-range and non-integer values are refused rather than clamped: quietly reading `limit=1000` as 200 teaches a caller its request worked.",
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "default": 50,
              "maximum": 200,
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "description": "The `next_cursor` from the previous page, sent back verbatim. Cursors are signed and bound to the filters and ordering they were minted under \u2014 replaying one against a differently filtered list is refused, not silently resumed part-way down a wider list. There is no page number or offset.",
            "in": "query",
            "name": "cursor",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Only sales rung at the location with this name, matched exactly against the `location_name` on the sale. A name, not an id, because a location is not an addressable resource in v1 \u2014 which also means renaming one in the web UI changes what this filter matches.",
            "in": "query",
            "name": "location_name",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Only sales paid at or after this timestamp, inclusive. Naive values are read in the winery's own time zone.",
            "in": "query",
            "name": "paid_since",
            "required": false,
            "schema": {
              "format": "date-time",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "additionalProperties": false,
                  "properties": {
                    "data": {
                      "items": {
                        "$ref": "#/components/schemas/Sale"
                      },
                      "type": "array"
                    },
                    "has_more": {
                      "description": "Whether another page follows.",
                      "type": "boolean"
                    },
                    "next_cursor": {
                      "description": "Send this back as `?cursor=` for the next page. `null` on the last page \u2014 always present, never absent, so a generated type has no optional field here.",
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "object": {
                      "const": "list",
                      "type": "string"
                    }
                  },
                  "required": [
                    "object",
                    "data",
                    "has_more",
                    "next_cursor"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "Success."
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`invalid_request` \u2014 That request wasn't valid. Check the parameters and try again."
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`authentication_failed` \u2014 Missing or invalid API key. Send it as 'Authorization: Bearer sk_live_\u2026'."
          },
          "402": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`subscription_inactive` \u2014 This winery's SipsPOS subscription isn't active, so its API access is paused."
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`permission_denied` \u2014 This API key doesn't have permission to do that. `feature_not_enabled` \u2014 That feature isn't enabled on this winery's plan."
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`rate_limited` \u2014 You've sent too many requests. Wait for the window in Retry-After, then try again."
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`internal` \u2014 An unexpected error occurred. Our team has been notified; if it persists, include this request's id when you contact support."
          }
        },
        "summary": "List register sales",
        "tags": [
          "Sales"
        ]
      }
    },
    "/sales/{sale_id}": {
      "get": {
        "description": "Requires the `sales:read` scope. A key without it answers 403 `permission_denied`, naming the scope and this endpoint.",
        "operationId": "get_sale",
        "parameters": [
          {
            "description": "The resource's opaque id, `sale_` followed by 32 hex characters. An id belonging to another winery answers 404, never 403 \u2014 \"not found\" and \"not yours\" are deliberately the same answer.",
            "in": "path",
            "name": "sale_id",
            "required": true,
            "schema": {
              "pattern": "^sale_[0-9a-f]{32}$",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Sale"
                }
              }
            },
            "description": "Success."
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`authentication_failed` \u2014 Missing or invalid API key. Send it as 'Authorization: Bearer sk_live_\u2026'."
          },
          "402": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`subscription_inactive` \u2014 This winery's SipsPOS subscription isn't active, so its API access is paused."
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`permission_denied` \u2014 This API key doesn't have permission to do that. `feature_not_enabled` \u2014 That feature isn't enabled on this winery's plan."
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`not_found` \u2014 We couldn't find that."
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`rate_limited` \u2014 You've sent too many requests. Wait for the window in Retry-After, then try again."
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`internal` \u2014 An unexpected error occurred. Our team has been notified; if it persists, include this request's id when you contact support."
          }
        },
        "summary": "Read one register sale",
        "tags": [
          "Sales"
        ]
      }
    },
    "/shipping/quote": {
      "post": {
        "description": "Ask before you sell: may this winery ship this many bottles to this destination today, and what would it cost? A destination the winery may not ship to answers 200 with `allowed: false` and a reason \u2014 a refusal is an answer to this question, not a failure of it, so it is not an error status.\n\nRequires the `orders:read` scope. A key without it answers 403 `permission_denied`, naming the scope and this endpoint.",
        "operationId": "shipping_quote",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "additionalProperties": false,
                "properties": {
                  "bottles": {
                    "description": "How many bottles. A whole number: `6.5` is refused, not rounded. Required.",
                    "minimum": 0,
                    "type": "integer"
                  },
                  "country": {
                    "description": "The destination country. Defaults to the winery's own.",
                    "type": "string"
                  },
                  "customer_id": {
                    "description": "An existing customer to price for. Another winery's customer id is a 404.",
                    "pattern": "^cus_[0-9a-f]{32}$",
                    "type": "string"
                  },
                  "email": {
                    "description": "The buyer's email, when you have one. Used only for the compliance check.",
                    "format": "email",
                    "type": "string"
                  },
                  "state": {
                    "description": "The destination subdivision \u2014 a province or a state code, whichever `GET /market` says this winery uses. Required.",
                    "type": "string"
                  },
                  "subtotal_cents": {
                    "description": "The cart subtotal after discounts, in cents. Required \u2014 free-shipping thresholds are priced off it, and a quote without it would undercharge against the winery's own shop.",
                    "minimum": 0,
                    "type": "integer"
                  }
                },
                "required": [
                  "state",
                  "bottles",
                  "subtotal_cents"
                ],
                "type": "object"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ShippingQuote"
                }
              }
            },
            "description": "Success."
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`invalid_request` \u2014 That request wasn't valid. Check the parameters and try again."
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`authentication_failed` \u2014 Missing or invalid API key. Send it as 'Authorization: Bearer sk_live_\u2026'."
          },
          "402": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`subscription_inactive` \u2014 This winery's SipsPOS subscription isn't active, so its API access is paused."
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`permission_denied` \u2014 This API key doesn't have permission to do that. `feature_not_enabled` \u2014 That feature isn't enabled on this winery's plan."
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`rate_limited` \u2014 You've sent too many requests. Wait for the window in Retry-After, then try again."
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`internal` \u2014 An unexpected error occurred. Our team has been notified; if it persists, include this request's id when you contact support."
          }
        },
        "summary": "Price a shipment, and check it is allowed",
        "tags": [
          "Orders"
        ]
      }
    }
  },
  "security": [
    {
      "apiKey": []
    }
  ],
  "servers": [
    {
      "description": "The winery's own host.",
      "url": "https://{host}/api/v1",
      "variables": {
        "host": {
          "default": "winery.sipspos.com",
          "description": "The winery's SipsPOS host \u2014 `<slug>.sipspos.com`, or its custom domain. The winery's admin can read it off the address bar; it is also printed in the in-app API guide."
        }
      }
    }
  ],
  "tags": [
    {
      "description": "The winery's own configuration \u2014 read this first.",
      "name": "Market"
    },
    {
      "description": "Products and the groups they are organised into.",
      "name": "Catalog"
    },
    {
      "description": "Customer records, including the club status on each.",
      "name": "Customers"
    },
    {
      "description": "Tiers, members, releases and the allocations in them.",
      "name": "Club"
    },
    {
      "description": "Online-shop orders, and the shipping quote behind a checkout.",
      "name": "Orders"
    },
    {
      "description": "Register (tasting-room) transactions.",
      "name": "Sales"
    },
    {
      "description": "Bottles on hand and the movement ledger behind them.",
      "name": "Inventory"
    },
    {
      "description": "The pack queue: what ships, where, and what has gone.",
      "name": "Fulfillment"
    },
    {
      "description": "Bookable experiences, live availability, and the reservations against them.",
      "name": "Bookings"
    }
  ]
}
