PUBLIC
Returns a paginated list of merchants for the given campaign.
| Time | Status | User Agent | |
|---|---|---|---|
Retrieving recent requests… | |||
Cache —Cache-Control: public, max-age=14400(4 hours)
Part of the catalog — The product catalog explains how merchants, products, calendars and timeslots fit together.
Why this endpoint exists
A merchant is the venue or operator whose experience you are selling — a zoo, a theme park, a museum, a spa. It is the entity the customer recognises and chooses, and it owns the products they eventually buy.
This is the plain, complete, paginated enumeration of the merchants in your campaign. It is what you build a browse page or a category grid on. When the customer knows what they want and types it, use broad search or custom search instead — this endpoint does not filter.
How to use it
curl "https://api.acc.funtrips.io/v2/merchants?limit=24" \
-H "x-campaign-id: 3c1e5a90-7b2d-4f61-9a83-1d4e7f2b8c05" \
-H "Accept-Language: nl-NL"{
"data": [
{
"id": "8a2f4c61-9d3e-4b57-a0c8-1e5f7b2d9a30",
"alias": "amsterdam-zoo",
"title": "Amsterdam Zoo",
"content": { "…": "descriptions, images" },
"options": { "…": "merchant flags" },
"address": { "…": "street, city, coordinates" },
"categories": [ { "…": "category" } ],
"tags": ["family", "outdoor"],
"max_discount": { "…": "deepest discount available" },
"best_effective_price": { "value": "18.50", "currency": "EUR" },
"best_list_price": { "value": "24.00", "currency": "EUR" },
"last_updated": "2026-09-01T08:12:44Z"
}
],
"meta": {
"correlation_id": "…",
"item_count": 24,
"pagination": { "limit": 24, "next_token": "eyJwayI6…" }
}
}Query parameters
| Parameter | Type | Default | Notes |
|---|---|---|---|
limit | integer | 20 | 1–100 |
next_token | string | — | Cursor from meta.pagination.next_token |
The fields that drive a merchant tile
Most of the payload is content you render as-is. Three fields deserve explanation because they encode pricing decisions, not content.
best_effective_price is the actual price a customer pays for the cheapest discounted product this merchant offers — the "from €18.50" headline on the tile. best_list_price is that same product's pre-discount price, the "was €24.00" strikethrough. They are a pair: render both or neither.
Both are present only when the merchant has at least one discount-eligible product. A merchant with no discounts has no headline price, and your tile needs a sensible layout for that case. Do not fall back to 0 — an absent price means "no promotional price to show", not "free".
max_discount is the deepest discount available across the merchant's products, for a "up to 30% off" badge.
These are computed server-side from stock-aware availability: a product whose remaining days are all sold out does not contribute. That means the headline price you show is one the customer can actually buy, which is the whole point of computing it centrally rather than in your client.
alias is a stable URL-safe slug (amsterdam-zoo). Use it in your own URLs; use id for API calls — including for the merchant detail read.
Ordering
Merchants come back in key order — stable between requests, but not alphabetical, not chronological, and not ranked. If you need a specific order in your UI, sort client-side after collecting the pages, or use custom search, which ranks by relevance and can sort by distance.
Errors
| Status | type | Cause |
|---|---|---|
500 | urn:qup:error:merchant-internal-error | Unexpected fault |
That is genuinely the only documented failure. A campaign with no merchants returns 200 with an empty data array — not a 404. An unknown x-campaign-id also yields an empty list rather than an error, because the campaign scope is applied as a filter here. If you get an unexpectedly empty catalog, verify your x-campaign-id first — that is the usual cause, and this endpoint will not tell you.
