fepli
REST API

Conventions

Every endpoint of the fepli API follows the same rules for identifiers, dates, money, filters and writes. They're explained once here, so the resource reference can stay short.

Requests and responses

The API speaks JSON only. Responses are application/json; errors are application/problem+json (see Errors).

Requests with a body (POST, PATCH) must send Content-Type: application/json. For PATCH, application/merge-patch+json works too. A request body without a JSON content type is refused with 415 Unsupported Media Type.

A write 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": 15}'

Field names are English. Content such as offer names and descriptions is returned as it was entered, usually in German.

Identifiers

Every record is identified by a UUID (RFC 4122), returned as uuid:

0192a4f1-6e3b-7c85-b1d2-8f4e6a9c3b03

The admin's URLs contain the same UUID in a shorter base58 form (22 characters, for example 1C8wV6pQk2Tz9RfXh4MbNd). The API accepts both forms wherever it reads an identifier, in paths, filters and request bodies, so you can paste whichever you have at hand. Responses always use the RFC 4122 form.

Some records can also be named by something more memorable, in filters and in request bodies:

RecordAlso accepted
Editionits alias, e.g. herbstferien-2026
Organiserits alias, e.g. stadtjugendring-musterstadt
Categoryits alias, e.g. sport-bewegung
Accountits e-mail address

Paths take a UUID (in either form), never an alias: /editions/herbstferien-2026 does not work, ?edition=herbstferien-2026 does.

References

When a record points to another, it embeds a small reference with enough to display it and to fetch it:

A reference

{
  "uuid": "01913e5c-2b4a-7d10-9f3e-6a1c2e7b4d01",
  "name": "Herbstferien 2026",
  "alias": "herbstferien-2026"
}
  • Name
    uuid
    Type
    string
    Description

    The identifier to fetch the full record with.

  • Name
    name
    Type
    string
    Description

    A display name: a person's full name, an offer's title, a receipt's number. Left out when the person's data is masked.

  • Name
    alias
    Type
    string
    Description

    The URL-safe alias, for records that have one (editions, organisers, offers, categories).

Some lists of related records, such as receipts on an application, contain bare UUID strings instead.

Empty fields are left out

Fields whose value is null are omitted from responses. An offer without a meeting point simply has no meetingPoint key. Treat a missing field as null.

Lists that apply to you are always present, and empty ones are returned as []. Fields you may not see, such as the internal fields of an offer without a token, are left out entirely. Booleans and numbers that have a default are always present.

The exception are a few nested lists of plain values, such as an edition's tasks, a receipt's items and a ledger's bookEntries: there, empty values appear as null.

Dates and times

Timestamps are ISO 8601 with an offset, in the municipality's local time (Europe/Berlin):

2026-10-14T09:00:00+02:00

When you send a timestamp, include the offset. Date-only values (such as a participant's dateOfBirth) are accepted as 2015-04-12.

Date filters such as from, until or modifiedSince accept a date (2026-10-01) or a full timestamp. An unreadable date is a 400.

Money

Amounts are integers in cents. fee: 750 is €7.50. Negative amounts appear on reversals and in ledgers: see Receipts and Debtors.

Filters

Collection endpoints take their filters as query parameters. Each resource lists its own.

curl "https://ferienpass-musterstadt.de/api/attendances?edition=herbstferien-2026&status=confirmed,waitlisted&paid=false" \
  -H "Authorization: Bearer $FEPLI_TOKEN"
  • Several filters are combined: a record must match all of them.
  • Several values of one filter, where the filter allows it, are comma-separated and combined with OR: status=confirmed,waitlisted.
  • Booleans are true or false (1/0 and yes/no work as well).
  • Free-text search (q) matches any part of the fields each resource names, ignoring case.
  • A malformed value, such as a date that can't be read, a true that isn't a boolean or an unknown sort, is a 400 Bad Request whose detail names the parameter.
  • A reference that matches nothing, such as ?edition= with an alias that doesn't exist, is not an error. It simply returns an empty list. (The offer filter of applications is the exception: an unknown offer is a 404.)
  • Unknown parameters are ignored.

Writes

MethodUsed forSuccess
POST /thingscreate a record201 Created with the new record
PATCH /things/{uuid}change a record200 OK with the changed record
DELETE /things/{uuid}delete a record204 No Content
POST /things/{uuid}/actionan action: a transition, a reversal, a revocation200 OK with the resulting record
POST /things/{uuid}/actionan action that makes a new record: a copy, a variant, a move to another offer201 Created with the new record
POST /things/{uuid}/actionan action done in the background: refilling a waiting list, committing decisions202 Accepted with the record as it is now

How PATCH works

PATCH applies only the keys you send:

  • A key you leave out is left alone.
  • A key you send as null clears the field.
  • A list you send (for example an offer's dates, hosts or categories) replaces the whole list.
  • An empty body is a 422: there is nothing to change. (Editions are the exception and return unchanged.)

Change the fee, clear the meeting point

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 '{"fee": 500, "meetingPoint": null}'

Fields that must always have a value, such as a name or an offer's edition, ignore null. Each resource lists them.

Validation

Writes are validated with the same rules as the admin's forms. Invalid input is a 422 Unprocessable Entity. Its detail always holds one line per problem, each starting with the field:

422 Unprocessable Entity

{
  "type": "/validation_errors/04b91c99-a946-4221-afc5-e65ebac401eb",
  "title": "An error occurred",
  "status": 422,
  "detail": "maxAge: This value should be between 0 and 99.",
  "violations": [
    {
      "propertyPath": "maxAge",
      "message": "This value should be between 0 and 99.",
      "code": "04b91c99-a946-4221-afc5-e65ebac401eb"
    }
  ]
}

When a value is malformed or out of range, as above, the response also lists each problem in violations. When a request is well-formed but doesn't make sense for the record, such as an edition that doesn't exist or an amount above the fee, you get the detail only. Read detail and you're covered either way.

Workflows

Offers and applications move through states, just like in the admin. You don't set their state directly. Instead you apply a transition with POST …/{uuid}/transition:

curl -X POST https://ferienpass-musterstadt.de/api/attendances/0192d0e8-3a4b-7c5d-9e6f-7a8b9c0d1e07/transition \
  -H "Authorization: Bearer $FEPLI_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"transition": "confirm", "notify": true}'

Every offer and application lists its possible transitions in transitions. For offers the list already takes your permissions into account; for applications it only reflects the state. If you try one that isn't possible, the answer is a 409 Conflict that names the possible ones:

409 Conflict

{
  "type": "/errors/409",
  "title": "An error occurred",
  "status": 409,
  "detail": "The transition \"noshow\" is not possible from \"waitlisted\". Possible: confirm, withdraw, reset, reject, unfulfill, participated."
}

The transitions trigger the same side effects as in the admin: e-mails to families (where you ask for them), reordered participant lists, updated payment records.

Caching

Responses to authenticated requests are marked Cache-Control: private, no-store. They are one person's view and must never be stored by a shared cache. Anonymous reads of the public offers stay cacheable.

Was this page helpful?