fepli
Use cases

Offers on your website

The published offers of your holiday programme can be read without a token. This guide shows how to put them on a municipal website, a partner's site or an app, and how to keep them up to date.

Embeds will put the offers on your website with a script and one HTML element, no server code needed. They are a prototype for now; until they are released, use the API as described here.

What you get

Without a token, GET /offers returns the offers the public fepli website shows:

  • offers that are published,
  • in an edition that is online,
  • and public, not reachable through a private link only.

Each offer comes with its public fields: name, teaser, description, dates, fee, age range, meeting point, places left, images, categories, organisers and the link to its page on the fepli website. Internal fields such as the contact person or the application counts are left out.

Public access must be switched on under Einstellungen → Integrationen → API → Öffentlicher Zugriff. It is on by default.

Fetch on your server, not in the browser

Anonymous requests are limited to 5 per hour per IP address (see Rate limits). Calling the API from your visitors' browsers would therefore work for the first few visitors of an office network and then fail. Instead:

  1. Fetch the offers on your server.
  2. Cache the result, in a file, a database or your CMS's cache.
  3. Serve your pages from the cache and refresh it every few hours.

A complete refresh is also the simplest way to stay correct: offers that were unpublished or deleted just don't come back.

Fetch and cache the offers of an edition

import { readFile, writeFile } from 'node:fs/promises'

const API = 'https://ferienpass-musterstadt.de/api'
const CACHE = './offers.json'
const MAX_AGE = 3 * 60 * 60 * 1000 // three hours

export async function getOffers() {
  try {
    const cached = JSON.parse(await readFile(CACHE, 'utf8'))
    if (Date.now() - cached.fetchedAt < MAX_AGE) return cached.offers
  } catch {}

  const offers = []
  for (let page = 1; ; page++) {
    const query = new URLSearchParams({
      edition: 'herbstferien-2026',
      itemsPerPage: 200,
      page,
    })
    const response = await fetch(`${API}/offers?${query}`)
    if (!response.ok) throw new Error(`fepli API: ${response.status}`)
    const batch = await response.json()
    offers.push(...batch)
    if (batch.length < 200) break
  }

  await writeFile(CACHE, JSON.stringify({ fetchedAt: Date.now(), offers }))
  return offers
}

With 200 offers per page, an edition of 350 offers takes two requests per refresh, well within the limit.

Choose what to show

Narrow the list with filters. They can be combined:

You wantUse
one editionedition=herbstferien-2026 (alias or UUID)
one categorycategory=sport-bewegung
one organiserhost=stadtjugendring-musterstadt
offers on certain daysfrom=2026-10-12&until=2026-10-18 (for offers with several dates, see the filter)
a search boxq=Fahrrad
a different ordersort=name, sort=begin&order=desc

Find the aliases of editions, categories and organisers in the offers themselves: every offer carries edition, categories and hosts as references with uuid, name and alias.

Build an offer card

The fields you'll most likely use:

FieldHow to show it
name, teasertitle and subtitle
descriptionMarkdown. Convert it to HTML with a Markdown library.
datesa list of begin/end timestamps. An offer can have several dates.
feecents. 750 is €7.50; missing or 0 means free.
minAge, maxAge"for ages 8 to 12". Either can be missing.
vacancies, fullyBookedplaces left. vacancies is missing when places are unlimited.
requiresApplication, applicationDeadlinewhether and until when families have to apply
meetingPoint, meetingAddress, meetingLat, meetingLngwhere to go, for a map
mediaabsolute URLs of the offer's images, at most 3000 × 3000 pixels
statuscontains cancelled when a published offer was called off. Show it as cancelled rather than dropping it.
urlthe offer's page on the fepli website

Families apply on the fepli website, not on yours. Link each card to its url, and they land on the offer's page with the application form.

An offer, as anonymous callers see it

{
  "uuid": "0192a4f1-6e3b-7c85-b1d2-8f4e6a9c3b03",
  "name": "Fahrradtour durch den Stadtpark",
  "alias": "fahrradtour-durch-den-stadtpark",
  "edition": {
    "uuid": "01913e5c-2b4a-7d10-9f3e-6a1c2e7b4d01",
    "name": "Herbstferien 2026",
    "alias": "herbstferien-2026"
  },
  "hosts": [
    {
      "uuid": "01905a7e-8c21-7b3f-a0d4-3e9f1c6b2a02",
      "name": "Stadtjugendring Musterstadt e.V.",
      "alias": "stadtjugendring-musterstadt"
    }
  ],
  "status": ["published"],
  "teaser": "Mit dem Rad durch den Park – mit Picknick am See.",
  "description": "Eine **geführte Fahrradtour** für Kinder ab 8 Jahren.\n\nBitte mitbringen:\n\n- Fahrrad\n- Helm",
  "dates": [
    {
      "uuid": "0192a4f2-0a11-7e6c-9d38-2b7f5c1e8a04",
      "begin": "2026-10-14T09:00:00+02:00",
      "end": "2026-10-14T12:00:00+02:00"
    }
  ],
  "fee": 750,
  "minAge": 8,
  "maxAge": 12,
  "maxParticipants": 15,
  "vacancies": 4,
  "fullyBooked": false,
  "requiresApplication": true,
  "onlineApplication": true,
  "applicationDeadline": "2026-09-30T23:59:59+02:00",
  "meetingPoint": "Haupteingang Stadtpark",
  "meetingAddress": "Parkstraße 1, 12345 Musterstadt",
  "meetingLat": 52.520008,
  "meetingLng": 13.404954,
  "bring": "Fahrrad und Helm",
  "wheelchairAccessible": false,
  "categories": [
    {
      "uuid": "018f2c3d-4e5f-7a6b-8c7d-9e0f1a2b3c12",
      "name": "Sport & Bewegung",
      "alias": "sport-bewegung"
    }
  ],
  "url": "https://ferienpass-musterstadt.de/angebote/herbstferien-2026/1C8wV6pQk2Tz9RfXh4MbNd-fahrradtour-durch-den-stadtpark",
  "media": [
    "https://ferienpass-musterstadt.de/assets/images/f/fahrradtour-3000x2000.jpg"
  ],
  "createdAt": "2026-08-03T10:12:44+02:00",
  "modifiedAt": "2026-09-15T16:40:02+02:00"
}

Fields without a value are left out of the response. Treat a missing field as empty. See Conventions.

Cancelled offers, drafts and private offers

A published offer that is called off stays in the list, with status reading ["published", "cancelled"], so families who saw it can find out it was cancelled.

Offers that are still being prepared and offers published only through a private link are never returned without a token. If your integration needs them, for example an organiser's own site showing their drafts, use a token with read access.

Was this page helpful?