Files
frommeandClaude Opus 5.5 2bd663a9bf docs: CMS API and calendars
docs/cms-api.md describes the endpoints TYPO3 serves to MyE&P and the
chatbot (/api/hotel, /api/product, /api/calendar-hotels,
/api/content/*): key, parameters, responses, errors. It is the other
side of MyE&P's API consumer guide.

docs/calendars.md covers the price calendar, the Belegungskalender and
the iCal feed: which product gets which, the MyE&P endpoints behind
them, caching, troubleshooting, and the rule that a product's calendar
hotel must carry the BusPro code, not a group house's CMS code (which
broke three example trips).

The README replaces the TYPO3 base distribution boilerplate and links
both. The plugin renaming SQL in ep_products' README is obsolete since
the migration. The cmsApiKey description names /api/calendar-hotels.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-29 15:37:36 +01:00

102 lines
4.4 KiB
Markdown

# CMS API
Endpoints TYPO3 serves to other systems: **MyE&P** (hotel and product content, calendar hotels)
and the **chatbot** (content feed). They are answered by `EP\EpProducts\Middleware\ApiMiddleware`
before the page resolver, so they do not depend on a page and are never page-cached.
The other side of these contracts is documented in MyE&P's `docs/api-consumer-guide.md`
(`EP/myep`).
## Authentication
All endpoints need the shared key `cmsApiKey` from the `ep_products` extension configuration
(Settings → Extension Configuration → ep_products, tab ContentFeed):
- header `X-Api-Key: <key>` (preferred, query strings end up in access logs), or
- query parameter `?key=<key>`.
A missing or wrong key answers `403 {"message": "Invalid or missing api key"}`. An empty
`cmsApiKey` denies every request. MyE&P sends the key as `APP_CMS_API_KEY`; both sides must be
changed together.
## `GET /api/hotel?hotel=<code>`
Content of one hotel, for MyE&P's group house pages and booking flow.
- `hotel`: the TYPO3 hotel code or one of its matchcode aliases. In MyE&P this is the
accommodation's **CMS code** (e.g. `DGSGRP`), which can differ from its calendar code.
- `400` without `hotel`, `404 {"message": "Hotel not found"}` for an unknown code.
```json
{
"name": "…", "code": "…",
"description": "…", "features": "…", "room_types": "…",
"additional_information": "…", "address": "…",
"images": {"original": [{"url": "…", "alt": "…", "title": "…"}], "resized": […]},
"icons": […],
"region": {"name": "…", "latitude": …, "longitude": …, "webcam": "…", "ski_area": "…",
"ski_area_extended": "…", "description": "…", "news": "…", "length": …,
"altitude": …, "lifts": …, "images": {…}, "region_maps": [{"url": "…", "alt": "…", "title": "…"}]}
}
```
## `GET /api/product?product=<code>[&hotel=<code>]`
Content of one product, optionally with a hotel.
- `product`: the product code. `400` without it, `404 {"message": "Product not found"}` for an
unknown code.
- `hotel` (optional): adds `hotel` with the same content as `/api/hotel`.
```json
{
"product": {"name": "…", "code": "…", "images": {…}},
"country": {"name": "…"},
"region": {…},
"city": {"name": "…"},
"hotel": {…}
}
```
## `GET /api/calendar-hotels`
The calendar hotel of every visible product: the hotel codes whose availability TYPO3 shows
(iCal feed, availability calendar, price calendar). MyE&P syncs its contingent snapshot for
these codes in addition to its accommodations, so hotels offered only in winter, which have no
accommodation in MyE&P, have availability too. See [calendars.md](calendars.md).
- Only products and hotels that are not hidden or deleted; hotels without a code are left out.
- Sorted by code; products by uid. Names have line breaks collapsed.
- `season`: `"s"` (summer), `"w"` (winter) or `null`.
```json
[
{"code": "DKI", "name": "Sportclub Schwendi", "products": [
{"uid": 155, "name": "Sportclub Schwendi", "season": "w"},
{"uid": 362, "name": "Gruppenhaus Schwendi", "season": "s"}
]}
]
```
Every code in this list should be a **BusPro calendar code**. A group house's content record
(`…GRP`, `…SO`) set as a product's calendar hotel puts its CMS code in here, which bpn-connect does
not know (see [calendars.md](calendars.md#the-calendar-hotel-field)).
## `GET /api/content/manifest` and `GET /api/content/records?type=<type>&uids=<uids>`
The content feed for the chatbot (`EP\EpProducts\ContentFeed\ContentFeedApi`).
- `manifest`: everything the feed publishes (pages of the configured sites and records), with a
modification timestamp per item. Cached for 10 minutes.
- `records`: up to 100 records of one type per request. `type` is one of `product`, `hotel`,
`region`, `country`, `city`, `concept`, `news`; `uids` comma-separated. Uids that are no longer
eligible are left out, not answered with an error (the consumer reads them as deleted).
- `400` for an unknown type, no uids or more than 100 uids; `404` for other paths.
- `503` with `Retry-After: 3600` when the feed is switched off (`contentFeedEnabled = 0`, the kill
switch for when the host is struggling), `503` when `contentFeedSites` is empty. The consumer
keeps what it has on a 503; nothing is deleted on the chatbot side.
Configuration (extension configuration `ep_products`, tab ContentFeed): `contentFeedEnabled`,
`contentFeedSites`, `contentFeedLanguages`, `contentFeedExcludedPages`,
`contentFeedNewsDetailPages`.