{
  "openapi": "3.1.0",
  "info": {
    "title": "VacoTours Supplier API",
    "version": "1.0.0",
    "summary": "Publish inventory, push availability and rates, read your bookings.",
    "description": "For operators who would rather push from their own system than work in the panel. Everything the panel does is available here, because both run through the same code.\n\n## Authentication\n\n```http\nAuthorization: Bearer vt_live_xxxxx.sk_xxxxx\n```\n\nCredentials are scoped. A key issued for inventory management carries `products:write` and `availability:write`; one issued for reporting carries only `bookings:read`.\n\n## Import only\n\nAn operator's key imports. Every product you create here is added to your own products, exactly as if you had built it in the panel, and is sold from there. The key exports nothing, your own products included: the OCTO and Distribution v2 catalogues refuse it. On OCTO it can still read the bookings of your own inventory, cancel them and redeem vouchers at the gate.\n\n## Product archetypes\n\nCreating a product starts by choosing one of 37 archetypes. Each one carries its own defaults and its own setup flow, which `GET /product-types` returns in full, including the wizard steps the panel renders.\n\nPick the archetype that matches how the product is actually sold, not the one whose name sounds closest. A timed-entry ticket and a general admission ticket look similar and behave completely differently.\n\n## Rentals and stays\n\nA vehicle or equipment rental, a villa and travel cover are booked from one date to another. Their options come with a `range` (day, night or hour); set it with `PUT /products/{uuid}/options/{optionUuid}/range`. Rates are then per day, night or hour: push a base rate, and a cheaper one for longer bookings with `min_length`. If several options or products share the same cars or boats, add them as resources so none is out twice at once.\n\n## Priced on request\n\nTailor-made and incentive archetypes have no calendar or rates: set `quote_settings` in their details (reply time, group sizes, what you need to know). Requests arrive at `GET /quote-requests` and by email; answer with your price for the whole group.\n\n## Money\n\nSend prices as decimal amounts in USD. The field is called `net`, but it is your price: what a traveller and a trade buyer both pay. Nothing is added to it. Our commission comes out of it as a percentage, set by the promotion level you choose for each product in the operator panel, and what you are owed for a booking is that price less the commission.\n\n## A typical first integration\n\n1. `GET /product-types` and choose an archetype.\n2. `POST /products` with your content, options, units and rates.\n3. `POST /products/{uuid}/options/{optionUuid}/availability` for your operating calendar.\n4. If several options share one boat, vehicle or guide, `POST /resources` and attach it to each option, so it is never sold twice at the same time.\n5. `GET /bookings` to reconcile.",
    "contact": {
      "name": "VacoTours operator support",
      "email": "hello@vacotours.com"
    }
  },
  "servers": [
    {
      "url": "https://api.vacotours.com/api/supplier",
      "description": "Production and sandbox (use a vt_test_ key for sandbox)"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "tags": [
    {
      "name": "Reference",
      "description": "Archetypes and their setup flows."
    },
    {
      "name": "Products",
      "description": "Create and maintain inventory."
    },
    {
      "name": "Resources",
      "description": "Boats, vehicles, equipment and guides that several options share."
    },
    {
      "name": "Availability and rates",
      "description": "Push your calendar and your prices."
    },
    {
      "name": "Bookings",
      "description": "What has been sold."
    },
    {
      "name": "Quotes",
      "description": "Requests for a price on products you sell on request."
    }
  ],
  "paths": {
    "/product-types": {
      "get": {
        "tags": [
          "Reference"
        ],
        "summary": "List the 37 product archetypes",
        "description": "Each entry carries its defaults, its capability flags and the ordered wizard steps that archetype collects.",
        "operationId": "listArchetypes",
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Archetype"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "The request was rejected.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credential.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded for this credential.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/products": {
      "get": {
        "tags": [
          "Products"
        ],
        "summary": "List your products",
        "operationId": "listSupplierProducts",
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "draft",
                "pending_review",
                "published",
                "archived"
              ]
            }
          },
          {
            "name": "per_page",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 25
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/SupplierProduct"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "The request was rejected.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credential.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded for this credential.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "Products"
        ],
        "summary": "Create a product",
        "description": "Creates a product with its options, units, rates and an initial availability calendar in one call.\n\nAnything you leave out is filled from the archetype's defaults, so a minimal payload still produces a coherent product.",
        "operationId": "createSupplierProduct",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateProductRequest"
              },
              "example": {
                "type": "class_workshop",
                "title": "Istanbul Home Cooking: A Half Day in a Kadikoy Kitchen",
                "status": "published",
                "city": "Istanbul",
                "short_description": "Shop the Kadikoy market with your host, then cook six dishes in a family kitchen.",
                "duration_hours": 5,
                "max_pax": 8,
                "category_slugs": [
                  "cooking-classes",
                  "food-drink"
                ],
                "options": [
                  {
                    "title": "Morning class with market tour",
                    "start_times": [
                      "09:30"
                    ],
                    "capacity": 8,
                    "cutoff_amount": 48,
                    "units": [
                      {
                        "type": "adult",
                        "label": "Adult",
                        "min_age": 13,
                        "max_age": 99
                      },
                      {
                        "type": "child",
                        "label": "Child (6-12)",
                        "min_age": 6,
                        "max_age": 12
                      }
                    ],
                    "prices": [
                      {
                        "unit_type": "adult",
                        "net": 48.0,
                        "rrp": 75.0
                      },
                      {
                        "unit_type": "child",
                        "net": 26.0,
                        "rrp": 42.0
                      }
                    ]
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/SupplierProduct"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "The request was rejected.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credential.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded for this credential.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/products/{uuid}": {
      "get": {
        "tags": [
          "Products"
        ],
        "summary": "Get one of your products",
        "operationId": "getSupplierProduct",
        "parameters": [
          {
            "name": "uuid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/SupplierProduct"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "The request was rejected.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credential.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded for this credential.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "patch": {
        "tags": [
          "Products"
        ],
        "summary": "Update a product",
        "operationId": "updateSupplierProduct",
        "parameters": [
          {
            "name": "uuid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "title": {
                    "type": "string"
                  },
                  "status": {
                    "type": "string",
                    "enum": [
                      "draft",
                      "pending_review",
                      "published",
                      "archived"
                    ]
                  },
                  "short_description": {
                    "type": "string"
                  },
                  "description": {
                    "type": "string"
                  },
                  "highlights": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/SupplierProduct"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "The request was rejected.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credential.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded for this credential.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/products/{uuid}/options/{optionUuid}/availability": {
      "post": {
        "tags": [
          "Availability and rates"
        ],
        "summary": "Push an availability calendar",
        "description": "Declares when an option operates, and materialises the bookable slots.\n\nSafe to re-run. Capacity is never reduced below what has already been sold, so a corrected calendar cannot cancel existing bookings by accident.",
        "operationId": "pushAvailability",
        "parameters": [
          {
            "name": "uuid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "optionUuid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AvailabilityPush"
              },
              "example": {
                "date_from": "2026-10-01",
                "date_to": "2027-03-31",
                "weekday_mask": 127,
                "start_times": [
                  "09:30",
                  "14:00"
                ],
                "capacity": 12
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "The request was rejected.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credential.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded for this credential.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/products/{uuid}/options/{optionUuid}/pricing": {
      "post": {
        "tags": [
          "Availability and rates"
        ],
        "summary": "Push prices",
        "description": "Rates you push take priority over anything configured earlier for the same dates, so a seasonal correction does not require deleting the old rows.",
        "operationId": "pushPricing",
        "parameters": [
          {
            "name": "uuid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "optionUuid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PricingPush"
              },
              "example": {
                "prices": [
                  {
                    "unit_type": "adult",
                    "net": 52.0,
                    "rrp": 82.0,
                    "min_selling": 68.0,
                    "date_from": "2026-12-20",
                    "date_to": "2027-01-05"
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "The request was rejected.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credential.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded for this credential.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/bookings": {
      "get": {
        "tags": [
          "Bookings"
        ],
        "summary": "List bookings on your inventory",
        "description": "Shows what you are owed per booking: your price less the commission of the promotion level the product sold on.",
        "operationId": "listSupplierBookings",
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "date_from",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "date_to",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "The request was rejected.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid credential.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded for this credential.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/products/{uuid}/details": {
      "put": {
        "tags": [
          "Products"
        ],
        "summary": "Set a detail section",
        "operationId": "setSupplierProductDetails",
        "description": "What this kind of product says about itself: a rental's deposit and driver age, an eSIM's countries and data, a pass's attractions, a venue, a transfer's route. GET /product-types lists, per type, the sections it asks for and their fields as detailSchema. A section with level option needs the option's uuid. Values are merged unless replace is true. The same sections can be sent on create, as details on the product and details on each option.",
        "parameters": [
          {
            "name": "uuid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "section",
                  "values"
                ],
                "properties": {
                  "section": {
                    "type": "string",
                    "example": "rental_terms"
                  },
                  "values": {
                    "type": "object",
                    "example": {
                      "deposit": 300,
                      "min_age": 21,
                      "licence_required": true,
                      "fuel_policy": "full_to_full"
                    }
                  },
                  "option": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "replace": {
                    "type": "boolean"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Saved",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "section": {
                          "type": "string"
                        },
                        "values": {
                          "type": "object"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "422": {
            "description": "A field that section does not have, or a value it cannot take. The message names it."
          }
        }
      }
    },
    "/products/{uuid}/options/{optionUuid}/questions": {
      "put": {
        "tags": [
          "Products"
        ],
        "summary": "Replace checkout questions",
        "operationId": "setSupplierQuestions",
        "description": "The whole list, in order. Send a question's uuid to keep it, so answers already given stay linked to it; a question left out is removed. A price makes it a paid extra (USD, per answer); per_day charges it per day of a rental. type is text, textarea, number, date, time, datetime, phone, email, country, checkbox, select, select_multiple or file; choice types need at least two choices.",
        "parameters": [
          {
            "name": "uuid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "optionUuid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "questions"
                ],
                "properties": {
                  "questions": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "required": [
                        "label"
                      ],
                      "properties": {
                        "uuid": {
                          "type": "string"
                        },
                        "label": {
                          "type": "string"
                        },
                        "description": {
                          "type": "string"
                        },
                        "type": {
                          "type": "string"
                        },
                        "per_traveller": {
                          "type": "boolean"
                        },
                        "required": {
                          "type": "boolean"
                        },
                        "choices": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          }
                        },
                        "price": {
                          "type": "number"
                        },
                        "per_day": {
                          "type": "boolean"
                        },
                        "format": {
                          "type": "string"
                        },
                        "min_number": {
                          "type": "integer"
                        },
                        "max_number": {
                          "type": "integer"
                        },
                        "valid_from": {
                          "type": "string",
                          "format": "date"
                        },
                        "valid_to": {
                          "type": "string",
                          "format": "date"
                        }
                      }
                    }
                  }
                }
              },
              "example": {
                "questions": [
                  {
                    "label": "Hotel name",
                    "type": "text",
                    "required": true
                  },
                  {
                    "label": "Child seat",
                    "type": "checkbox",
                    "price": 5,
                    "per_day": true
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The questions as saved"
          },
          "422": {
            "description": "A question that cannot be asked as sent. The message says which."
          }
        }
      }
    },
    "/products/{uuid}/options/{optionUuid}/itinerary": {
      "put": {
        "tags": [
          "Products"
        ],
        "summary": "Replace the itinerary",
        "operationId": "setSupplierItinerary",
        "description": "The stops in order. type is stop, meeting, pickup, meal, overnight, activity, transfer, free_time or end. Latitude and longitude put the stop on the map for travellers and partners.",
        "parameters": [
          {
            "name": "uuid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "optionUuid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "stops"
                ],
                "properties": {
                  "stops": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "required": [
                        "name"
                      ],
                      "properties": {
                        "day": {
                          "type": "integer"
                        },
                        "name": {
                          "type": "string"
                        },
                        "type": {
                          "type": "string"
                        },
                        "description": {
                          "type": "string"
                        },
                        "address": {
                          "type": "string"
                        },
                        "latitude": {
                          "type": "number"
                        },
                        "longitude": {
                          "type": "number"
                        },
                        "duration_minutes": {
                          "type": "integer"
                        }
                      }
                    }
                  }
                }
              },
              "example": {
                "stops": [
                  {
                    "day": 1,
                    "name": "Freedom Square",
                    "type": "meeting",
                    "latitude": 41.6934,
                    "longitude": 44.8015
                  },
                  {
                    "day": 1,
                    "name": "Narikala Fortress",
                    "duration_minutes": 45
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The itinerary as saved"
          },
          "422": {
            "description": "A stop without a name, or off the map."
          }
        }
      }
    },
    "/products/{uuid}/resources": {
      "get": {
        "tags": [
          "Resources"
        ],
        "summary": "List resources",
        "operationId": "listSupplierResources",
        "description": "Your resources are shared by all your products. Each option names what it takes from them; a booking holds that for its whole window plus the turnaround, so a morning charter and a full day on the same yacht cannot both be sold. Availability reads, on every channel, leave out departures whose resources are taken.",
        "parameters": [
          {
            "name": "uuid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Resources and what each option uses.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/ResourcePage"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/products/{uuid}/options/{optionUuid}/resources": {
      "put": {
        "tags": [
          "Resources"
        ],
        "summary": "Set what an option uses",
        "operationId": "setSupplierOptionResources",
        "description": "The whole list for this option; an empty list detaches everything.",
        "parameters": [
          {
            "name": "uuid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "optionUuid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "resources"
                ],
                "properties": {
                  "resources": {
                    "type": "array",
                    "maxItems": 20,
                    "items": {
                      "$ref": "#/components/schemas/ResourceNeed"
                    }
                  }
                }
              },
              "example": {
                "resources": [
                  {
                    "resource": "0b8f1c9e-2f3a-4c55-9d7e-1a2b3c4d5e6f",
                    "quantity": 1,
                    "per": "booking"
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Saved.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/ResourcePage"
                    }
                  }
                }
              }
            }
          },
          "422": {
            "description": "A resource that is not yours, or a bad quantity.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/resources": {
      "post": {
        "tags": [
          "Resources"
        ],
        "summary": "Create or change a resource",
        "operationId": "saveSupplierResource",
        "description": "Without uuid, creates one. With uuid, changes only the fields sent.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "uuid": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "name": {
                    "type": "string",
                    "maxLength": 160
                  },
                  "type": {
                    "type": "string",
                    "enum": [
                      "vehicle",
                      "vessel",
                      "guide",
                      "equipment",
                      "room",
                      "driver",
                      "other"
                    ]
                  },
                  "capacity": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 10000
                  },
                  "buffer_minutes": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 10080
                  },
                  "enabled": {
                    "type": "boolean",
                    "description": "Switched off, it stops being required, and so stops limiting sales."
                  }
                }
              },
              "example": {
                "name": "Sea Breeze, 52 ft yacht",
                "type": "vessel",
                "capacity": 1,
                "buffer_minutes": 60
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "uuid": {
                          "type": "string"
                        },
                        "name": {
                          "type": "string"
                        },
                        "capacity": {
                          "type": "integer"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "200": {
            "description": "Changed."
          }
        }
      }
    },
    "/products/{uuid}/options/{optionUuid}/range": {
      "put": {
        "tags": [
          "Products"
        ],
        "summary": "Set how an option is booked",
        "operationId": "setSupplierOptionRange",
        "description": "Single date, or from a pickup date to a return: a car by the day, a villa by the night, a jet ski by the hour. With a range, travellers and partners choose a return, every date in between must be open and holds capacity, rates are per day, night or hour (or flat), and questions with per_day are charged for each day.",
        "parameters": [
          {
            "name": "uuid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "optionUuid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/OptionRange"
              },
              "example": {
                "basis": "day",
                "min": 1,
                "max": 28
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Saved; the terms partners now see.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "range": {
                          "type": [
                            "object",
                            "null"
                          ]
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "422": {
            "description": "Not a basis, or a bad length.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/quote-requests": {
      "get": {
        "tags": [
          "Quotes"
        ],
        "summary": "List quote requests",
        "operationId": "listSupplierQuotes",
        "description": "Requests for a price on your products sold on request (tailor-made trips, incentives), open ones first. Needs bookings:read.",
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Requests.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/OperatorQuote"
                      }
                    },
                    "meta": {
                      "type": "object",
                      "properties": {
                        "open": {
                          "type": "integer"
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/quote-requests/{uuid}": {
      "get": {
        "tags": [
          "Quotes"
        ],
        "summary": "Get a quote request",
        "operationId": "getSupplierQuote",
        "parameters": [
          {
            "name": "uuid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The request.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/OperatorQuote"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/quote-requests/{uuid}/answer": {
      "post": {
        "tags": [
          "Quotes"
        ],
        "summary": "Send a price",
        "operationId": "answerSupplierQuote",
        "description": "Your price for the whole group. It is what the asker pays, and they book it from the link we email them; our commission comes out of it at the product's promotion level. Answering again replaces the figure. Needs availability:write.",
        "parameters": [
          {
            "name": "uuid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "net"
                ],
                "properties": {
                  "net": {
                    "type": "number",
                    "description": "Your price for the whole group, USD."
                  },
                  "message": {
                    "type": "string",
                    "description": "What it includes. The asker sees this."
                  },
                  "valid_until": {
                    "type": "string",
                    "format": "date",
                    "description": "Defaults to a week."
                  }
                }
              },
              "example": {
                "net": 7200,
                "message": "Seven nights, private driver, two wineries.",
                "valid_until": "2026-10-15"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Sent.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/OperatorQuote"
                    }
                  }
                }
              }
            }
          },
          "422": {
            "description": "Not open, or no price.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/quote-requests/{uuid}/decline": {
      "post": {
        "tags": [
          "Quotes"
        ],
        "summary": "Decline",
        "operationId": "declineSupplierQuote",
        "parameters": [
          {
            "name": "uuid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "reason": {
                    "type": "string",
                    "description": "The asker sees this."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Declined.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/OperatorQuote"
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "Your token, formatted `{key}.{secret}`."
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "required": [
          "error",
          "errorMessage"
        ],
        "properties": {
          "error": {
            "type": "string",
            "description": "Machine-readable error identifier.",
            "examples": [
              "UNAVAILABLE"
            ]
          },
          "errorMessage": {
            "type": "string"
          },
          "productId": {
            "type": "string"
          },
          "optionId": {
            "type": "string"
          },
          "availabilityId": {
            "type": "string"
          },
          "uuid": {
            "type": "string"
          }
        }
      },
      "Archetype": {
        "type": "object",
        "properties": {
          "code": {
            "type": "string",
            "examples": [
              "timed_entry_ticket"
            ]
          },
          "name": {
            "type": "string"
          },
          "family": {
            "type": "string",
            "examples": [
              "ticket"
            ]
          },
          "description": {
            "type": "string"
          },
          "defaults": {
            "type": "object",
            "description": "What a product of this type starts with. pricingChoices lists the bases it may be priced on, its default pricingPer first: a transfer or a car with a driver offers BOOKING (per vehicle) and UNIT (per person)."
          },
          "capabilities": {
            "type": "object"
          },
          "wizardSteps": {
            "type": "array",
            "items": {
              "type": "object"
            }
          }
        }
      },
      "SupplierProduct": {
        "type": "object",
        "properties": {
          "uuid": {
            "type": "string",
            "format": "uuid"
          },
          "slug": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "type": {
            "type": "string"
          },
          "status": {
            "type": "string"
          },
          "availabilityType": {
            "type": "string",
            "enum": [
              "START_TIME",
              "OPENING_HOURS"
            ]
          },
          "pricingPer": {
            "type": "string",
            "enum": [
              "UNIT",
              "BOOKING"
            ]
          },
          "fromPrice": {
            "type": [
              "number",
              "null"
            ],
            "description": "The lowest price a buyer pays for one ticket of this product, as listing cards show it. Your own price, unless a minimum selling price raises it."
          },
          "fromNet": {
            "type": [
              "number",
              "null"
            ],
            "description": "The lowest price you have set on this product's tickets. Our commission comes out of it."
          },
          "optionCount": {
            "type": "integer"
          },
          "bookingCount": {
            "type": "integer"
          }
        }
      },
      "CreateProductRequest": {
        "type": "object",
        "required": [
          "type",
          "title",
          "options"
        ],
        "properties": {
          "type": {
            "type": "string",
            "description": "An archetype code from GET /product-types."
          },
          "title": {
            "type": "string"
          },
          "internal_name": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "draft",
              "pending_review",
              "published"
            ]
          },
          "pricing_per": {
            "type": "string",
            "enum": [
              "BOOKING",
              "UNIT"
            ],
            "description": "One of the type's defaults.pricingChoices. A transfer or a car with a driver is priced per vehicle (BOOKING) unless you send UNIT, which prices it per person: its tickets are then adult and child unless the option names its own. Left out, the type's own basis; one the type does not offer is refused."
          },
          "city": {
            "type": "string"
          },
          "address": {
            "type": "string"
          },
          "short_description": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "highlights": {
            "type": "string",
            "description": "One per line."
          },
          "price_includes": {
            "type": "string"
          },
          "know_before_you_go": {
            "type": "string"
          },
          "meta_title": {
            "type": "string"
          },
          "meta_description": {
            "type": "string"
          },
          "faq": {
            "type": "array",
            "items": {
              "type": "object"
            }
          },
          "category_slugs": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "duration_days": {
            "type": "integer"
          },
          "duration_hours": {
            "type": "integer"
          },
          "min_pax": {
            "type": "integer"
          },
          "max_pax": {
            "type": "integer"
          },
          "media": {
            "type": "array",
            "items": {
              "type": "object"
            }
          },
          "options": {
            "type": "array",
            "minItems": 1,
            "items": {
              "type": "object",
              "required": [
                "title",
                "prices"
              ],
              "properties": {
                "title": {
                  "type": "string"
                },
                "description": {
                  "type": "string"
                },
                "start_times": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  }
                },
                "opening_hours": {
                  "type": "array",
                  "items": {
                    "type": "object"
                  }
                },
                "capacity": {
                  "type": [
                    "integer",
                    "null"
                  ],
                  "description": "Null means freesale."
                },
                "cutoff_amount": {
                  "type": "integer"
                },
                "units": {
                  "type": "array",
                  "items": {
                    "type": "object"
                  }
                },
                "prices": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "properties": {
                      "unit_type": {
                        "type": "string"
                      },
                      "net": {
                        "type": "number",
                        "description": "Your price, in USD: what a traveller and a trade buyer both pay. Our commission comes out of it."
                      },
                      "rrp": {
                        "type": "number"
                      },
                      "date_from": {
                        "type": "string",
                        "format": "date"
                      },
                      "date_to": {
                        "type": "string",
                        "format": "date"
                      },
                      "weekday_mask": {
                        "type": "integer"
                      },
                      "min_quantity": {
                        "type": "integer"
                      },
                      "min_length": {
                        "type": "integer",
                        "minimum": 1,
                        "description": "Rentals and stays: this rate applies to bookings of at least this many days, nights or hours. A week can cost less a day than a weekend."
                      }
                    },
                    "required": [
                      "net"
                    ]
                  }
                },
                "cancellation_policies": {
                  "type": "array",
                  "items": {
                    "type": "object"
                  }
                },
                "questions": {
                  "type": "array",
                  "items": {
                    "type": "object"
                  }
                },
                "itinerary": {
                  "type": "array",
                  "items": {
                    "type": "object"
                  }
                },
                "range": {
                  "$ref": "#/components/schemas/OptionRange"
                }
              }
            }
          }
        }
      },
      "AvailabilityPush": {
        "type": "object",
        "required": [
          "date_from",
          "date_to"
        ],
        "properties": {
          "date_from": {
            "type": "string",
            "format": "date"
          },
          "date_to": {
            "type": "string",
            "format": "date"
          },
          "weekday_mask": {
            "type": "integer",
            "minimum": 1,
            "maximum": 127,
            "description": "Bit mask, Monday = 1 through Sunday = 64. 127 is every day."
          },
          "start_times": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "opening_hours": {
            "type": "array",
            "items": {
              "type": "object"
            }
          },
          "capacity": {
            "type": [
              "integer",
              "null"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "AVAILABLE",
              "CLOSED"
            ]
          }
        }
      },
      "PricingPush": {
        "type": "object",
        "required": [
          "prices"
        ],
        "properties": {
          "prices": {
            "type": "array",
            "minItems": 1,
            "items": {
              "type": "object",
              "required": [
                "net",
                "date_from",
                "date_to"
              ],
              "properties": {
                "unit_type": {
                  "type": "string",
                  "description": "Omit to price the whole booking."
                },
                "net": {
                  "type": "number",
                  "description": "Your price, in USD: what a traveller and a trade buyer both pay. Our commission comes out of it."
                },
                "rrp": {
                  "type": "number"
                },
                "min_selling": {
                  "type": "number"
                },
                "date_from": {
                  "type": "string",
                  "format": "date"
                },
                "date_to": {
                  "type": "string",
                  "format": "date"
                },
                "weekday_mask": {
                  "type": "integer"
                },
                "min_quantity": {
                  "type": "integer"
                },
                "min_length": {
                  "type": "integer",
                  "minimum": 1,
                  "description": "Rentals and stays: the rate from this length on. Omit for the base rate."
                }
              }
            }
          }
        }
      },
      "ResourcePage": {
        "type": "object",
        "description": "Your resources, with what each option of this product takes from them.",
        "properties": {
          "types": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "example": [
              "vehicle",
              "vessel",
              "guide",
              "equipment",
              "room",
              "driver",
              "other"
            ]
          },
          "resources": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "uuid": {
                  "type": "string",
                  "format": "uuid"
                },
                "type": {
                  "type": "string"
                },
                "name": {
                  "type": "string"
                },
                "reference": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "capacity": {
                  "type": "integer",
                  "description": "How many identical ones you have: 1 yacht, 12 scooters of one model."
                },
                "bufferMinutes": {
                  "type": "integer",
                  "description": "Turnaround kept free after each booking: cleaning, refuelling, a rest."
                },
                "notes": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "isEnabled": {
                  "type": "boolean"
                },
                "upcoming": {
                  "type": "integer",
                  "description": "Live bookings still to come that hold it."
                }
              }
            }
          },
          "options": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "uuid": {
                  "type": "string",
                  "format": "uuid"
                },
                "title": {
                  "type": "string"
                },
                "resources": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/ResourceNeed"
                  }
                }
              }
            }
          }
        }
      },
      "ResourceNeed": {
        "type": "object",
        "required": [
          "resource"
        ],
        "properties": {
          "resource": {
            "type": "string",
            "format": "uuid",
            "description": "The resource uuid."
          },
          "quantity": {
            "type": "integer",
            "minimum": 1,
            "default": 1
          },
          "per": {
            "type": "string",
            "enum": [
              "booking",
              "unit"
            ],
            "default": "booking",
            "description": "booking: one party takes this many. unit: this many per ticket, for example one scooter per rider."
          }
        }
      },
      "OptionRange": {
        "type": "object",
        "description": "How the option runs from one date to another. Rental and villa archetypes start as day and night; send basis none for a single date.",
        "properties": {
          "basis": {
            "type": "string",
            "enum": [
              "day",
              "night",
              "hour",
              "none"
            ],
            "description": "day: 24 hour periods from pickup to return. night: check-in to check-out. hour: started hours."
          },
          "min": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 1,
            "description": "Shortest booking, in the basis."
          },
          "max": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 1,
            "description": "Longest booking, in the basis."
          },
          "flat": {
            "type": "boolean",
            "description": "One rate for the whole period, chosen by its length (cover sold by the week), instead of a rate per day, night or hour."
          }
        },
        "required": [
          "basis"
        ]
      },
      "OperatorQuote": {
        "type": "object",
        "properties": {
          "uuid": {
            "type": "string"
          },
          "reference": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "open",
              "quoted",
              "accepted",
              "declined",
              "withdrawn",
              "expired"
            ]
          },
          "product": {
            "type": "object"
          },
          "travelDate": {
            "type": "string",
            "format": "date"
          },
          "endDate": {
            "type": "string",
            "format": "date"
          },
          "datesFlexible": {
            "type": "boolean"
          },
          "groupSize": {
            "type": "integer"
          },
          "budget": {
            "type": "integer",
            "description": "USD minor units, whole group."
          },
          "message": {
            "type": "string"
          },
          "answers": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "question": {
                  "type": "string"
                },
                "answer": {
                  "type": "string"
                }
              }
            }
          },
          "contact": {
            "type": "object"
          },
          "fromTrade": {
            "type": "boolean"
          },
          "net": {
            "type": "integer",
            "description": "Your answer, USD minor units."
          },
          "quoteMessage": {
            "type": "string"
          },
          "validUntil": {
            "type": "string",
            "format": "date-time"
          },
          "declineReason": {
            "type": "string"
          },
          "bookingId": {
            "type": "string"
          }
        }
      }
    }
  }
}
