Offers
An offer (Angebot) is one activity of an edition: a course, an excursion, a workshop, run by one or more organisers on one or more dates. Offers are the one part of the API that is public: the published offers can be read without a token.
The offer model
Fields marked with a token are returned to authenticated callers only. Fields without a value are left out of responses.
Properties
- Name
uuid- Type
- string
- Description
The offer's identifier.
- Name
name- Type
- string
- Description
The title of the offer.
- Name
alias- Type
- string
- Description
A URL-safe version of the name.
- Name
edition- Type
- reference
- Description
The edition the offer belongs to.
- Name
hosts- Type
- reference[]
- Description
The organisers who run the offer.
- Name
status- Type
- string[]
- Description
The places of the workflow the offer is in:
draft,finalized,reviewed,published,cancelled,completed. Usually one. A published offer keepspublishedwhen it is cancelled or completed, so a cancelled offer reads["published", "cancelled"].
- Name
teaser- Type
- string
- Description
A one-line summary.
- Name
description- Type
- string
- Description
The full description, formatted as Markdown.
- Name
dates- Type
- object[]
- Description
The dates of the offer. Each has a
uuid, abeginand usually anendtimestamp, and for recurring dates an iCalendarrecurrencerule (RRULE).
- Name
fee- Type
- integer
- Description
The fee in cents. Missing or
0means the offer is free.
- Name
minAge- Type
- integer
- Description
The minimum age of participants, in years.
- Name
maxAge- Type
- integer
- Description
The maximum age of participants, in years.
- Name
minParticipants- Type
- integer
- Description
How many participants the offer needs to take place.
- Name
maxParticipants- Type
- integer
- Description
How many participants the offer takes. Missing when unlimited.
- Name
vacancies- Type
- integer
- Description
Places left:
maxParticipantsminus confirmed and waitlisted applications. Missing when places are unlimited.
- Name
fullyBooked- Type
- boolean
- Description
Whether no places are left.
- Name
requiresApplication- Type
- boolean
- Description
Whether participants have to apply. If not, they just turn up.
- Name
onlineApplication- Type
- boolean
- Description
Whether families can apply on the fepli website.
- Name
applicationDeadline- Type
- timestamp
- Description
The last moment to apply.
- Name
meetingPoint- Type
- string
- Description
A short label for where to meet, such as "Haupteingang Stadtpark".
- Name
meetingAddress- Type
- string
- Description
The street address of the meeting point.
- Name
meetingLat- Type
- number
- Description
The latitude of the meeting point (WGS 84).
- Name
meetingLng- Type
- number
- Description
The longitude of the meeting point (WGS 84).
- Name
bring- Type
- string
- Description
What participants should bring.
- Name
applyText- Type
- string
- Description
Extra information shown when applying, formatted as Markdown.
- Name
wheelchairAccessible- Type
- boolean
- Description
Whether the offer is wheelchair accessible.
- Name
accessibility- Type
- string[]
- Description
Further accessibility features.
- Name
categories- Type
- reference[]
- Description
The categories the offer is filed under.
- Name
url- Type
- string
- Description
The offer's page on the fepli website, where families apply.
- Name
media- Type
- string[]
- Description
Absolute URLs of the offer's images, scaled to at most 3000 × 3000 pixels.
- Name
createdAt- Type
- timestamp
- Description
When the offer was created.
- Name
modifiedAt- Type
- timestamp
- Description
When the offer was last changed.
- Name
publish- Type
- string
- Access
- with a token
- Description
public, orprivate_linkfor an offer that can only be reached through its private link.
- Name
comment- Type
- string
- Access
- with a token
- Description
An internal note, never shown publicly.
- Name
contact- Type
- object[]
- Access
- with a token
- Description
The contact persons, as entered: objects with
name,emailandphone.
- Name
collectDateOfBirth- Type
- boolean
- Access
- with a token
- Description
Whether the application form asks for the participant's date of birth.
- Name
disableOnlinePayment- Type
- boolean
- Access
- with a token
- Description
Whether online payment is switched off for this offer.
- Name
attendances- Type
- object
- Access
- with a token
- Description
How many applications the offer has, by state:
confirmed,waitlisted,waiting,withdrawnandrejected.totalcounts every application except withdrawn and unfulfilled ones.
- Name
participantListRecipientEmails- Type
- string[]
- Access
- with a token
- Description
Extra e-mail addresses that receive the participant list.
- Name
applicationQuestions- Type
- object[]
- Access
- with a token
- Description
The extra questions of the application form, each with a
uuid, thequestion, itstype(ynyes/no,intnumber,txttext,fileupload), whether it isrequired, and for uploads the acceptedfileTypes(image,pdf).
- Name
variantOf- Type
- reference
- Access
- with a token
- Description
The offer this one is a variant of, for example another date of the same course.
- Name
variants- Type
- reference[]
- Access
- with a token
- Description
The variants of this offer. Create one with
POST /offers/{uuid}/variants.
- Name
transitions- Type
- string[]
- Access
- with a token
- Description
The workflow transitions you may apply right now.
The offer workflow
An offer moves through the same states as in the admin. An organiser drafts it and submits it (finalize); the team reviews it (approve) and publishes it (publish). Apply a transition with POST /offers/{uuid}/transition.
| Transition | From | To | Who may apply it |
|---|---|---|---|
finalize | draft | finalized | anyone who can see the offer |
to_draft | finalized | draft | admins; organisers if the edition lets them publish |
approve | finalized, unless completed | reviewed | admins; organisers if the edition lets them publish |
unapprove | reviewed | finalized | admins; organisers if the edition lets them publish |
publish | reviewed | published | admins; organisers if the edition lets them publish |
unpublish | published | reviewed | admins; organisers if the edition lets them publish |
cancel | published and not cancelled, before its last date has ended | adds cancelled | admins; organisers if the installation allows it |
relaunch | cancelled, before its last date has ended | published | as for cancel |
complete | published, after its last date has ended | adds completed | admins; organisers who can see the offer |
A completed offer can't be cancelled or relaunched any more.
To take a reviewed offer back to draft, apply unapprove and then
to_draft. A reviewed offer lists to_draft_2 in transitions: that is the
admin's name for the same step, and the API doesn't accept it yet.
List all offers
Returns a page of offers, ordered by their first date.
- Without a token: the published, public offers of the editions that are online, with the public fields.
- With a token: every offer you could open in the admin, in any state, with all fields. Admins see all offers; organiser users see their organisers' offers and offers shared with them. A token whose owner is neither an admin nor a member of an organiser sees no offers at all, not even the published ones: leave the token out to read those.
Optional filters
- Name
q- Type
- string
- Description
Only offers whose name contains this text.
- Name
edition- Type
- string
- Description
Only offers of this edition (UUID or alias).
- Name
host- Type
- string
- Description
Only offers of this organiser (UUID or alias).
- Name
category- Type
- string
- Description
Only offers in this category (UUID or alias).
- Name
status- Type
- string
- Description
Only offers in these workflow places, comma-separated:
draft,finalized,reviewed,published,cancelled,completed.publishedalso matches offers that were cancelled after publication.
- Name
from- Type
- date
- Description
Only offers with a date that begins on or after this day.
- Name
until- Type
- date
- Description
Only offers with a date that begins on or before this day. Combined with
from, an offer with several dates matches if one date is on or afterfromand one (not necessarily the same) is on or beforeuntil.
- Name
modifiedSince- Type
- timestamp
- Description
Only offers changed since this moment.
- Name
sort- Type
- string
- Description
begin(default, the first date),name,createdAtormodifiedAt.
- Name
order- Type
- string
- Description
asc(default) ordesc.
- Name
page, itemsPerPage- Type
- integer
- Description
See Pagination.
Request
curl -G https://ferienpass-musterstadt.de/api/offers \
-d edition=herbstferien-2026 \
-d from=2026-10-12
Response
[
{
"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.",
"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"
}
]
Retrieve an offer
Returns one offer. The same visibility rules apply as for the list: without a token, only a published, public offer of an online edition can be retrieved. Anything else is a 404.
With a token, the offer includes the internal fields, among them the application counts and the transitions you may apply.
Request
curl https://ferienpass-musterstadt.de/api/offers/0192a4f1-6e3b-7c85-b1d2-8f4e6a9c3b03 \
-H "Authorization: Bearer $FEPLI_TOKEN"
Response (with a token, excerpt)
{
"uuid": "0192a4f1-6e3b-7c85-b1d2-8f4e6a9c3b03",
"name": "Fahrradtour durch den Stadtpark",
"status": ["published"],
"…": "…",
"publish": "public",
"comment": "Leihräder beim ADFC angefragt.",
"contact": [
{
"name": "Jonas Becker",
"email": "j.becker@sjr-musterstadt.de",
"phone": "0123 456789"
}
],
"collectDateOfBirth": false,
"disableOnlinePayment": false,
"attendances": {
"confirmed": 11,
"waitlisted": 0,
"waiting": 2,
"withdrawn": 1,
"rejected": 0,
"total": 13
},
"participantListRecipientEmails": [],
"applicationQuestions": [
{
"uuid": "0192a4f4-1b2c-7d3e-8f4a-5b6c7d8e9f13",
"question": "Kann Ihr Kind sicher Fahrrad fahren?",
"type": "yn",
"required": true,
"fileTypes": []
}
],
"variants": [],
"transitions": ["cancel", "unpublish"]
}
Create an offer
Creates an offer in the state draft. Admins can create offers in any edition. Organiser users can create them for their own organisers, in editions whose editing phase is running right now.
When a person creates the offer, they receive its participant list. When an organiser user belongs to exactly one organiser, that organiser is added automatically. Send contact to name the contact persons; the API doesn't fill it in for you.
Required attributes
- Name
name- Type
- string
- Description
The title of the offer.
- Name
edition- Type
- string
- Description
The edition, as UUID or alias. Organiser users must name one; without it the answer is
403.
Optional attributes
- Name
hosts- Type
- string[]
- Description
Organisers, as UUIDs or aliases. Organiser users may only name their own.
- Name
dates- Type
- object[]
- Description
Dates, each with
begin, and optionallyendandrecurrence.
- Name
categories- Type
- string[]
- Description
Categories, as UUIDs or aliases.
- Name
publish- Type
- string
- Description
Send
public(the default). The API doesn't acceptprivate_linkyet; to publish an offer through its private link only, set that in the admin.
- Name
contact- Type
- object[]
- Description
Contact persons: objects with
name,emailandphone.
- Name
participantListRecipientEmails- Type
- string[]
- Description
Extra recipients of the participant list.
You can also send teaser, description, fee, minAge, maxAge, minParticipants, maxParticipants, requiresApplication, onlineApplication, applicationDeadline, meetingPoint, meetingAddress, meetingLat, meetingLng, bring, applyText, wheelchairAccessible, accessibility, comment, collectDateOfBirth and disableOnlinePayment, as described in the model.
Ages range from 0 to 99, fees and participant numbers can't be negative, and short texts such as meetingPoint, bring, applyText and comment are limited to 255 characters.
Request
curl -X POST https://ferienpass-musterstadt.de/api/offers \
-H "Authorization: Bearer $FEPLI_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Töpfern für Anfänger",
"edition": "herbstferien-2026",
"hosts": ["stadtjugendring-musterstadt"],
"dates": [
{ "begin": "2026-10-20T14:00:00+02:00", "end": "2026-10-20T16:30:00+02:00" }
],
"fee": 500,
"minAge": 6,
"maxAge": 10,
"maxParticipants": 8,
"requiresApplication": true,
"onlineApplication": true,
"categories": ["kreativ"]
}'
Response (201 Created, excerpt)
{
"uuid": "0193c8a2-5d6e-7f70-9a8b-1c2d3e4f5a14",
"name": "Töpfern für Anfänger",
"alias": "toepfern-fuer-anfaenger",
"status": ["draft"],
"…": "…",
"transitions": ["finalize"]
}
Update an offer
Changes an offer. Send only the attributes you want to change. The attributes are the same as for creating an offer. null clears a field, except for name, edition, hosts, dates, categories, publish and the yes/no fields, which ignore it. An empty body is a 422.
Dates are replaced as a whole when you send dates. To keep an existing date, include its uuid; dates you leave out are deleted, and dates without a uuid are added.
Admins can change any offer. Organiser users can change their offers until they are reviewed or published, and only while the edition is open for editing (see the edition's editableForHosts). Otherwise the answer is 403.
Request
curl -X PATCH https://ferienpass-musterstadt.de/api/offers/0192a4f1-6e3b-7c85-b1d2-8f4e6a9c3b03 \
-H "Authorization: Bearer $FEPLI_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"maxParticipants": 18,
"dates": [
{
"uuid": "0192a4f2-0a11-7e6c-9d38-2b7f5c1e8a04",
"begin": "2026-10-14T09:00:00+02:00",
"end": "2026-10-14T12:30:00+02:00"
},
{ "begin": "2026-10-16T09:00:00+02:00", "end": "2026-10-16T12:30:00+02:00" }
]
}'
The response holds the changed offer.
Delete an offer
Deletes an offer with its applications. Responds with 204 No Content.
The deletion is refused with 409 while a paid receipt refers to one of the offer's applications. Reverse the receipt first, or cancel the offer instead. Organiser users can only delete offers that have no applications yet, while the edition is open for editing.
If the offer has variants, the next one takes its place as the main offer.
Request
curl -X DELETE https://ferienpass-musterstadt.de/api/offers/0193c8a2-5d6e-7f70-9a8b-1c2d3e4f5a14 \
-H "Authorization: Bearer $FEPLI_TOKEN"
Apply a transition
Moves the offer through its workflow. The offer's transitions list the ones you may apply right now. Anything else, including a transition you lack the permission for, is a 409 that names the possible transitions.
cancel and relaunch e-mail the families who applied, the same as in the admin.
Required attributes
- Name
transition- Type
- string
- Description
One of
finalize,to_draft,approve,unapprove,publish,unpublish,cancel,relaunch,complete.
Optional attributes
- Name
notifyText- Type
- string
- Description
For
cancelandrelaunch: the text of the e-mail to the families. Leave it out to use the configured text.
Request
curl -X POST https://ferienpass-musterstadt.de/api/offers/0192a4f1-6e3b-7c85-b1d2-8f4e6a9c3b03/transition \
-H "Authorization: Bearer $FEPLI_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"transition": "cancel",
"notifyText": "Leider muss die Fahrradtour wegen Sturmwarnung ausfallen."
}'
Response (excerpt)
{
"uuid": "0192a4f1-6e3b-7c85-b1d2-8f4e6a9c3b03",
"status": ["published", "cancelled"],
"…": "…",
"transitions": ["relaunch", "unpublish"]
}
Copy an offer
Copies an offer into a new offer in the state draft, like Angebot kopieren in the admin. Responds with 201 Created and the copy. The original is left as it is.
Without edition, the copy is made in the offer's own edition, dates included. With edition, it is made in that edition, and the dates and the application deadline stay behind: they belonged to the old holidays. Add the new ones with PATCH /offers/{uuid}. This also applies when you name the offer's own edition.
You need to see the original (404 otherwise) and be allowed to create an offer in the edition the copy lands in: admins always are, organiser users while that edition's editing phase is running (403 otherwise). An edition that doesn't exist is a 422.
What a copy carries over
The copy takes the texts and settings of the original: name, teaser, description, fee, ages, participant numbers, application settings and questions, meeting point, what to bring, accessibility, categories, organisers, contact persons, recipients of the participant list, the internal comment, publish and consent forms. Images and attachments are carried over too; the copy refers to the same files.
It doesn't take what the original has gathered: applications, comments, the history, variants, and the accounts the offer was shared with directly. A copy is always an offer of its own, even when the original is a variant. It gets a new uuid and its own private link. Its createdAt and modifiedAt are the moment of the copy.
Optional attributes
- Name
edition- Type
- string
- Description
The edition to copy into, as UUID or alias. Leave it out to copy within the offer's own edition, dates included.
The request needs a JSON body: send {} to copy within the edition.
Copying a whole edition
There is no call for that. List the offers with GET /offers?edition=… and copy each into the new edition. The admin's Aus Vorjahr kopieren leaves out the variants (offers with variantOf), so they don't turn into offers of their own; do the same if you want its result.
Request
curl -X POST https://ferienpass-musterstadt.de/api/offers/0192a4f1-6e3b-7c85-b1d2-8f4e6a9c3b03/copy \
-H "Authorization: Bearer $FEPLI_TOKEN" \
-H "Content-Type: application/json" \
-d '{"edition": "herbstferien-2027"}'
Response (201 Created, excerpt)
{
"uuid": "019a3b1e-7c2d-7e4f-8a5b-6c7d8e9f0a21",
"name": "Fahrradtour durch den Stadtpark",
"edition": {
"uuid": "019a3b10-2c3d-7e4f-9a5b-6c7d8e9f0a20",
"name": "Herbstferien 2027",
"alias": "herbstferien-2027"
},
"status": ["draft"],
"dates": [],
"fee": 750,
"…": "…",
"attendances": {
"confirmed": 0,
"waitlisted": 0,
"waiting": 0,
"withdrawn": 0,
"rejected": 0,
"total": 0
},
"transitions": ["finalize"]
}
Create a variant
Creates a variant of an offer: the same activity for another group of participants, usually on other dates. Like Neuen Termin erstellen in the admin. Responds with 201 Created and the new variant.
A variant is a copy in the state draft, in the same edition and with the same dates, that is linked to the main offer: its variantOf names the main offer, and the main offer lists it in variants. Change its dates with PATCH /offers/{uuid}. A variant of a variant is linked to the same main offer.
The request has no body. The same permissions apply as for a copy: 404 if you can't see the offer, 403 if you may not create an offer in its edition.
Request
curl -X POST https://ferienpass-musterstadt.de/api/offers/0192a4f1-6e3b-7c85-b1d2-8f4e6a9c3b03/variants \
-H "Authorization: Bearer $FEPLI_TOKEN"
Response (201 Created, excerpt)
{
"uuid": "019a3b2f-1d2e-7f30-8b4c-5d6e7f8a9b22",
"name": "Fahrradtour durch den Stadtpark",
"status": ["draft"],
"dates": [
{
"uuid": "019a3b2f-1d2f-7a41-9c5d-6e7f8a9b0c23",
"begin": "2026-10-14T09:00:00+02:00",
"end": "2026-10-14T12:00:00+02:00"
}
],
"…": "…",
"variantOf": {
"uuid": "0192a4f1-6e3b-7c85-b1d2-8f4e6a9c3b03",
"name": "Fahrradtour durch den Stadtpark",
"alias": "fahrradtour-durch-den-stadtpark"
}
}
Refill from the waiting list
Fills the free places of an offer from its waiting list, like Teilnahmeliste auffüllen in the admin. Families move up in the order of the waiting list (first come, first served), whatever application procedure the edition uses. Each family that moves up is confirmed, and its fee falls due as for any confirmation.
The work is done in the background, shortly after the call. The API answers 202 Accepted with the offer as it is at that moment; fetch its participant list a little later to see who moved up.
To add places, raise maxParticipants with PATCH /offers/{uuid}, then call this endpoint. That is what Anzahl Plätze erhöhen does in the admin.
The call is refused with 409 when there is nothing to do: the offer has no dates or its first date has begun, nobody is on the waiting list, or every place is taken. It needs the permission to change the offer: admins always have it; organiser users only until the offer is reviewed or published, while the edition is open for editing (403 otherwise).
Optional attributes
The request needs a JSON body: send {} for the defaults.
- Name
notify- Type
- boolean
- Description
E-mail the families who move up that they have a place. Defaults to
false.
- Name
commit- Type
- boolean
- Description
Record the confirmations now. Defaults to
true. Withfalse, they are held back until the edition's decisions are committed;notifythen has to befalsetoo (422otherwise).
Request
curl -X POST https://ferienpass-musterstadt.de/api/offers/0192a4f1-6e3b-7c85-b1d2-8f4e6a9c3b03/refill \
-H "Authorization: Bearer $FEPLI_TOKEN" \
-H "Content-Type: application/json" \
-d '{"notify": true}'
Response (202 Accepted, excerpt)
{
"uuid": "0192a4f1-6e3b-7c85-b1d2-8f4e6a9c3b03",
"maxParticipants": 18,
"…": "…",
"attendances": {
"confirmed": 15,
"waitlisted": 5,
"waiting": 0,
"withdrawn": 1,
"rejected": 0,
"total": 20
}
}