NavigaTUM API

Public API to search for and resolve rooms, buildings, and other places across TUM campuses. Written in Rust with a MeiliSearch backend and documented via an OpenAPI 3.0 specification. The /api/search endpoint is live and returns JSON without authentication.

OpenAPI Specification

tum-navigatum.yaml Raw ↑
openapi: 3.0.3
info:
  title: NavigaTUM
  description: 'Navigating around TUM with excellence – An API to search for rooms,

    buildings and other places


    NavigaTUM is a tool developed by students for students, to help you get around at [TUM](https://tum.de). Feel free to contribute.


    - [x] Interactive/static maps to look up the position of rooms or buildings

    - [x] Fast and typo-tolerant search

    - [x] Support for different room code formats as well as generic names

    - [x] All functionality is also available via an open and well documented API

    - [x] Automatically update the data from upstream datasources

    - [x] Allow students/staff to easily submit feedback and data patches

    - [x] Generate turn by turn navigation advice for navigating end to end

    - [ ] Generate maps from CAD data sources


    If you''d like to help out or join us in this adventure, we would love to talk to you.'
  termsOfService: https://nav.tum.de/en/about/privacy
  contact:
    name: OpenSource @ TUM e.V.
    url: https://tum.dev/
    email: [email protected]
  license:
    name: GPL v3
    url: https://www.gnu.org/licenses/
  version: 0.0.0
  x-logo:
    altText: null
    backgroundColor: null
    href: https://nav.tum.de
    url: https://raw.githubusercontent.com/TUM-Dev/NavigaTUM/refs/heads/main/webclient/app/assets/logos/navigatum.svg
servers:
- url: https://nav.tum.de
  description: production
paths:
  /api/calendar:
    post:
      tags:
      - calendar
      summary: Retrieve Calendar Entries
      description: 'Retrieves calendar entries for specific `ids` within the requested time span.

        The time span is defined by the `start_after` and `end_before` query parameters.

        Ensure to provide valid date-time formats for these parameters.


        If successful, returns additional entries in the requested time span.'
      operationId: calendar_handler
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/Arguments'
        required: true
      responses:
        '200':
          description: '**Entries of the calendar** in the requested time span'
          content:
            application/json:
              schema:
                type: object
                additionalProperties:
                  $ref: '#/components/schemas/LocationEventsResponse'
                propertyNames:
                  type: string
        '400':
          description: '**Bad Request.** Not all fields in the body are present as defined above'
          content:
            text/plain:
              schema:
                type: string
              example: Too many ids to query. We suspect that users don't need this. If you need this limit increased, please send us a message
        '404':
          description: '**Not found.** The requested location does not have a calendar'
          content:
            text/plain:
              schema:
                type: string
              example: Not found
        '503':
          description: '**Not Ready.** please retry later'
          content:
            text/plain:
              schema:
                type: string
              example: Waiting for first sync with TUMonline
  /api/feedback/feedback:
    post:
      tags:
      - feedback
      summary: Post feedback
      description: '***Do not abuse this endpoint.***


        This posts the actual feedback to GitHub and returns the GitHub link.

        This API will create issues instead of pull-requests

        => all feedback is allowed, but [`/api/feedback/propose_edits`](#tag/feedback/operation/propose_edits) is preferred, if it can be posted there.


        For this Endpoint to work, you need to generate a token via the [`/api/feedback/get_token`](#tag/feedback/operation/get_token) endpoint.


        # Note


        Tokens are only used if we return a 201 Created response.

        Otherwise, they are still valid'
      operationId: send_feedback
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PostFeedbackRequest'
        required: true
      responses:
        '201':
          description: The feedback has been **successfully posted to GitHub**. We return the link to the GitHub issue.
          content:
            text/plain:
              schema:
                type: string
                format: uri
              example: https://github.com/TUM-Dev/navigatum/issues/9
        '400':
          description: '**Bad Request.** Not all fields in the body are present as defined above'
        '403':
          description: '**Forbidden.** Causes are (delivered via the body):


            - `Invalid token`: You have not supplied a token generated via the `gen_token`-Endpoint.

            - `Token not old enough, please wait`: Tokens are only valid after 10s.

            - `Token expired`: Tokens are only valid for 12h.

            - `Token already used`: Tokens are non reusable/refreshable single-use items.'
          content:
            text/plain:
              schema:
                type: string
        '422':
          description: '**Unprocessable Entity.** Subject or body missing or too short.'
        '451':
          description: '**Unavailable for legal reasons.** Using this endpoint without accepting the privacy policy is not allowed. For us to post to GitHub, this has to be `true`'
        '500':
          description: '**Internal Server Error.** We have a problem communicating with GitHubs servers. Please try again later'
        '503':
          description: '**Service unavailable.** We have not configured a GitHub Access Token. This could be because we are experiencing technical difficulties or intentional. Please try again later.'
  /api/feedback/get_token:
    post:
      tags:
      - feedback
      summary: Get a feedback-token
      description: '***Do not abuse this endpoint.***


        This returns a JWT token usable for submitting feedback.

        You should request a token, ***if (and only if) a user is on a feedback page***


        As a rudimentary way of rate-limiting feedback, this endpoint returns a token.

        To post feedback, you will need this token.


        Tokens gain validity after 5s, and are invalid after 12h of being issued.

        They are not refreshable, and are only valid for one usage.


        # Note:


        Global Rate-Limiting allows bursts with up to 20 requests and replenishes 50 requests per day'
      operationId: get_token
      responses:
        '201':
          description: '**Created** a usable token'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TokenResponse'
        '429':
          description: '**Too many requests.** We are rate-limiting everyone''s requests, please try again later.'
        '503':
          description: '**Service unavailable.** We have not configured a GitHub Access Token. This could be because we are experiencing technical difficulties or intentional. Please try again later.'
  /api/feedback/propose_edits:
    post:
      tags:
      - feedback
      summary: Post Edit-Requests
      description: '***Do not abuse this endpoint.***


        This posts the actual feedback to GitHub and returns the github link.

        This API will create pull-requests instead of issues => only a subset of feedback is allowed.

        For this Endpoint to work, you need to generate a token via the [`/api/feedback/get_token`](#tag/feedback/operation/get_token) endpoint.


        # Note:


        Tokens are only used if we return a 201 Created response. Otherwise, they are still valid'
      operationId: propose_edits
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/EditRequest'
        required: true
      responses:
        '201':
          description: The edit request feedback has been **successfully posted to GitHub**. We return the link to the GitHub issue.
          content:
            text/plain:
              schema:
                type: string
                format: uri
              example: https://github.com/TUM-Dev/navigatum/issues/9
        '400':
          description: '**Bad Request.** Not all fields in the body are present as defined above'
        '403':
          description: '**Forbidden.** Causes are (delivered via the body):


            - `Invalid token`: You have not supplied a token generated via the `gen_token`-Endpoint.

            - `Token not old enough, please wait`: Tokens are only valid after 10s.

            - `Token expired`: Tokens are only valid for 12h.

            - `Token already used`: Tokens are non reusable/refreshable single-use items.'
        '422':
          description: '**Unprocessable Entity.** Subject or body missing or too short.'
        '451':
          description: '**Unavailable for legal reasons.** Using this endpoint without accepting the privacy policy is not allowed. For us to post to GitHub, this has to be true'
        '500':
          description: '**Internal Server Error.** We have a problem communicating with GitHubs servers. Please try again later.'
        '503':
          description: Service unavailable. We have not configured a GitHub Access Token. This could be because we are experiencing technical difficulties or intentional. Please try again later.
  /api/locations/{id}:
    get:
      tags:
      - locations
      summary: Get entry-details
      description: 'This returns the full data available for the entry (room/building).


        This is more data, that should be supplied once a user clicks on an entry.

        Preloading this is not an issue on our end, but keep in mind bandwith constraints on your side.

        The data can be up to 50kB (using gzip) or 200kB unzipped.

        More about this data format is described in the NavigaTUM-data documentation'
      operationId: get_handler
      parameters:
      - name: id
        in: path
        description: ID of the location
        required: true
        schema:
          type: string
      - name: lang
        in: query
        description: The language you want your preview to be in. If either this or the query parameter is set to en, this will be delivered.
        required: false
        schema:
          type: string
          enum:
          - de
          - en
      responses:
        '200':
          description: '**Details** about the **location**'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/LocationDetailsResponse'
        '400':
          description: '**Bad request.** Make sure that requested item ID is not empty and not longer than 255 characters'
          content:
            text/plain:
              schema:
                type: string
              example: Invalid ID
        '404':
          description: '**Not found.** Make sure that requested item exists'
          content:
            text/plain:
              schema:
                type: string
              example: Not found
  /api/locations/{id}/nearby:
    get:
      tags:
      - locations
      summary: Get the nearby items
      description: Shows nearby POIs like public transport stations
      operationId: nearby_handler
      parameters:
      - name: id
        in: path
        description: ID of a location
        required: true
        schema:
          type: string
      responses:
        '200':
          description: Things **nearby to the location**
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/NearbyLocationsResponse'
        '400':
          description: '**Bad request.** Make sure that requested item ID is not empty and not longer than 255 characters'
          content:
            text/plain:
              schema:
                type: string
              example: Invalid ID
        '404':
          description: '**Not found.** Make sure that requested item exists'
          content:
            text/plain:
              schema:
                type: string
              example: Not found
  /api/locations/{id}/preview:
    get:
      tags:
      - locations
      summary: Get a entry-preview
      description: 'This returns a 1200x630px preview for the location (room/building/..).


        This is usefully for implementing custom `OpenGraph` images for detail previews.'
      operationId: maps_handler
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
      - name: lang
        in: query
        required: false
        schema:
          type: string
          enum:
          - de
          - en
      - name: format
        in: query
        required: false
        schema:
          type: string
          enum:
          - open_graph
          - square
      responses:
        '200':
          description: '**Preview image**'
          content:
            image/png: {}
        '400':
          description: '**Bad request.** Make sure that requested item ID is not empty and not longer than 255 characters'
          content:
            text/plain:
              schema:
                type: string
              example: Invalid ID
        '404':
          description: '**Not found.** Make sure that requested item exists'
          content:
            text/plain:
              schema:
                type: string
              example: Not found
  /api/locations/{id}/qr-code:
    get:
      tags:
      - locations
      summary: Get a QR code for a location
      description: 'This returns a QR code image (PNG) that links to the location''s detail page.

        The QR code uses TUM blue (#0065bd) as foreground color with white background and rounded corners.'
      operationId: qr_code_handler
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
      responses:
        '200':
          description: '**QR code image**'
          content:
            image/png: {}
        '400':
          description: '**Bad request.** Make sure that requested item ID is not empty and not longer than 255 characters'
          content:
            text/plain:
              schema:
                type: string
              example: Invalid ID
        '500':
          description: '**Internal server error**'
          content:
            text/plain:
              schema:
                type: string
  /api/maps/route:
    get:
      tags:
      - maps
      summary: Routing requests
      description: "**API IS EXPERIMENTAL AND ACTIVELY SUBJECT TO CHANGE**\n\nThe user specifies using provided origin (`from`) and destination (`to`) locations and a transport mode (`route_costing`) to tune their routing between the two locations.\nThe costing is fine-tuned by the server side accordingly.\n\nInternally, this endpoint relies on\n- [Valhalla](https://github.com/valhalla/valhalla) for routing for route calculation\n- our database to resolve ids.\n\n  You will need to look the ids up via [`/api/search`](#tag/locations/operation/search_handler) beforehand.\n  **Note:** [`/api/search`](#tag/locations/operation/search_handler) does support both university internal routing and external addressing.\n\n**In the future (i.e. public transit routing currently is not implemented)**, it will als rely on either\n- [OpenTripPlanner2](https://www.opentripplanner.org/) or\n- [Motis](https://github.com/motis-project/motis)"
      operationId: route_handler
      parameters:
      - name: lang
        in: query
        required: false
        schema:
          type: string
          enum:
          - de
          - en
      - name: from
        in: query
        description: Start of the route
        required: true
        schema:
          oneOf:
          - $ref: '#/components/schemas/Coordinate'
            description: 'Either an

              - external address which was looked up or

              - the users current location'
          - type: string
            description: Our (uni internal) key for location identification
      - name: to
        in: query
        description: Destination of the route
        required: true
        schema:
          oneOf:
          - $ref: '#/components/schemas/Coordinate'
            description: 'Either an

              - external address which was looked up or

              - the users current location'
          - type: string
            description: Our (uni internal) key for location identification
      - name: route_costing
        in: query
        description: 'Transport mode the user wants to use


          If not specified, the default is based on how far the destinations are apart and requested time.'
        required: false
        schema:
          oneOf:
          - type: 'null'
          - type: string
            description: Transport mode the user wants to use
            enum:
            - pedestrian
            - bicycle
            - motorcycle
            - car
            - public_transit
      - name: pedestrian_type
        in: query
        description: Does the user have specific walking restrictions?
        required: false
        schema:
          type: string
          description: Does the user have specific walking needs?
          enum:
          - standard
          - blind
          - wheelchair
      - name: ptw_type
        in: query
        description: Does the user prefer mopeds or motorcycles for powered two-wheeled (ptw)?
        required: false
        schema:
          type: string
          description: Does the user have a moped or motorcycle
          enum:
          - motorcycle
          - moped
      - name: bicycle_type
        in: query
        description: Which kind of bicycle do you ride?
        required: false
        schema:
          type: string
          description: Which kind of bicycle do you ride?
          enum:
          - road
          - hybrid
          - cross
          - mountain
      - name: page_cursor
        in: query
        description: 'Cursor position for pagination

          Only avaliable for some costings'
        required: false
        schema:
          type: string
          nullable: true
      - name: time
        in: query
        description: 'Time for the route (ISO 8601 format)

          Used with `arrive_by` to determine if this is departure or arrival time'
        required: false
        schema:
          type: string
          format: date-time
          nullable: true
      - name: arrive_by
        in: query
        description: Whether the time parameter represents arrival time (true) or departure time (false/not set)
        required: false
        schema:
          type: boolean
      responses:
        '200':
          description: '**Routing solution**'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RoutingResponse'
        '404':
          description: '**Not found.** The requested location does not exist'
          content:
            text/plain:
              schema:
                type: string
              example: Not found
  /api/openapi.json:
    get:
      summary: Openapi service definition
      description: Usefull for consuming in external openapi tooling
      operationId: openapi_doc
      responses:
        '200':
          description: The openapi definition
          content:
            application/json: {}
  /api/search:
    get:
      tags:
      - locations
      summary: Search entries
      description: 'This endpoint is designed to support search-as-you-type results.


        Instead of simply returning a list, the search results are returned in a way to provide a richer experience by splitting them up into sections. You might not necessarily need to implement all types of sections, or all sections features (if you just want to show a list). The order of sections is a suggested order to display them, but you may change this as you like.


        Some fields support highlighting the query terms and it uses \x19 and \x17 to mark the beginning/end of a highlighted sequence.

        (See [Wikipedia](https://en.wikipedia.org/wiki/C0_and_C1_control_codes#Modified_C0_control_code_sets)).

        Some text-renderers will ignore them, but in case you do not want to use them, you might want to remove them from the responses via empty `pre_highlight` and `post_highlight` query parameters.'
      operationId: search_handler
      parameters:
      - name: q
        in: query
        description: 'string you want to search for.


          The amounts returned can be controlled using the `limit_*` parameters.

          Use `in`, `usage`, `type`, and `near` query parameters for filtering.'
        required: true
        schema:
          type: string
      - name: in
        in: query
        description: 'Filter by parent (building, campus, etc.).


          Can be repeated for multiple values (e.g. `&in=garching&in=5304`).'
        required: false
        schema:
          type: array
          items:
            type: string
      - name: usage
        in: query
        description: 'Filter by usage type (e.g. `wc`, `büro`).


          Can be repeated for multiple values.'
        required: false
        schema:
          type: array
          items:
            type: string
      - name: type
        in: query
        description: 'Filter by facet.


          Can be repeated for multiple values. Unknown values cause a `400`.'
        required: false
        schema:
          type: array
          items:
            $ref: '#/components/schemas/FacetFilter'
      - name: near
        in: query
        description: Sort results by distance to a coordinate (`lat,lon`).
        required: false
        schema:
          type: string
      - name: search_addresses
        in: query
        description: 'Include adresses in the saerch


          Be aware that Nominatim (which we use to do this search) is really slow (~100ms).

          Only activate this when you really need it.'
        required: false
        schema:
          type: boolean
      - name: limit_sites
        in: query
        description: 'Maximum number of sites (campus / site / area) to return.


          Clamped to `0`..`1000`.

          If this is a problem for you, please open an issue.'
        required: false
        schema:
          type: integer
          minimum: 0
      - name: limit_buildings
        in: query
        description: 'Maximum number of buildings to return.


          Clamped to `0`..`1000`.

          If this is a problem for you, please open an issue.'
        required: false
        schema:
          type: integer
          minimum: 0
      - name: limit_rooms
        in: query
        description: 'Maximum number of rooms to return.


          Clamped to `0`..`1000`.

          If this is an problem for you, please open an issue.'
        required: false
        schema:
          type: integer
          minimum: 0
      - name: limit_pois
        in: query
        description: 'Maximum number of POIs (points of interest) to return.


          Clamped to `0`..`1000`.

          If this is a problem for you, please open an issue.'
        required: false
        schema:
          type: integer
          minimum: 0
      - name: limit_all
        in: query
        description: 'Maximum number of results to return.


          Clamped to `1`..`1000`.

          If this is an problem for you, please open an issue.'
        required: false
        schema:
          type: integer
          minimum: 0
      - name: pre_highlight
        in: query
        description: 'string to include in front of highlighted sequences.


          If this and `post_highlight` are empty, highlighting is disabled.

          For background on the default values, please see [Wikipedia](https://en.wikipedia.org/wiki/C0_and_C1_control_codes#Modified_C0_control_code_sets)).'
        required: false
        schema:
          type: string
      - name: post_highlight
        in: query
        description: 'string to include after the highlighted sequences.


          If this and `pre_highlight` are empty, highlighting is disabled.

          For background on the default values, please see [Wikipedia](https://en.wikipedia.org/wiki/C0_and_C1_control_codes#Modified_C0_control_code_sets)).'
        required: false
        schema:
          type: string
      - name: cropping
        in: query
        description: 'How to handle cropping of long building names in `parsed_id`.


          - `crop` (default): crop long names (> 25 chars) with an ellipsis.

          - `full`: never crop; always show full building names.'
        required: false
        schema:
          type: string
          description: Controls whether long building names inside `parsed_id` are cropped.
          enum:
          - crop
          - full
      - name: parsed_id
        in: query
        description: 'How to format `parsed_id` for rooms.


          - `prefixed` (default): add common building prefixes (e.g. `MW 1801`).

          - `roomfinder`: return room codes in Roomfinder format (`archname@building_id`).'
        required: false
        schema:
          type: string
          description: Controls how `parsed_id` is built for room results.
          enum:
          - prefixed
          - roomfinder
      responses:
        '200':
          description: Search entries
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SearchResponse'
        '400':
          description: '**Bad Request.** Not all fields in the body are present as defined above'
          content:
            text/plain:
              schema:
                type: string
              example: 'Query deserialize error: invalid digit found in string'
        '404':
          description: '**Not found.** `q` is empty. Since searching for nothing is nonsensical, we dont support this.'
          content:
            text/plain:
              schema:
                type: string
              example: Not found
        '414':
          description: '**URI Too Long.** The uri you are trying to request is unreasonably long. Search querys dont have thousands of chars..'
          content:
            text/plain:
              schema:
                type: string
  /api/status:
    get:
      summary: API healthcheck
      description: 'If this endpoint does not return 200, the API is experiencing a catastrophic outage.

        **Should never happen.**'
      operationId: health_status_handler
      responses:
        '200':
          description: API is **healthy**
          content:
            text/plain:
              schema:
                type: string
              example: 'healthy

                source_code: https://github.com/TUM-Dev/navigatum/tree/{hash}'
        '503':
          description: API is **NOT healthy**
          content:
            text/plain:
              schema:
                type: string
              example: 'unhealthy

                source_code: https://github.com/TUM-Dev/navigatum/tree/{hash}'
components:
  schemas:
    AlertCauseResponse:
      type: string
      enum:
      - unknown_cause
      - other_cause
      - technical_problem
      - strike
      - demonstration
      - accident
      - holiday
      - weather
      - maintenance
      - construction
      - police_activity
      - medical_emergency
    AlertEffectResponse:
      type: string
      enum:
      - no_service
      - reduced_service
      - significant_delays
      - detour
      - additional_service
      - modified_service
      - other_effect
      - unknown_effect
      - stop_moved
      - no_effect
      - accessibility_issue
    AlertResponse:
      type: object
      required:
      - description_text
      - header_text
      properties:
        cause:
          oneOf:
          - type: 'null'
          - $ref: '#/components/schemas/AlertCauseResponse'
        cause_detail:
          type: string
          description: "Description of the cause of the alert that allows for\n agency-specific language; more specific than the Cause."
          nullable: true
        description_text:
          type: string
          description: "Description for the alert.\nThis plain-text string will be formatted as the body of the alert\n (or shown on an explicit \"expand\" request by the user).\n The information in the description should add to the information of\n the header."
        effect:
          oneOf:
          - type: 'null'
          - $ref: '#/components/schemas/AlertEffectResponse'
        effect_detail:
          type: string
          description: "Description of the effect of the alert that allows for\n agency-specific language; more specific than the Effect."
          nullable: true
        header_text:
          type: string
          description: "Header for the alert. This plain-text string will be highlighted,\n for example in boldface."
        image_alternative_text:
          type: string
          description: "Text describing the appearance of the linked image in the image\n field (e.g., in case the image can't be displayed or the\n user can't see the image for accessibility reasons). See the\n HTML spec for alt image text."
          nullable: true
        image_media_type:
          type: string
          description: "IANA media type as to specify the type of image to be displayed. The\n type must start with \"image/\""
          nullable: true
        image_url:
          type: string
          description: String containing an URL linking to an image.
          nullable: true
        severity_level:
          oneOf:
          - type: 'null'
          - $ref: '#/components/schemas/AlertSeverityLevelResponse'
        url:
          type: string
          description: The URL which provides additional information about the alert.
          nullable: true
    AlertSeverityLevelResponse:
      type: string
      enum:
      - unknown
      - info
      - warning
      - severe
    Arguments:
      type: object
      required:
      - ids
      - start_after
      - end_before
      properties:
        end_before:
          type: string
          format: date-time
          description: The last allowed time the calendar would like to display
          examples:
          - '2039-01-19T03:14:07+01:00'
          - 2042-01-07T00:00:00 UTC
        ids:
          type: array
          items:
            type: string
          description: 'ids you want the calendars for


            Limit of max. 10 ids is arbitraryly chosen, if you need this limit increased, please contact us'
          example:
          - 5605.EG.011
          - 5510.02.001
          - 5606.EG.036
          - '5304'
          maxItems: 10
          minItems: 1
        start_after:
          type: string
          format: date-time
          description: The first allowed time the calendar would like to display
          examples:
          - '2039-01-19T03:14:07+01:00'
          - 2042-01-07T00:00:00 UTC
    BuildingKind:
      type: string
      enum:
      - building
      - joined_building
      - area
    BuildingsOverviewItemResponse:
      type: object
      required:
      - id
      - name
      - subtext
      properties:
        id:
          type: string
          description: The id of the entry
        name:
          type: string
          description: Human display name
        subtext:
          type: string
          description: What should be displayed below this Building
        thumb:
          type: string
          description: The thumbnail for the building
          nullable: true
    BuildingsOverviewResponse:
      type: object
      required:
      - entries
      - n_visible
      properties:
        entries:
          type: array
          items:
            $ref: '#/components/schemas/BuildingsOverviewItemResponse'
        n_visible:
          type: integer
          format: int32
          minimum: 0
    CalendarLocationResponse:
      type: object
      required:
      - key
      - name
      - last_calendar_scrape_at
      - type_common_name
      - type
      properties:
        calendar_url:
          type: string
          description: Link to the calendar of the room
          examples:
          - https://campus.tum.de/tumonline/tvKalender.wSicht?cOrg=19691&cRes=12543&cReadonly=J
          - https://campus.tum.de/tumonline/

# --- truncated at 32 KB (100 KB total) ---
# Full source: https://raw.githubusercontent.com/api-evangelist/tum/refs/heads/main/openapi/tum-navigatum.yaml