Get product calendar

PUBLIC

Returns a product with its calendar day records for the given date range.

Recent Requests
Log in to see full request history
TimeStatusUser Agent
Retrieving recent requests…
LoadingLoading…
📘

CacheCache-Control: public, max-age=300 (5 minutes)

Part of the catalog — The product catalog explains how merchants, products, calendars and timeslots fit together.

Why this endpoint exists

Dated products are priced per day. A Tuesday in November and a Saturday in July are different prices with different discounts and different remaining stock. This endpoint is the calendar behind the date picker: for each day in a range it returns what the customer pays, what they save, and whether any capacity is left.

The five-minute cache is the shortest in the catalog, and deliberately so — stock moves. Everything else about a product can be an hour stale without harm; availability cannot.

Skip this endpoint entirely when the product reports no_date: true. There are no day records to fetch.

How to use it

curl "https://api.acc.funtrips.io/v2/merchants/$MERCHANT/products/$PRODUCT/calendar?from=2026-09-04&to=2026-09-18" \
  -H "x-campaign-id: $CAMPAIGN_ID"
{
  "data": {
    "product": {
      "id": "d4b8f2a1-6c3e-4957-b18d-2f5a9c7e3b04",
      "product_type": "TICKET",
      "physical_product": false,
      "unit_price": { "value": "24.00", "currency": "EUR" },
      "list_price": { "value": "24.00", "currency": "EUR" },
      "booking_costs": { "value": "0.99", "currency": "EUR" },
      "visit_guarantee_cost": { "value": "1.50", "currency": "EUR" }
    },
    "days": [
      {
        "date": "2026-09-04",
        "effective_price": { "value": "18.50", "currency": "EUR" },
        "list_price": { "value": "24.00", "currency": "EUR" },
        "discount_percentage": 22.9,
        "discount_amount": { "value": "5.50", "currency": "EUR" },
        "available_stock": 142
      },
      {
        "date": "2026-09-05",
        "effective_price": { "value": "24.00", "currency": "EUR" },
        "list_price": { "value": "24.00", "currency": "EUR" },
        "discount_percentage": 0,
        "discount_amount": { "value": "0.00", "currency": "EUR" }
      }
    ]
  }
}

Query parameters

ParameterTypeDefaultNotes
fromdate YYYY-MM-DDtodayInclusive start
todate YYYY-MM-DDfrom + 7 daysInclusive end

Both are optional. Omit both and you get today through today + 7 days. Both bounds are inclusive, so from=2026-09-04&to=2026-09-04 returns exactly one day.

Request the range your UI actually shows. A month view asks for a month; do not fetch a year to render a fortnight.

Reading the days array

A missing date is not a bookable date. days contains only days with a calendar record — gaps mean closed or not on sale, and your date picker should disable any date absent from the response. Never interpolate.

FieldMeaning
dateThe calendar day, YYYY-MM-DD
effective_priceWhat the customer pays per unit that day
list_pricePre-discount reference — the strikethrough
discount_percentageSaving as a percentage
discount_amountSaving as money
available_stockUnits left, nullable

available_stock being null means unlimited, not zero

null is the "no capacity limit configured for this day" signal. Treating it as 0 hides every unconstrained day in your picker — a bug that looks like a sold-out product.

const bookable = day.available_stock === null || day.available_stock > 0;

Days with available_stock: 0 are genuinely sold out. Show them disabled rather than hiding them, so the customer can see they picked a popular date.

The prices here are per-day, and not the whole bill

effective_price is one unit on one day, before per-ticket surcharges and before customer-specific discounts from wallet, vouchers or membership. It is the right number for the date picker and the wrong number for the order summary. The order summary comes from create checkout.

The product wrapper is deliberately slim

data.product carries only what the calendar view owns: ids, type, the physical flag, and the base prices. title, conditions and age_group are not here. They belong to the products view — fetch them from get product or the product list.

The practical pattern for a product page is one metadata call plus one calendar call, the metadata cached for two hours and the calendar re-fetched as the customer pages through months.

Timeslots are a separate call

This endpoint answers "which days, at what price". When the product reports requires_timeslot: true, the customer must also pick a time of day — that comes from POST /fulfillment/timeslots for the chosen date. A day present here with stock available can still return zero timeslots, and that combination means the day is not bookable after all.

Errors

StatustypeCause
400Malformed date (must be YYYY-MM-DD), non-UUID path parameter, or missing x-campaign-id
404urn:qup:error:product-not-foundNo such product under that merchant in this campaign
500urn:qup:error:product-internal-errorUnexpected fault

A 200 with an empty days array is not an error: the product exists but has no bookable days in the range you asked for. Widen the range, or check sold_out and first_available_date on the product.

Path Params
uuid
required
uuid
required
Query Params
date

Start date (inclusive, YYYY-MM-DD). Defaults to today when omitted.

date

End date (inclusive, YYYY-MM-DD). Defaults to from + 7 days when omitted. If neither from nor to is supplied, the response covers today through today + 7 days.

Headers
uuid
required

Campaign scope for this request. Filters content to the specified campaign.

string

RFC 7231 language preference. The best match against supported locales (nl-NL, en-GB, de-DE) is selected. Defaults to nl-NL when omitted.

string
enum
Defaults to application/json

Generated from available response content types

Allowed:
Responses

Language
URL
LoadingLoading…
Response
Click Try It! to start a request and see the response here! Or choose an example:
application/json
application/problem+json