{
  "openapi": "3.1.0",
  "info": {
    "title": "Experience Bamfield — public API",
    "version": "2026-10-08",
    "summary": "Read the catalogue, check dates, price a basket, and hand the guest a link that fills their cart.",
    "description": "No key is needed for anything in this document, with one exception: `/api/assistant/trip/{key}` reads one guest's trip, and takes the key that guest made for their assistant. Prices are in Canadian cents (CAD).\n\n**How an assistant books a trip for a guest, with no key:**\n1. `GET /api/public/experience-inventory` — what can be booked, with ids, prices and how many each sleeps or seats.\n2. `GET /api/public/experience-inventory/day-availability` — which days are open for one product, up to three years ahead.\n3. `GET /api/public/cc_portals/bamfield/source-inventory-pricing` — every cottage's price for the guest's exact dates.\n4. `POST /api/public/cc_portals/bamfield/cart-quote` — the whole basket: each line's price and whether it is free for those dates in that number, then the cleaning fee, the taxes, the refundable deposits and `totals.dueAtCheckoutCents`, the amount the card is charged. It saves nothing.\n5. Build the link: `https://experiencebamfield.com/book?l=PRODUCT~FROM~TO~HOWMANY~GUESTS` (repeat `&l=…` for each line; moorage adds `~BOATFEET`; a kayak's FROM and TO may carry a time, `2027-07-12T09:00`).\n6. `GET /api/public/cc_portals/bamfield/cart-link?l=…` (the same `l=` parts) — check your own link before you hand it over: the lines it opens with, what is left out and why, and the totals. It creates no cart.\n7. Give the guest the link. Opening it adds those lines to their cart (to whatever is already in it). They review it and pay by card; the booking is confirmed the moment they pay.\n\n**A price that could not be read is said so.** A line or a cottage marked `retryable: true` (with a `Retry-After` header) is not a refusal: ask again in a few seconds. Never quote a guest from an answer whose `totals.complete` is false.\n\n**Fishing charters** are priced here (`tripType`: half-day or full-day; one trip is one day). A charter goes into the same cart as the rest of the trip, but from its own page, which asks who is fishing: https://experiencebamfield.com/p/harbourside-fishing-charters. A cart link does not carry one: give the guest that page as well.\n\n**Camping permits** for the Deer Group are applied for at https://experiencebamfield.com/permits and are not a line in the cart; their fee and how long they take are in `campingPermits`. Tell a guest camping there to apply as soon as the dates are known.\n\nKayak prices: a kayak is charged by the time between pick-up and return, not by the calendar date. The lengths are 4 hours, 8 hours, 24 hours, 2 days, 3 days, 4 days and a week; the price is the shortest of those that covers the rental (6 hours is charged as 8; 5 or 6 days is charged as the week), and past a week every started week is a whole week. Pick-up is from 07:00 and the boat is back by 20:00. Send `startTime` and `endTime` to quoteBasket for an exact price; with dates only, the same date twice is the 8-hour day and dates N apart are N days. Each boat's whole rate table is in pricingForDates under `kayakProducts[].rateTiers`, and the answer to quoteBasket names the rate it charged (`rate.summary` — e.g. \"2 × Weekly Rate (5 Days or More)\" for eight days; `rate.periods` is how many of `rate.label` were charged). Every boat also carries a refundable damage deposit (`depositCents`), charged at checkout.\n\nThe assistant never pays and never holds anything. Nothing is reserved until the guest pays.\n\n**MCP.** An assistant that speaks the Model Context Protocol can connect to `https://experiencebamfield.com/mcp` (streamable HTTP, no key) and use everything in this document as tools. It is read-only.\n\n**Partners** (travel agents, planners, other booking systems) can hold and book directly, including group bookings with a deposit, through the partner API at `/api/v1`. It needs a key and has a test environment that charges no real cards and books nothing real. Its documentation is at `/api/v1/docs`. Ask for a key at bamfieldadventures@gmail.com.",
    "contact": {
      "name": "Experience Bamfield",
      "email": "bamfieldadventures@gmail.com",
      "url": "https://experiencebamfield.com/c/bamfield/contact"
    }
  },
  "servers": [
    {
      "url": "https://experiencebamfield.com"
    }
  ],
  "externalDocs": {
    "description": "Partner API (key required): holds, bookings, group deposits, a test environment",
    "url": "https://experiencebamfield.com/api/v1/docs"
  },
  "paths": {
    "/api/public/experience-inventory": {
      "get": {
        "operationId": "listInventory",
        "summary": "Everything that can be booked",
        "description": "Cottages, kayaks and canoes, parking, moorage and fishing charters, with each product's id, price, deposit, how many are in the fleet (`operational.stockCount`), and how many a cottage sleeps (`detail.maxOccupancy`). A cottage's price depends on the dates: see source-inventory-pricing. A boat's `pricing.publicPriceCents` is its 24-hour rate (`pricing.publicPriceBasis`); `pricing.fromPriceCents` is its shortest rental. Photographs are at `/api/public/experience-media/object/…` and may be fetched.",
        "parameters": [
          {
            "name": "providerSlug",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Only one business's products, e.g. woods-end-landing."
          }
        ],
        "responses": {
          "200": {
            "description": "data.entries[] — one per product. `id` is what every other call and the cart link take.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "data": {
                      "type": "object",
                      "properties": {
                        "entries": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "id": {
                                "type": "string",
                                "description": "The product id every other call and the cart link take."
                              },
                              "providerSlug": {
                                "type": "string",
                                "description": "The business."
                              },
                              "providerLabel": {
                                "type": "string"
                              },
                              "category": {
                                "type": "string",
                                "enum": [
                                  "cottage",
                                  "kayak",
                                  "parking",
                                  "moorage",
                                  "charter"
                                ]
                              },
                              "publicTitle": {
                                "type": "string"
                              },
                              "shortDescription": {
                                "type": "string"
                              },
                              "longDescription": {
                                "type": "string"
                              },
                              "publicPath": {
                                "type": "string",
                                "description": "The product's page on the site."
                              },
                              "pricing": {
                                "type": "object",
                                "description": "Published prices. A cottage's price depends on the dates (pricingForDates).",
                                "properties": {
                                  "publicPriceCents": {
                                    "type": "integer",
                                    "description": "The headline price in Canadian cents; for a boat, its 24-hour rate (`publicPriceBasis`)."
                                  },
                                  "publicPriceBasis": {
                                    "type": "string"
                                  },
                                  "fromPriceCents": {
                                    "type": "integer",
                                    "description": "Boats: the shortest rental, in Canadian cents."
                                  },
                                  "publicPriceLabel": {
                                    "type": "string"
                                  },
                                  "depositCents": {
                                    "type": "integer",
                                    "description": "Refundable damage deposit for each boat."
                                  },
                                  "halfDayRateCents": {
                                    "type": "integer",
                                    "description": "Fishing charters: the half day."
                                  },
                                  "rateStructure": {
                                    "type": "string",
                                    "description": "Boats: the rate table as JSON text (base price and the multiplier for each length of rental)."
                                  },
                                  "priceNote": {
                                    "type": "string"
                                  }
                                }
                              },
                              "operational": {
                                "type": "object",
                                "description": "Counts and sizes.",
                                "properties": {
                                  "stockCount": {
                                    "type": "integer",
                                    "description": "How many are in the fleet or lot."
                                  },
                                  "sleeps": {
                                    "type": "integer"
                                  },
                                  "bedrooms": {
                                    "type": "integer"
                                  },
                                  "bathrooms": {
                                    "type": "number"
                                  },
                                  "seats": {
                                    "type": "integer",
                                    "description": "Paddlers a boat takes."
                                  },
                                  "dockLengthFt": {
                                    "type": "integer"
                                  }
                                }
                              },
                              "detail": {
                                "type": "object",
                                "description": "What a cottage offers.",
                                "properties": {
                                  "maxOccupancy": {
                                    "type": [
                                      "integer",
                                      "null"
                                    ],
                                    "description": "How many the cottage sleeps. null for anything that is not a cottage."
                                  },
                                  "capacityLine": {
                                    "type": "string"
                                  },
                                  "amenityGroups": {
                                    "type": "array",
                                    "items": {
                                      "type": "object"
                                    }
                                  },
                                  "policies": {
                                    "type": "object"
                                  },
                                  "reviews": {
                                    "type": "object"
                                  }
                                }
                              },
                              "media": {
                                "type": "object",
                                "description": "Photograph ids; their addresses are in listMedia.",
                                "properties": {
                                  "heroMediaId": {
                                    "type": "string"
                                  },
                                  "cardMediaId": {
                                    "type": "string"
                                  },
                                  "galleryMediaIds": {
                                    "type": "array",
                                    "items": {
                                      "type": "string"
                                    }
                                  }
                                }
                              },
                              "visibility": {
                                "type": "object",
                                "properties": {
                                  "onlineBookable": {
                                    "type": "boolean"
                                  }
                                }
                              }
                            }
                          }
                        },
                        "hiddenEntryIds": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          },
                          "description": "Products taken off sale; used by the site's own pages."
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/public/experience-inventory/day-availability": {
      "get": {
        "operationId": "dayAvailability",
        "summary": "Which days are open for one product",
        "parameters": [
          {
            "name": "inventoryId",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "A product id from listInventory."
          },
          {
            "name": "days",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 7,
              "maximum": 1200,
              "default": 90
            },
            "description": "How many days from today. The answer stops there: ask for enough days to reach the guest's dates (a date not in the answer has not been checked)."
          },
          {
            "name": "boatLengthFt",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 8,
              "maximum": 200
            },
            "description": "Moorage only: the boat's length, so each day says whether that boat fits."
          }
        ],
        "description": "One row for each day from today. For a yes or no on exact dates and a number of units, quoteBasket's `available` is the shorter road.",
        "responses": {
          "200": {
            "description": "data.days[] — `date`, `soldOut`, and `unitsLeft` where the product is counted (kayaks, parking, charters); for moorage, feet free and the longest boat that fits.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "data": {
                      "type": "object",
                      "properties": {
                        "inventoryId": {
                          "type": "string"
                        },
                        "stockTracked": {
                          "type": "boolean"
                        },
                        "days": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "date": {
                                "type": "string",
                                "format": "date"
                              },
                              "soldOut": {
                                "type": "boolean"
                              },
                              "unitsLeft": {
                                "type": "integer"
                              },
                              "feetFree": {
                                "type": "integer"
                              },
                              "longestBoatThatFitsFt": {
                                "type": "integer"
                              }
                            }
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/public/cc_portals/bamfield/source-inventory-pricing": {
      "get": {
        "operationId": "pricingForDates",
        "summary": "Every cottage's price and availability for exact dates",
        "parameters": [
          {
            "name": "startDate",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "First day (YYYY-MM-DD)."
          },
          {
            "name": "endDate",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "Last day: the checkout day for a stay, the return day for a rental."
          },
          {
            "name": "guests",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 50,
              "default": 4
            },
            "description": "How many people. Each cottage says whether it sleeps that many."
          },
          {
            "name": "vehicles",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            },
            "description": "Vehicles to park."
          },
          {
            "name": "kayaks",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            },
            "description": "Kayaks wanted."
          }
        ],
        "description": "`guests` is echoed back and each cottage says whether it sleeps that many (`fitsGuests`). Every cottage carries `rateStatus`: priced, sold_out, no_rate_for_dates (nobody has set a rate that far ahead — `ratesOnFileTo` is the last night with one), or unavailable_right_now (`retryable: true`; ask again in a few seconds). A cottage is never 'available' beside an empty price without one of those saying why. The figures here are before the cleaning fee and taxes: quoteBasket gives the all-in total.",
        "responses": {
          "200": {
            "description": "data.lodgingUnits[] (total and nightly price for the dates, `rateStatus`, availability, how many each sleeps), data.kayakProducts[] (each boat's rate table and whether it is free), data.parking, data.ratesOnFileTo.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "data": {
                      "type": "object",
                      "properties": {
                        "query": {
                          "type": "object",
                          "properties": {
                            "startDate": {
                              "type": "string"
                            },
                            "endDate": {
                              "type": "string"
                            },
                            "guests": {
                              "type": "integer"
                            },
                            "vehicles": {
                              "type": "integer"
                            },
                            "nights": {
                              "type": "integer"
                            }
                          }
                        },
                        "lodgingUnits": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "unitSlug": {
                                "type": "string"
                              },
                              "unitName": {
                                "type": "string"
                              },
                              "sleeps": {
                                "type": "integer"
                              },
                              "bedrooms": {
                                "type": [
                                  "integer",
                                  "null"
                                ],
                                "description": "null for a suite with no separate bedroom."
                              },
                              "bathrooms": {
                                "type": "number"
                              },
                              "selectedDateRateCents": {
                                "type": [
                                  "integer",
                                  "null"
                                ],
                                "description": "The stay for the dates asked, before the cleaning fee and taxes, in Canadian cents."
                              },
                              "nightlyEstimateCents": {
                                "type": [
                                  "integer",
                                  "null"
                                ]
                              },
                              "rateDisplayLabel": {
                                "type": "string"
                              },
                              "rateStatus": {
                                "type": "string",
                                "enum": [
                                  "priced",
                                  "sold_out",
                                  "no_rate_for_dates",
                                  "unavailable_right_now",
                                  "dates_passed",
                                  "beyond_booking_window",
                                  "invalid_date",
                                  "end_before_start"
                                ],
                                "description": "dates_passed and beyond_booking_window: these dates cannot be booked at all, and asking again will not change that."
                              },
                              "retryable": {
                                "type": "boolean"
                              },
                              "fitsGuests": {
                                "type": "boolean"
                              },
                              "availabilityStatus": {
                                "type": "string",
                                "enum": [
                                  "available",
                                  "sold_out",
                                  "limited",
                                  "unknown",
                                  "unavailable"
                                ],
                                "description": "unavailable: not bookable for these dates (no rate yet, or the dates cannot be booked)."
                              },
                              "availabilityLabel": {
                                "type": "string"
                              }
                            }
                          }
                        },
                        "kayakProducts": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "productSlug": {
                                "type": "string"
                              },
                              "productName": {
                                "type": "string"
                              },
                              "availabilityStatus": {
                                "type": "string"
                              },
                              "availabilityLabel": {
                                "type": "string"
                              },
                              "rateTiers": {
                                "type": "array",
                                "items": {
                                  "type": "object",
                                  "properties": {
                                    "tier": {
                                      "type": "string"
                                    },
                                    "label": {
                                      "type": "string"
                                    },
                                    "amountCents": {
                                      "type": "integer",
                                      "description": "Canadian cents."
                                    },
                                    "amountLabel": {
                                      "type": "string"
                                    }
                                  }
                                }
                              }
                            }
                          }
                        },
                        "parking": {
                          "type": "object",
                          "properties": {
                            "dayRateCents": {
                              "type": "integer",
                              "description": "Canadian cents."
                            },
                            "availabilityStatus": {
                              "type": "string"
                            },
                            "capacity": {
                              "type": "integer"
                            },
                            "availableCount": {
                              "type": "integer"
                            },
                            "bookedCount": {
                              "type": "integer"
                            }
                          }
                        },
                        "ratesOnFileTo": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "date",
                          "description": "The last night a cottage rate has been set for."
                        },
                        "retryable": {
                          "type": "boolean"
                        },
                        "retryAfterSeconds": {
                          "type": "integer"
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/public/cc_portals/bamfield/experience/search": {
      "get": {
        "operationId": "searchSummary",
        "summary": "One row per bookable business, with its published figures (the catalogue, not a count of what is free)",
        "parameters": [
          {
            "name": "startDate",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "First day (YYYY-MM-DD)."
          },
          {
            "name": "endDate",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "Last day: the checkout day for a stay, the return day for a rental."
          },
          {
            "name": "guests",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            },
            "description": "How many people."
          },
          {
            "name": "services",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "default": "all"
            },
            "description": "all, or a comma list of accommodations, parking, moorage, charters, kayaks."
          }
        ],
        "responses": {
          "200": {
            "description": "sections.{accommodations,parking,moorage,charters,kayaks}.items[] — a summary of what exists, not of what is free on the dates: `summary.bedsAvailable` is how many people the cottages sleep in all, `parkingSpotsAvailable` every space there is. For dates that cannot be booked (already past, or beyond the booking window) the answer carries `datesProblem` and every row says `availability.isAvailable: false` with the reason. For a price to quote a guest, use pricingForDates and quoteBasket; for open days, day-availability.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "portal": {
                      "type": "object",
                      "properties": {
                        "slug": {
                          "type": "string"
                        },
                        "name": {
                          "type": "string"
                        }
                      }
                    },
                    "summary": {
                      "type": "object",
                      "properties": {
                        "bedsAvailable": {
                          "type": "integer"
                        },
                        "parkingSpotsAvailable": {
                          "type": "integer"
                        },
                        "moorageDockFeet": {
                          "type": "integer"
                        },
                        "charterVesselsAvailable": {
                          "type": "integer",
                          "description": "Charter trips a day."
                        },
                        "kayaksAvailable": {
                          "type": "integer",
                          "description": "Boats: kayaks and canoes."
                        }
                      }
                    },
                    "sections": {
                      "type": "object",
                      "description": "One list of rows for each of accommodations, parking, moorage, charters and kayaks.",
                      "additionalProperties": {
                        "type": "object",
                        "properties": {
                          "items": {
                            "type": "array",
                            "items": {
                              "type": "object",
                              "properties": {
                                "title": {
                                  "type": "string"
                                },
                                "description": {
                                  "type": "string"
                                },
                                "metrics": {
                                  "type": "object"
                                },
                                "pricing": {
                                  "type": "object",
                                  "properties": {
                                    "currency": {
                                      "type": "string"
                                    },
                                    "unit": {
                                      "type": "string"
                                    },
                                    "rate": {
                                      "type": [
                                        "number",
                                        "null"
                                      ],
                                      "description": "Dollars."
                                    },
                                    "note": {
                                      "type": "string"
                                    }
                                  }
                                }
                              }
                            }
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/public/camping-permits": {
      "get": {
        "operationId": "campingPermits",
        "summary": "Where a camping permit is needed, and who issues it",
        "description": "Overnight camping in the Deer Group or the Broken Group Islands takes a permit; day paddling on the inlets and trips elsewhere on Barkley Sound do not. Each place names its issuing authority and, where the application is done on this site, the campsites that can be applied for. A guest starts the application at https://experiencebamfield.com/permits — with their booking or before it, alone or for a group they invite. Send them there; an assistant does not file it for them.",
        "responses": {
          "200": {
            "description": "data.destinations[] — `label`, `requiresPermit`, `authority.name`, `applyHere`, `terms` (fee per person per night, stay limit, how long a permit takes), `campsites[]`, and `info.url` where another body takes the reservation.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "data": {
                      "type": "object",
                      "properties": {
                        "applyAt": {
                          "type": "string"
                        },
                        "notes": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          }
                        },
                        "destinations": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "key": {
                                "type": "string"
                              },
                              "label": {
                                "type": "string"
                              },
                              "description": {
                                "type": "string"
                              },
                              "requiresPermit": {
                                "type": "boolean"
                              },
                              "applyHere": {
                                "type": "boolean",
                                "description": "True where the application is done on this site."
                              },
                              "authority": {
                                "type": [
                                  "object",
                                  "null"
                                ],
                                "properties": {
                                  "name": {
                                    "type": "string"
                                  },
                                  "territoryLabel": {
                                    "type": "string"
                                  },
                                  "url": {
                                    "type": "string"
                                  }
                                }
                              },
                              "terms": {
                                "type": [
                                  "object",
                                  "null"
                                ],
                                "properties": {
                                  "feeCentsPerPersonPerNight": {
                                    "type": "integer",
                                    "description": "Canadian cents."
                                  },
                                  "fee": {
                                    "type": "string"
                                  },
                                  "stayLimitNights": {
                                    "type": "integer"
                                  },
                                  "processingBusinessDays": {
                                    "type": "integer"
                                  },
                                  "source": {
                                    "type": "string"
                                  }
                                }
                              },
                              "campsites": {
                                "type": "array",
                                "items": {
                                  "type": "object",
                                  "properties": {
                                    "key": {
                                      "type": "string"
                                    },
                                    "label": {
                                      "type": "string"
                                    }
                                  }
                                }
                              },
                              "info": {
                                "type": [
                                  "object",
                                  "null"
                                ],
                                "properties": {
                                  "url": {
                                    "type": "string"
                                  },
                                  "note": {
                                    "type": "string"
                                  }
                                }
                              }
                            }
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/assistant/trip/{key}": {
      "get": {
        "operationId": "tripSummaryForAssistant",
        "summary": "One trip, read-only, for the guest's assistant (the one address in this public API that takes a key; the partner API is separate, with keys of its own)",
        "description": "A guest makes this link for their assistant from their trip page (/trips, signed in). The key is the whole of the permission and it permits reading this one summary: whether the trip is ready, what needs doing, by when and who it is waiting on, what is booked and what each line came to, and the money.\n\nIt never includes anyone's name or contact details, the order number, card details, waivers, ID photographs or permits, and nothing can be changed through it.\n\n**Read it again before you answer.** The summary is the trip as recorded at `freshness.checkedAt` and is worked out from the records on every read; do not answer from a copy older than `freshness.staleAfterSeconds`.\n\n**Not known is never yes, no or 0.** A fact that cannot be verified is `unknown`; an amount that cannot be stated is null. `confirmedByTheBusiness: not_yet` means the business has not answered yet — it is not a refusal.\n\n**Money.** A line is `paid: yes`, and the trip can be `ready`, only when the order's payment record adds up. When it does not, `money.state` is `not_reconciled`, `owingCents` and `bookingsTotalCents` are null, a deposit on the order is `unknown`, a `payment` line appears under `needsAttention`, and the guest should not pay again on the strength of the summary — they call the desk.\n\nThe damage deposit comes back from Experience Bamfield within `money.deposit.sentBackWithinHours` of the return or check-out; the guest's bank can take 5 to 10 business days after that to show it.\n\n**The link** stops working 30 days after the end of the trip's last day in Bamfield, and never sooner than 30 days after it was made (`thisLink.expiresAt`, an ISO time in UTC); the guest can turn it off before then. The key may be sent as `Authorization: Bearer KEY` to /api/assistant/trip instead of in the address. One slash on the end of the address is ignored. An assistant running in a browser may read it (any origin; no cookie is read). At most 30 reads a minute. Every answer, a refusal included, is `no-store`, `noindex` and sends no referrer.\n\nThe same summary is the MCP tool `get_trip_summary`, which takes the whole link as `link` or just the key as `key`.",
        "parameters": [
          {
            "name": "key",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9_-]{43}$"
            },
            "description": "The key from the link the guest gave you: the last part of the link."
          }
        ],
        "responses": {
          "200": {
            "description": "The summary, in `data`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "data"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "data": {
                      "type": "object",
                      "required": [
                        "about",
                        "freshness",
                        "trip",
                        "ready",
                        "needsAttention",
                        "booked",
                        "money",
                        "finishes",
                        "notIncluded",
                        "thisLink",
                        "contact"
                      ],
                      "properties": {
                        "about": {
                          "type": "string"
                        },
                        "freshness": {
                          "type": "object",
                          "description": "The summary is the trip as recorded at checkedAt and is worked out again on every read. Read the link again before answering; do not answer from a copy older than staleAfterSeconds.",
                          "properties": {
                            "checkedAt": {
                              "type": "string"
                            },
                            "staleAfterSeconds": {
                              "type": "integer"
                            },
                            "readAgainBeforeAnswering": {
                              "type": "boolean"
                            },
                            "note": {
                              "type": "string"
                            }
                          }
                        },
                        "trip": {
                          "type": "object",
                          "properties": {
                            "title": {
                              "type": "string"
                            },
                            "startDate": {
                              "type": [
                                "string",
                                "null"
                              ],
                              "description": "YYYY-MM-DD, or null."
                            },
                            "endDate": {
                              "type": [
                                "string",
                                "null"
                              ],
                              "description": "YYYY-MM-DD, or null."
                            },
                            "stage": {
                              "type": "string",
                              "enum": [
                                "upcoming",
                                "under_way",
                                "finished",
                                "cancelled"
                              ]
                            },
                            "testOrder": {
                              "type": "boolean",
                              "description": "Present, and true, only on an order made with a test code. A real trip does not carry this field."
                            }
                          }
                        },
                        "ready": {
                          "type": "object",
                          "properties": {
                            "answer": {
                              "type": "string",
                              "enum": [
                                "ready",
                                "not_ready",
                                "cannot_tell",
                                "not_applicable"
                              ],
                              "description": "ready only when every line the trip depends on is ready, the payment record included. cannot_tell: nothing is waiting on anyone, and something the trip depends on could not be verified. not_applicable: the trip is over or cancelled."
                            },
                            "headline": {
                              "type": "string"
                            },
                            "detail": {
                              "type": "string"
                            },
                            "readyCount": {
                              "type": [
                                "integer",
                                "null"
                              ],
                              "description": "null once the trip is over or cancelled."
                            },
                            "dependsOn": {
                              "type": [
                                "integer",
                                "null"
                              ]
                            },
                            "rule": {
                              "type": "string"
                            },
                            "checkedAt": {
                              "type": "string"
                            }
                          }
                        },
                        "needsAttention": {
                          "type": "array",
                          "description": "Lines that need someone: a payment record to be checked first, then the guest's own, then the desk's, then what could not be verified, then what is waiting; within each, the earliest actBy first. Once the trip is over or cancelled, only money that still needs someone is listed (a payment record to check, a deposit not returned).",
                          "items": {
                            "type": "object",
                            "properties": {
                              "what": {
                                "type": "string",
                                "enum": [
                                  "stay",
                                  "parking",
                                  "rental",
                                  "charter",
                                  "moorage",
                                  "service",
                                  "waivers",
                                  "permits",
                                  "payment",
                                  "deposit",
                                  "group"
                                ]
                              },
                              "title": {
                                "type": "string"
                              },
                              "business": {
                                "type": [
                                  "string",
                                  "null"
                                ],
                                "description": "The business as a guest knows it."
                              },
                              "state": {
                                "type": "string",
                                "enum": [
                                  "ready",
                                  "yours",
                                  "waiting",
                                  "unknown",
                                  "failed",
                                  "cancelled"
                                ],
                                "description": "ready: nothing more is needed. yours: the guest has something to do. waiting: someone else does (waitingOn). unknown: the record that would say is missing — never treat it as ready. failed: the desk has to fix it. cancelled: no longer part of the trip. A booked and confirmed line whose order's payment record does not add up is waiting (on the desk), not ready."
                              },
                              "status": {
                                "type": "string"
                              },
                              "detail": {
                                "type": "string",
                                "description": "What to tell the guest, in words."
                              },
                              "waitingOn": {
                                "type": "string",
                                "enum": [
                                  "guest",
                                  "organiser",
                                  "business",
                                  "skipper",
                                  "permit_office",
                                  "desk",
                                  "nobody"
                                ]
                              },
                              "blocksTheTrip": {
                                "type": "boolean"
                              },
                              "actBy": {
                                "type": [
                                  "string",
                                  "null"
                                ],
                                "description": "The date to act by. For a camping permit not yet applied for, two weeks before it is needed. For a payment record to check, today. null when nothing is left to do, and on a booked line that is only waiting on the payment check (the payment item carries the date)."
                              },
                              "neededBy": {
                                "type": [
                                  "string",
                                  "null"
                                ],
                                "description": "The day the thing this line gates begins."
                              },
                              "urgency": {
                                "type": [
                                  "string",
                                  "null"
                                ],
                                "enum": [
                                  "now",
                                  "overdue",
                                  "this_week",
                                  "later",
                                  null
                                ],
                                "description": "now: money that does not add up or has not come back — the guest should contact the desk today. Otherwise from actBy and the day in Bamfield at checkedAt; overdue: the date has passed."
                              },
                              "counts": {
                                "type": [
                                  "object",
                                  "null"
                                ],
                                "properties": {
                                  "done": {
                                    "type": "integer"
                                  },
                                  "total": {
                                    "type": "integer"
                                  }
                                },
                                "description": "For waivers and permits: how many are done of how many are needed."
                              },
                              "recordLastChangedAt": {
                                "type": [
                                  "string",
                                  "null"
                                ],
                                "description": "When the record this line was read from last changed. null when there is no record behind the line at all (for example, boats booked with nobody listed to paddle them)."
                              }
                            }
                          }
                        },
                        "booked": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "what": {
                                "type": "string",
                                "enum": [
                                  "stay",
                                  "parking",
                                  "rental",
                                  "charter",
                                  "moorage",
                                  "service",
                                  "waivers",
                                  "permits",
                                  "payment",
                                  "deposit",
                                  "group"
                                ]
                              },
                              "title": {
                                "type": "string"
                              },
                              "business": {
                                "type": [
                                  "string",
                                  "null"
                                ],
                                "description": "The business as a guest knows it."
                              },
                              "state": {
                                "type": "string",
                                "enum": [
                                  "ready",
                                  "yours",
                                  "waiting",
                                  "unknown",
                                  "failed",
                                  "cancelled"
                                ],
                                "description": "ready: nothing more is needed. yours: the guest has something to do. waiting: someone else does (waitingOn). unknown: the record that would say is missing — never treat it as ready. failed: the desk has to fix it. cancelled: no longer part of the trip. A booked and confirmed line whose order's payment record does not add up is waiting (on the desk), not ready."
                              },
                              "status": {
                                "type": "string"
                              },
                              "detail": {
                                "type": "string",
                                "description": "What to tell the guest, in words."
                              },
                              "waitingOn": {
                                "type": "string",
                                "enum": [
                                  "guest",
                                  "organiser",
                                  "business",
                                  "skipper",
                                  "permit_office",
                                  "desk",
                                  "nobody"
                                ]
                              },
                              "blocksTheTrip": {
                                "type": "boolean"
                              },
                              "actBy": {
                                "type": [
                                  "string",
                                  "null"
                                ],
                                "description": "The date to act by. For a camping permit not yet applied for, two weeks before it is needed. For a payment record to check, today. null when nothing is left to do, and on a booked line that is only waiting on the payment check (the payment item carries the date)."
                              },
                              "neededBy": {
                                "type": [
                                  "string",
                                  "null"
                                ],
                                "description": "The day the thing this line gates begins."
                              },
                              "urgency": {
                                "type": [
                                  "string",
                                  "null"
                                ],
                                "enum": [
                                  "now",
                                  "overdue",
                                  "this_week",
                                  "later",
                                  null
                                ],
                                "description": "now: money that does not add up or has not come back — the guest should contact the desk today. Otherwise from actBy and the day in Bamfield at checkedAt; overdue: the date has passed."
                              },
                              "counts": {
                                "type": [
                                  "object",
                                  "null"
                                ],
                                "properties": {
                                  "done": {
                                    "type": "integer"
                                  },
                                  "total": {
                                    "type": "integer"
                                  }
                                },
                                "description": "For waivers and permits: how many are done of how many are needed."
                              },
                              "recordLastChangedAt": {
                                "type": [
                                  "string",
                                  "null"
                                ],
                                "description": "When the record this line was read from last changed. null when there is no record behind the line at all (for example, boats booked with nobody listed to paddle them)."
                              },
                              "startDate": {
                                "type": [
                                  "string",
                                  "null"
                                ],
                                "description": "YYYY-MM-DD, or null."
                              },
                              "endDate": {
                                "type": [
                                  "string",
                                  "null"
                                ],
                                "description": "YYYY-MM-DD, or null."
                              },
                              "booked": {
                                "type": "string",
                                "enum": [
                                  "yes",
                                  "no",
                                  "unknown",
                                  "not_applicable"
                                ]
                              },
                              "paid": {
                                "type": "string",
                                "enum": [
                                  "yes",
                                  "no",
                                  "unknown",
                                  "not_applicable"
                                ],
                                "description": "yes only when the order's payment record adds up. unknown when it does not. not_applicable on a cancelled line: what was paid for it is in the refunds."
                              },
                              "confirmedByTheBusiness": {
                                "type": "string",
                                "enum": [
                                  "yes",
                                  "not_yet",
                                  "unknown",
                                  "not_applicable"
                                ],
                                "description": "not_yet means the business has not answered yet. It is not a refusal."
                              },
                              "amount": {
                                "type": [
                                  "object",
                                  "null"
                                ],
                                "description": "What this line came to. null when the payment record does not add up, or the order is from before lines were itemised.",
                                "properties": {
                                  "priceCents": {
                                    "type": "integer",
                                    "description": "Canadian cents."
                                  },
                                  "discountCents": {
                                    "type": "integer",
                                    "description": "Canadian cents."
                                  },
                                  "feesCents": {
                                    "type": "integer",
                                    "description": "Fees that are part of the cost, such as cleaning. Never the damage deposit."
                                  },
                                  "taxes": {
                                    "type": "array",
                                    "items": {
                                      "type": "object",
                                      "properties": {
                                        "code": {
                                          "type": "string"
                                        },
                                        "label": {
                                          "type": "string"
                                        },
                                        "amountCents": {
                                          "type": "integer",
                                          "description": "Canadian cents."
                                        }
                                      }
                                    }
                                  },
                                  "taxCents": {
                                    "type": "integer",
                                    "description": "Canadian cents."
                                  },
                                  "totalCents": {
                                    "type": "integer",
                                    "description": "price − discount + fees + tax. The damage deposit is not in it."
                                  },
                                  "suppliedBy": {
                                    "type": [
                                      "string",
                                      "null"
                                    ]
                                  }
                                }
                              }
                            }
                          }
                        },
                        "money": {
                          "type": "object",
                          "description": "Read from the order's own payment record. Always present on this link.",
                          "properties": {
                            "state": {
                              "type": "string",
                              "enum": [
                                "reconciled",
                                "not_reconciled"
                              ],
                              "description": "not_reconciled: what was received cannot be matched to what the order came to. No balance is stated, the trip is not ready, and the guest should not pay again on the strength of this summary."
                            },
                            "headline": {
                              "type": "string"
                            },
                            "detail": {
                              "type": "string"
                            },
                            "currency": {
                              "type": "string",
                              "enum": [
                                "CAD"
                              ]
                            },
                            "bookingsTotalCents": {
                              "type": [
                                "integer",
                                "null"
                              ],
                              "description": "What the bookings came to: the sum of booked[].amount.totalCents plus minimumChargeCents. The damage deposit is not in it. For a cancelled trip, what it came to before it was cancelled. null when not reconciled."
                            },
                            "minimumChargeCents": {
                              "type": "integer",
                              "description": "Added to reach the card network's smallest charge, when the order came to less. Usually 0."
                            },
                            "deposit": {
                              "type": "object",
                              "description": "The refundable damage deposit, held separately from the cost. When reconciled: paymentsReceivedCents = bookingsTotalCents + deposit.amountCents.",
                              "properties": {
                                "state": {
                                  "type": "string",
                                  "enum": [
                                    "none",
                                    "held",
                                    "refund_processing",
                                    "partly_returned",
                                    "returned",
                                    "unknown"
                                  ],
                                  "description": "none: the order has no deposit. held: taken and not sent back. refund_processing: sent back, not finished. partly_returned: part was sent back and the rest was not. returned: all of it was sent back. unknown: the payment record does not add up and the order carries a deposit (or nothing says whether it does), so where it stands is not claimed."
                                },
                                "amountCents": {
                                  "type": [
                                    "integer",
                                    "null"
                                  ],
                                  "description": "The deposit on the order. null when state is unknown."
                                },
                                "returnedCents": {
                                  "type": [
                                    "integer",
                                    "null"
                                  ],
                                  "description": "Canadian cents. null means not known — never read null as 0."
                                },
                                "onItsWayBackCents": {
                                  "type": [
                                    "integer",
                                    "null"
                                  ],
                                  "description": "Canadian cents. null means not known — never read null as 0."
                                },
                                "notReturnedCents": {
                                  "type": [
                                    "integer",
                                    "null"
                                  ],
                                  "description": "Once a return has started: what was left out of it. The reason is not recorded: do not tell the guest it is refundable, and do not tell them it was kept — they contact the desk. null while the deposit is simply held (state held) — a held deposit is not money that has failed to come back — and when state is unknown."
                                },
                                "sentBackAt": {
                                  "type": [
                                    "string",
                                    "null"
                                  ],
                                  "description": "When the latest deposit return was sent. The bank can take 5 to 10 business days from then."
                                },
                                "sentBackWithinHours": {
                                  "type": "integer",
                                  "description": "Experience Bamfield sends the deposit back within this many hours of the return or check-out. The guest's bank can take 5 to 10 business days after that to show it."
                                },
                                "detail": {
                                  "type": [
                                    "string",
                                    "null"
                                  ]
                                }
                              }
                            },
                            "paymentsReceivedCents": {
                              "type": [
                                "integer",
                                "null"
                              ],
                              "description": "Every payment received. Refunds are not taken off. null when the order is marked paid and the amount charged is not on the record."
                            },
                            "refundsCompletedCents": {
                              "type": "integer",
                              "description": "Canadian cents."
                            },
                            "refundsPendingCents": {
                              "type": "integer",
                              "description": "Refunds sent and not finished."
                            },
                            "netRetainedCents": {
                              "type": [
                                "integer",
                                "null"
                              ],
                              "description": "Net payments after completed refunds: paymentsReceivedCents − refundsCompletedCents. A refund that is still on its way (refundsPendingCents) has NOT been taken off yet — see netAfterPendingRefundsCents."
                            },
                            "netAfterPendingRefundsCents": {
                              "type": [
                                "integer",
                                "null"
                              ],
                              "description": "netRetainedCents − refundsPendingCents: what will remain once the refunds on their way finish."
                            },
                            "events": {
                              "type": "array",
                              "description": "Each payment and refund with its date, oldest first.",
                              "items": {
                                "type": "object",
                                "properties": {
                                  "kind": {
                                    "type": "string",
                                    "enum": [
                                      "payment",
                                      "payment_registered",
                                      "refund"
                                    ]
                                  },
                                  "at": {
                                    "type": [
                                      "string",
                                      "null"
                                    ],
                                    "description": "An ISO 8601 time in UTC (ending in Z), or null."
                                  },
                                  "amountCents": {
                                    "type": [
                                      "integer",
                                      "null"
                                    ],
                                    "description": "Canadian cents. null means not known — never read null as 0."
                                  },
                                  "status": {
                                    "type": "string",
                                    "enum": [
                                      "completed",
                                      "processing",
                                      "failed"
                                    ]
                                  },
                                  "isDepositReturn": {
                                    "type": "boolean"
                                  }
                                }
                              }
                            },
                            "owingCents": {
                              "type": [
                                "integer",
                                "null"
                              ],
                              "description": "What the guest still owes. null when not reconciled."
                            },
                            "notReconciledReason": {
                              "type": [
                                "string",
                                "null"
                              ],
                              "enum": [
                                "not_paid",
                                "charge_missing",
                                "total_missing",
                                "line_total_missing",
                                "lines_do_not_add_up",
                                "charge_differs_from_total",
                                "refund_on_unitemised_order",
                                "nothing_recorded",
                                null
                              ],
                              "description": "Which check failed, when state is not_reconciled; null when reconciled. For the desk and for testing: the guest is told the same thing whichever it is — do not pay again, contact the desk."
                            }
                          }
                        },
                        "finishes": {
                          "type": "object",
                          "description": "Three separate things: ready to travel, the trip being over, and the money being settled.",
                          "properties": {
                            "readyToTravel": {
                              "type": "boolean"
                            },
                            "tripFinished": {
                              "type": "boolean"
                            },
                            "moneySettled": {
                              "type": "string",
                              "enum": [
                                "settled",
                                "owing",
                                "refund_pending",
                                "deposit_held",
                                "deposit_part_not_returned",
                                "not_reconciled",
                                "not_shown"
                              ],
                              "description": "deposit_held: taken and due back after the trip. deposit_part_not_returned: part went back and the rest did not, with no reason recorded — the guest contacts the desk."
                            }
                          }
                        },
                        "notIncluded": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          }
                        },
                        "thisLink": {
                          "type": "object",
                          "properties": {
                            "readsOnly": {
                              "type": "boolean"
                            },
                            "cannot": {
                              "type": "string"
                            },
                            "expiresAt": {
                              "type": "string",
                              "description": "An ISO 8601 time in UTC. 30 days after the end of the trip's last day in Bamfield, and never sooner than 30 days after the link was made."
                            },
                            "expiryRule": {
                              "type": "string"
                            },
                            "timesRead": {
                              "type": [
                                "integer",
                                "null"
                              ],
                              "description": "How many times this link has been read, this read included. Counted in one step in the database, so reads arriving together are all counted. Reads through the MCP tool get_trip_summary are counted here too, and count toward the same 30-a-minute limit."
                            },
                            "lastReadAt": {
                              "type": [
                                "string",
                                "null"
                              ],
                              "description": "When it was read before this."
                            }
                          }
                        },
                        "contact": {
                          "type": "object",
                          "properties": {
                            "deskPhone": {
                              "type": "string"
                            },
                            "for": {
                              "type": "string"
                            }
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "LINK_NOT_VALID — not a link this site made."
          },
          "405": {
            "description": "METHOD_NOT_ALLOWED — this address only reads (GET)."
          },
          "410": {
            "description": "LINK_EXPIRED, LINK_TURNED_OFF or TRIP_NOT_AVAILABLE — ask the guest for a new link."
          },
          "429": {
            "description": "ASK_AGAIN_LATER — read too often; wait a minute (`Retry-After`)."
          },
          "503": {
            "description": "TRY_AGAIN — the trip could not be checked just now; this is not a refusal. Ask again in a few seconds."
          }
        }
      }
    },
    "/api/public/experience-media": {
      "get": {
        "operationId": "listMedia",
        "summary": "Photographs, with their addresses",
        "description": "Every photograph of a business and its products. A product's `media.*MediaId` in listInventory is an `id` here; `deliveryUrl` is the photograph's address on this site and may be fetched (add `?w=640` or `?w=1600` for a smaller copy).",
        "parameters": [
          {
            "name": "providerSlug",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Only one business's photographs, e.g. bamfield-adventure-center."
          }
        ],
        "responses": {
          "200": {
            "description": "data.entries[] — `id`, `title`, `altText`, `category` and `deliveryUrl`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "data": {
                      "type": "object",
                      "properties": {
                        "entries": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "id": {
                                "type": "string"
                              },
                              "providerSlug": {
                                "type": "string"
                              },
                              "mediaType": {
                                "type": "string",
                                "enum": [
                                  "image",
                                  "video"
                                ]
                              },
                              "category": {
                                "type": "string"
                              },
                              "title": {
                                "type": "string"
                              },
                              "altText": {
                                "type": "string"
                              },
                              "caption": {
                                "type": "string"
                              },
                              "deliveryUrl": {
                                "type": "string",
                                "description": "Path on this site."
                              },
                              "sortOrder": {
                                "type": "integer"
                              }
                            }
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/public/cc_portals/bamfield/cart-quote": {
      "post": {
        "operationId": "quoteBasket",
        "summary": "The price of a whole basket, and whether it can be booked — the amount checkout charges",
        "description": "Each line: its price, and whether it is free for those dates in the number asked for (the same counts checkout refuses on — seven boats of a fleet of six comes back `available: false`). The basket: the cleaning fee, each tax, the refundable deposits and `totals.dueAtCheckoutCents`, the amount the card is charged. Saves nothing and holds nothing; call it as often as you like. Nothing is held until the guest pays, so availability is true at the moment of the answer.\n\nKayak prices: a kayak is charged by the time between pick-up and return, not by the calendar date. The lengths are 4 hours, 8 hours, 24 hours, 2 days, 3 days, 4 days and a week; the price is the shortest of those that covers the rental (6 hours is charged as 8; 5 or 6 days is charged as the week), and past a week every started week is a whole week. Pick-up is from 07:00 and the boat is back by 20:00. Send `startTime` and `endTime` to quoteBasket for an exact price; with dates only, the same date twice is the 8-hour day and dates N apart are N days. Each boat's whole rate table is in pricingForDates under `kayakProducts[].rateTiers`, and the answer to quoteBasket names the rate it charged (`rate.summary` — e.g. \"2 × Weekly Rate (5 Days or More)\" for eight days; `rate.periods` is how many of `rate.label` were charged). Every boat also carries a refundable damage deposit (`depositCents`), charged at checkout.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "lines"
                ],
                "properties": {
                  "lines": {
                    "type": "array",
                    "minItems": 1,
                    "items": {
                      "type": "object",
                      "required": [
                        "id",
                        "canonicalInventoryId",
                        "startDate",
                        "endDate",
                        "quantity"
                      ],
                      "properties": {
                        "id": {
                          "type": "string",
                          "description": "Your own name for the line; it comes back on the answer."
                        },
                        "canonicalInventoryId": {
                          "type": "string",
                          "description": "A product id from listInventory."
                        },
                        "startDate": {
                          "type": "string",
                          "format": "date"
                        },
                        "endDate": {
                          "type": "string",
                          "format": "date"
                        },
                        "quantity": {
                          "type": "integer",
                          "minimum": 1,
                          "description": "1 for a cottage; the number of boats, vehicles or charter trips otherwise."
                        },
                        "guests": {
                          "type": "integer",
                          "minimum": 1,
                          "description": "Cottages: how many people, checked against how many the cottage sleeps. Prices do not change with the number of guests."
                        },
                        "tripType": {
                          "type": "string",
                          "enum": [
                            "half-day",
                            "full-day"
                          ],
                          "description": "Fishing charters only. Full day when left out; anything else is refused (invalid_trip_type). One trip is one day: add a line for each day."
                        },
                        "boatLengthFt": {
                          "type": "integer",
                          "minimum": 1,
                          "maximum": 300,
                          "description": "Moorage only: the boat's length in feet."
                        },
                        "startTime": {
                          "type": "string",
                          "pattern": "^([01]\\d|2[0-3]):[0-5]\\d$",
                          "description": "Boats only: pick-up time, HH:MM, from 07:00. A boat is charged by the time between pick-up and return. Send both times or neither."
                        },
                        "endTime": {
                          "type": "string",
                          "pattern": "^([01]\\d|2[0-3]):[0-5]\\d$",
                          "description": "Boats only: return time, HH:MM, by 20:00."
                        }
                      }
                    }
                  }
                }
              },
              "example": {
                "lines": [
                  {
                    "id": "kayaks",
                    "canonicalInventoryId": "bac-sea-kayak-single",
                    "startDate": "2027-07-11",
                    "endDate": "2027-07-13",
                    "quantity": 2
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "data.lines[] and data.totals. HTTP 200 also when a line is refused: read `priced`, `available` and `totals.bookable`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "data": {
                      "type": "object",
                      "properties": {
                        "lines": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "id": {
                                "type": "string",
                                "description": "The id you sent."
                              },
                              "inventoryId": {
                                "type": "string"
                              },
                              "title": {
                                "type": "string"
                              },
                              "category": {
                                "type": "string",
                                "enum": [
                                  "cottage",
                                  "kayak",
                                  "parking",
                                  "moorage",
                                  "charter"
                                ]
                              },
                              "quantity": {
                                "type": "integer"
                              },
                              "startDate": {
                                "type": "string",
                                "format": "date"
                              },
                              "endDate": {
                                "type": "string",
                                "format": "date"
                              },
                              "nights": {
                                "type": "integer",
                                "description": "Stays and moorage."
                              },
                              "days": {
                                "type": "integer",
                                "description": "Parking."
                              },
                              "priced": {
                                "type": "boolean"
                              },
                              "totalCents": {
                                "type": [
                                  "integer",
                                  "null"
                                ],
                                "description": "This line before fees and taxes, in Canadian cents. Null when `priced` is false."
                              },
                              "depositCents": {
                                "type": "integer",
                                "description": "Refundable damage deposit for this line (boats)."
                              },
                              "reason": {
                                "type": "string",
                                "enum": [
                                  "unknown_inventory",
                                  "missing_canonical_inventory_id",
                                  "dates_required",
                                  "end_before_start",
                                  "invalid_time",
                                  "invalid_trip_type",
                                  "return_time_required",
                                  "pickup_time_required",
                                  "outside_hours",
                                  "return_before_pickup",
                                  "stay_needs_a_night",
                                  "sold_out",
                                  "no_rate_for_dates",
                                  "source_rate_unavailable",
                                  "unpriced_category"
                                ],
                                "description": "Why the line has no price. unknown_inventory: No product on sale has that id. missing_canonical_inventory_id: The line has no product id. dates_required: The line has no dates. end_before_start: endDate is before startDate. invalid_time: A time is not HH:MM on the 24-hour clock. invalid_trip_type: A charter's tripType is half-day or full-day. return_time_required: A pick-up time was given without a return time. pickup_time_required: A return time was given without a pick-up time. outside_hours: A boat's pick-up or return is outside 07:00–20:00. `field` says which. return_before_pickup: The return is not after the pick-up. stay_needs_a_night: A stay needs at least one night. sold_out: Taken for those dates. no_rate_for_dates: No nightly rate has been set that far ahead yet. source_rate_unavailable: The nightly rate could not be read just now. Retryable: ask again in a few seconds. unpriced_category: Not sold online by the night, day or trip."
                              },
                              "field": {
                                "type": "string",
                                "enum": [
                                  "startTime",
                                  "endTime"
                                ],
                                "description": "With outside_hours: which time to move."
                              },
                              "message": {
                                "type": "string",
                                "description": "The reason in words a guest can act on."
                              },
                              "retryable": {
                                "type": "boolean",
                                "description": "True when asking again in a few seconds can succeed."
                              },
                              "available": {
                                "type": [
                                  "boolean",
                                  "null"
                                ],
                                "description": "Whether the line is free for those dates in the number asked for — the same counts checkout refuses on. Null where the product is not counted."
                              },
                              "unitsLeft": {
                                "type": "integer",
                                "description": "How many are free for those dates, before this line."
                              },
                              "unitsTotal": {
                                "type": "integer"
                              },
                              "availability": {
                                "type": "object",
                                "description": "Present when `available` is false.",
                                "properties": {
                                  "reason": {
                                    "type": "string",
                                    "enum": [
                                      "one_unit_only",
                                      "over_capacity",
                                      "sold_out",
                                      "not_enough_left",
                                      "no_room_at_the_dock",
                                      "boat_too_long"
                                    ],
                                    "description": "one_unit_only: A cottage is one unit: quantity above 1 cannot be booked. over_capacity: More guests than the cottage sleeps. sold_out: Nothing is free for those dates. not_enough_left: Fewer are free than were asked for. `unitsLeft` says how many. no_room_at_the_dock: The boat does not fit at the dock on those nights. `longestBoatThatFitsFt` says what does. boat_too_long: The boat is longer than the dock takes for any one boat. `maxBoatLengthFt` says the limit."
                                  },
                                  "message": {
                                    "type": "string"
                                  },
                                  "unitsLeft": {
                                    "type": "integer"
                                  },
                                  "unitsTotal": {
                                    "type": "integer"
                                  },
                                  "sleeps": {
                                    "type": "integer"
                                  },
                                  "longestBoatThatFitsFt": {
                                    "type": "integer"
                                  }
                                }
                              },
                              "rate": {
                                "type": "object",
                                "description": "Which rate was charged (boats and charters).",
                                "properties": {
                                  "label": {
                                    "type": "string"
                                  },
                                  "periods": {
                                    "type": "integer"
                                  },
                                  "summary": {
                                    "type": "string"
                                  },
                                  "coversHours": {
                                    "type": "number"
                                  },
                                  "chargedByTime": {
                                    "type": "boolean"
                                  }
                                }
                              },
                              "notes": {
                                "type": "array",
                                "items": {
                                  "type": "string"
                                },
                                "description": "Anything assumed in pricing the line, e.g. a boat length that was not given."
                              }
                            }
                          }
                        },
                        "totals": {
                          "type": "object",
                          "description": "The whole basket, the way checkout charges it. Quote a guest from `dueAtCheckoutCents` (and say how much of it is refundable deposit), not from the lines alone.",
                          "properties": {
                            "currency": {
                              "type": "string",
                              "enum": [
                                "CAD"
                              ]
                            },
                            "itemsCents": {
                              "type": "integer",
                              "description": "The priced lines added up."
                            },
                            "fees": {
                              "type": "array",
                              "items": {
                                "type": "object",
                                "properties": {
                                  "label": {
                                    "type": "string"
                                  },
                                  "amountCents": {
                                    "type": "integer",
                                    "description": "Canadian cents."
                                  }
                                }
                              },
                              "description": "A cleaning fee for each cottage."
                            },
                            "feesCents": {
                              "type": "integer",
                              "description": "Canadian cents."
                            },
                            "taxes": {
                              "type": "array",
                              "items": {
                                "type": "object",
                                "properties": {
                                  "label": {
                                    "type": "string"
                                  },
                                  "amountCents": {
                                    "type": "integer",
                                    "description": "Canadian cents."
                                  }
                                }
                              },
                              "description": "Each tax by what it is charged on. Parking carries none."
                            },
                            "taxCents": {
                              "type": "integer",
                              "description": "Canadian cents."
                            },
                            "totalCents": {
                              "type": "integer",
                              "description": "What the trip costs: lines, fees and taxes."
                            },
                            "refundableDepositCents": {
                              "type": "integer",
                              "description": "Held on the card at checkout and returned after the rental. Not part of the trip's cost."
                            },
                            "dueAtCheckoutCents": {
                              "type": "integer",
                              "description": "What the card is charged at checkout: totalCents plus refundableDepositCents."
                            },
                            "complete": {
                              "type": "boolean",
                              "description": "False when a line could not be priced; the figures then leave that line out."
                            },
                            "bookable": {
                              "type": "boolean",
                              "description": "True when every line is priced and free for its dates in the number asked for."
                            },
                            "notes": {
                              "type": "array",
                              "items": {
                                "type": "string"
                              }
                            }
                          }
                        },
                        "retryable": {
                          "type": "boolean"
                        },
                        "retryAfterSeconds": {
                          "type": "integer"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "503": {
            "description": "Prices could not be read at all just now. Retryable: `Retry-After` says when.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "code": {
                      "type": "string"
                    },
                    "retryable": {
                      "type": "boolean"
                    },
                    "retryAfterSeconds": {
                      "type": "integer"
                    },
                    "message": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/public/cc_portals/bamfield/cart-link": {
      "get": {
        "operationId": "previewCartLink",
        "summary": "What a cart link opens to — read without opening it",
        "description": "Send the same `l=` parts the link carries. The answer is what the guest will find: each line with its price and whether it is free, `opensInCart` (false, with `leftOutBecause`, for a line the cart page will leave out and name instead), and the totals. Read only: it creates no cart and holds nothing. Use it to check a link before handing it over — the page at https://experiencebamfield.com/book itself is not for crawlers.",
        "parameters": [
          {
            "name": "l",
            "in": "query",
            "required": true,
            "schema": {
              "type": "array",
              "items": {
                "type": "string"
              },
              "maxItems": 12
            },
            "style": "form",
            "explode": true,
            "description": "One line: PRODUCT~FROM~TO~HOWMANY~GUESTS[~BOATFEET]. Repeat for each line."
          }
        ],
        "responses": {
          "200": {
            "description": "data.url (the link), data.lines[] (as quoteBasket, plus `opensInCart`), data.totals, data.opensWith, data.leftOut, data.notUnderstood (parts of the address that were not lines).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "data": {
                      "type": "object",
                      "properties": {
                        "url": {
                          "type": "string"
                        },
                        "lines": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "id": {
                                "type": "string",
                                "description": "The id you sent."
                              },
                              "inventoryId": {
                                "type": "string"
                              },
                              "title": {
                                "type": "string"
                              },
                              "category": {
                                "type": "string",
                                "enum": [
                                  "cottage",
                                  "kayak",
                                  "parking",
                                  "moorage",
                                  "charter"
                                ]
                              },
                              "quantity": {
                                "type": "integer"
                              },
                              "startDate": {
                                "type": "string",
                                "format": "date"
                              },
                              "endDate": {
                                "type": "string",
                                "format": "date"
                              },
                              "nights": {
                                "type": "integer",
                                "description": "Stays and moorage."
                              },
                              "days": {
                                "type": "integer",
                                "description": "Parking."
                              },
                              "priced": {
                                "type": "boolean"
                              },
                              "totalCents": {
                                "type": [
                                  "integer",
                                  "null"
                                ],
                                "description": "This line before fees and taxes, in Canadian cents. Null when `priced` is false."
                              },
                              "depositCents": {
                                "type": "integer",
                                "description": "Refundable damage deposit for this line (boats)."
                              },
                              "reason": {
                                "type": "string",
                                "enum": [
                                  "unknown_inventory",
                                  "missing_canonical_inventory_id",
                                  "dates_required",
                                  "end_before_start",
                                  "invalid_time",
                                  "invalid_trip_type",
                                  "return_time_required",
                                  "pickup_time_required",
                                  "outside_hours",
                                  "return_before_pickup",
                                  "stay_needs_a_night",
                                  "sold_out",
                                  "no_rate_for_dates",
                                  "source_rate_unavailable",
                                  "unpriced_category"
                                ],
                                "description": "Why the line has no price. unknown_inventory: No product on sale has that id. missing_canonical_inventory_id: The line has no product id. dates_required: The line has no dates. end_before_start: endDate is before startDate. invalid_time: A time is not HH:MM on the 24-hour clock. invalid_trip_type: A charter's tripType is half-day or full-day. return_time_required: A pick-up time was given without a return time. pickup_time_required: A return time was given without a pick-up time. outside_hours: A boat's pick-up or return is outside 07:00–20:00. `field` says which. return_before_pickup: The return is not after the pick-up. stay_needs_a_night: A stay needs at least one night. sold_out: Taken for those dates. no_rate_for_dates: No nightly rate has been set that far ahead yet. source_rate_unavailable: The nightly rate could not be read just now. Retryable: ask again in a few seconds. unpriced_category: Not sold online by the night, day or trip."
                              },
                              "field": {
                                "type": "string",
                                "enum": [
                                  "startTime",
                                  "endTime"
                                ],
                                "description": "With outside_hours: which time to move."
                              },
                              "message": {
                                "type": "string",
                                "description": "The reason in words a guest can act on."
                              },
                              "retryable": {
                                "type": "boolean",
                                "description": "True when asking again in a few seconds can succeed."
                              },
                              "available": {
                                "type": [
                                  "boolean",
                                  "null"
                                ],
                                "description": "Whether the line is free for those dates in the number asked for — the same counts checkout refuses on. Null where the product is not counted."
                              },
                              "unitsLeft": {
                                "type": "integer",
                                "description": "How many are free for those dates, before this line."
                              },
                              "unitsTotal": {
                                "type": "integer"
                              },
                              "availability": {
                                "type": "object",
                                "description": "Present when `available` is false.",
                                "properties": {
                                  "reason": {
                                    "type": "string",
                                    "enum": [
                                      "one_unit_only",
                                      "over_capacity",
                                      "sold_out",
                                      "not_enough_left",
                                      "no_room_at_the_dock",
                                      "boat_too_long"
                                    ],
                                    "description": "one_unit_only: A cottage is one unit: quantity above 1 cannot be booked. over_capacity: More guests than the cottage sleeps. sold_out: Nothing is free for those dates. not_enough_left: Fewer are free than were asked for. `unitsLeft` says how many. no_room_at_the_dock: The boat does not fit at the dock on those nights. `longestBoatThatFitsFt` says what does. boat_too_long: The boat is longer than the dock takes for any one boat. `maxBoatLengthFt` says the limit."
                                  },
                                  "message": {
                                    "type": "string"
                                  },
                                  "unitsLeft": {
                                    "type": "integer"
                                  },
                                  "unitsTotal": {
                                    "type": "integer"
                                  },
                                  "sleeps": {
                                    "type": "integer"
                                  },
                                  "longestBoatThatFitsFt": {
                                    "type": "integer"
                                  }
                                }
                              },
                              "rate": {
                                "type": "object",
                                "description": "Which rate was charged (boats and charters).",
                                "properties": {
                                  "label": {
                                    "type": "string"
                                  },
                                  "periods": {
                                    "type": "integer"
                                  },
                                  "summary": {
                                    "type": "string"
                                  },
                                  "coversHours": {
                                    "type": "number"
                                  },
                                  "chargedByTime": {
                                    "type": "boolean"
                                  }
                                }
                              },
                              "notes": {
                                "type": "array",
                                "items": {
                                  "type": "string"
                                },
                                "description": "Anything assumed in pricing the line, e.g. a boat length that was not given."
                              },
                              "opensInCart": {
                                "type": "boolean"
                              },
                              "leftOutBecause": {
                                "type": "string"
                              },
                              "guests": {
                                "type": "integer"
                              }
                            }
                          }
                        },
                        "totals": {
                          "type": "object",
                          "description": "The whole basket, the way checkout charges it. Quote a guest from `dueAtCheckoutCents` (and say how much of it is refundable deposit), not from the lines alone.",
                          "properties": {
                            "currency": {
                              "type": "string",
                              "enum": [
                                "CAD"
                              ]
                            },
                            "itemsCents": {
                              "type": "integer",
                              "description": "The priced lines added up."
                            },
                            "fees": {
                              "type": "array",
                              "items": {
                                "type": "object",
                                "properties": {
                                  "label": {
                                    "type": "string"
                                  },
                                  "amountCents": {
                                    "type": "integer",
                                    "description": "Canadian cents."
                                  }
                                }
                              },
                              "description": "A cleaning fee for each cottage."
                            },
                            "feesCents": {
                              "type": "integer",
                              "description": "Canadian cents."
                            },
                            "taxes": {
                              "type": "array",
                              "items": {
                                "type": "object",
                                "properties": {
                                  "label": {
                                    "type": "string"
                                  },
                                  "amountCents": {
                                    "type": "integer",
                                    "description": "Canadian cents."
                                  }
                                }
                              },
                              "description": "Each tax by what it is charged on. Parking carries none."
                            },
                            "taxCents": {
                              "type": "integer",
                              "description": "Canadian cents."
                            },
                            "totalCents": {
                              "type": "integer",
                              "description": "What the trip costs: lines, fees and taxes."
                            },
                            "refundableDepositCents": {
                              "type": "integer",
                              "description": "Held on the card at checkout and returned after the rental. Not part of the trip's cost."
                            },
                            "dueAtCheckoutCents": {
                              "type": "integer",
                              "description": "What the card is charged at checkout: totalCents plus refundableDepositCents."
                            },
                            "complete": {
                              "type": "boolean",
                              "description": "False when a line could not be priced; the figures then leave that line out."
                            },
                            "bookable": {
                              "type": "boolean",
                              "description": "True when every line is priced and free for its dates in the number asked for."
                            },
                            "notes": {
                              "type": "array",
                              "items": {
                                "type": "string"
                              }
                            }
                          }
                        },
                        "opensWith": {
                          "type": "integer"
                        },
                        "leftOut": {
                          "type": "integer"
                        },
                        "notUnderstood": {
                          "type": "integer"
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
  },
  "x-mcp": {
    "description": "The same answers as tools, for an assistant that speaks the Model Context Protocol. Streamable HTTP, no key to connect, read-only: it cannot hold, book or pay. One tool, get_trip_summary, reads a guest's trip and takes the link that guest made for their assistant.",
    "url": "https://experiencebamfield.com/mcp",
    "transport": "streamable-http",
    "authentication": "none",
    "tools": [
      "list_products",
      "get_product",
      "check_availability",
      "price_cottages",
      "quote_trip",
      "build_cart_link",
      "camping_permits",
      "list_photos",
      "get_trip_summary"
    ]
  },
  "x-cart-link": {
    "description": "The link that fills the guest's cart. It carries product ids, dates and small numbers only — no price and no personal details. The site reads the price and availability again when it is opened, and checkout checks once more. Opening it adds its lines to the cart the guest already has. Check a link without opening it with previewCartLink. Fishing charters are not carried: they go into the cart from their own page.",
    "format": "https://experiencebamfield.com/book?l=PRODUCT~FROM~TO~HOWMANY~GUESTS[~BOATFEET]",
    "example": "https://experiencebamfield.com/book?l=woods-end-castaway-cottage~2027-07-10~2027-07-13~1~4&l=bac-sea-kayak-single~2027-07-11~2027-07-13~2~4",
    "maxLines": 12
  }
}