{
  "openapi": "3.0.3",
  "info": {
    "title": "RadioAPI v1 (JSON & XML)",
    "description": "Öffentliche, unauthentifizierte v1-Schnittstelle von radioapi.de. Alle Endpunkte liefern wahlweise JSON (`/v1/json/...`) oder XML (`/v1/xml/...`) und senden offene CORS-Header (`Access-Control-Allow-Origin: *`). Es gibt keine einheitliche Fehler-/Hüllenstruktur über alle Endpunkte hinweg - viele Endpunkte sind historisch gewachsen und verhalten sich bewusst leicht unterschiedlich (siehe jeweilige Beschreibung).\n\nNetzwerk-Shortcuts: Für `stationData`, `songHistory`, `showSchedule`, `newSongs`, `genres` und `events` gibt es neben dem generischen `?network=<id>` Query-Parameter auch feste Pfad-Aliasse für die drei Haupt-Netzwerke, die serverseitig per nginx-Rewrite auf `?network=` gemappt werden: `lionfm` → network=1, `rbcs` → network=2, `linefm` → network=3 (z. B. `/v1/json/lionfm/stationData` ≡ `/v1/json/stationData?network=1`). Diese Aliasse existieren nur für die genannten Endpunkte und sind unten aus Übersichtlichkeit nicht als eigene Pfade modelliert - der Query-Parameter `network` ist der eigentliche, vollständige Vertrag.",
    "version": "1.2.0"
  },
  "servers": [
    {
      "url": "https://radioapi.de"
    }
  ],
  "tags": [
    {
      "name": "JSON",
      "description": "Endpunkte unter /v1/json"
    },
    {
      "name": "XML",
      "description": "Endpunkte unter /v1/xml"
    }
  ],
  "paths": {
    "/v1/json/stationData": {
      "get": {
        "tags": [
          "JSON"
        ],
        "summary": "Stationsdaten inkl. aktuellem Titel",
        "description": "Ohne Parameter werden alle sichtbaren Stationen zurückgegeben. Die Antwort ist standardmäßig ein Objekt, dessen Schlüssel die Stations-ID ist (interne `id`, oder bei gesetztem `network` die netzwerk-lokale `ext_id`) - kein Array. Mit `array=1` wird stattdessen ein einfaches JSON-Array derselben Objekte geliefert (nur JSON; die XML-Variante kennt diesen Parameter zwar, er hat dort aber keine Wirkung).",
        "parameters": [
          {
            "name": "network",
            "in": "query",
            "description": "Netzwerk-ID. Schaltet die Antwort-Schlüssel von der internen `id` auf die netzwerk-lokale `ext_id` um und aktiviert für network=1 (LionFM) das zusätzliche `cover`-Feld in `now_playing`.",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "station",
            "in": "query",
            "description": "Stations-ID (interne `id`, oder bei gesetztem `network` die `ext_id`). Alias für `stationID`.",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "stationID",
            "in": "query",
            "description": "Wie `station`; hat Vorrang, falls beide gesetzt sind.",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "genre",
            "in": "query",
            "description": "Netzwerk-lokale Genre-ID (siehe `/v1/json/genres`). Filtert Stationen eines Netzwerks nach Genre (benötigt zusätzlich `network`). Wird ignoriert, wenn `station`/`stationID` gesetzt ist.",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "topStations",
            "in": "query",
            "description": "1 = alle Stationen des Netzwerks, Top-Stationen zuerst sortiert. 2 = nur als Top-Station markierte Stationen. Benötigt `network`; wird ignoriert, wenn `station`/`stationID` oder `genre` gesetzt ist.",
            "schema": {
              "type": "integer",
              "enum": [
                1,
                2
              ]
            }
          },
          {
            "name": "array",
            "in": "query",
            "description": "1 = Antwort als JSON-Array statt als nach Stations-ID geschlüsseltes Objekt. Nur in der JSON-Variante wirksam.",
            "schema": {
              "type": "integer",
              "enum": [
                1
              ]
            }
          },
          {
            "name": "textFormat",
            "in": "query",
            "description": "Formatierung von `artist`/`title` in `now_playing`. 0 oder nicht gesetzt = Großschreibung (Default), 1 = Großschreibung (explizit), 2 = unverändert, 3 = Kleinschreibung.",
            "schema": {
              "type": "integer",
              "enum": [
                0,
                1,
                2,
                3
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Stationsdaten, standardmäßig als Objekt (Schlüssel = Stations-ID), mit `array=1` als Array.",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "type": "object",
                      "additionalProperties": {
                        "$ref": "#/components/schemas/StationData"
                      }
                    },
                    {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/StationData"
                      }
                    }
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/v1/json/songHistory": {
      "get": {
        "tags": [
          "JSON"
        ],
        "summary": "Playlist-Verlauf (gesendete Titel)",
        "description": "Ohne jegliche Parameter: letzte 10 Titel der Station mit `id=1` der letzten Stunde. Mit Parametern: `from`/`till` als `HH:MM` oder `HH:MM:SS`, oder beide `'latest'` für ein rollendes ±1h-Fenster um jetzt. `date` grenzt zusätzlich auf einen Kalendertag ein (Default: heute). Nur Einträge mit `type='Musik'` (bzw. NULL) werden berücksichtigt.",
        "parameters": [
          {
            "name": "from",
            "in": "query",
            "description": "Startzeit `HH:MM` oder `HH:MM:SS`, oder `'latest'`.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "till",
            "in": "query",
            "description": "Endzeit `HH:MM` oder `HH:MM:SS`, oder `'latest'`.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "date",
            "in": "query",
            "description": "Kalendertag im Format YYYY-MM-DD. Default: heute.",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "station",
            "in": "query",
            "description": "Stations-ID (interne `id`, oder bei gesetztem `network` die `ext_id`).",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "network",
            "in": "query",
            "description": "Netzwerk-ID. Schaltet `station` auf netzwerk-lokale Auflösung um und aktiviert für network=1 das `cover`-Feld.",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "textFormat",
            "in": "query",
            "description": "Formatierung von `artist`/`title`. 0/nicht gesetzt und 1 = Großschreibung, 2 = unverändert, 3 = Kleinschreibung.",
            "schema": {
              "type": "integer",
              "enum": [
                0,
                1,
                2,
                3
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Liste der gesendeten Titel, neueste zuerst.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/SongHistoryEntry"
                  }
                }
              }
            }
          },
          "404": {
            "description": "Die per `network`+`station` angegebene Kombination existiert nicht.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorMessage"
                }
              }
            }
          }
        }
      }
    },
    "/v1/json/allListeners": {
      "get": {
        "tags": [
          "JSON"
        ],
        "summary": "Summe der aktuellen Hörerzahlen",
        "description": "Liefert direkt eine JSON-Zahl zurück - kein Objekt/Array als Hülle.",
        "parameters": [
          {
            "name": "network",
            "in": "query",
            "description": "Eine oder mehrere (kommagetrennte) Netzwerk-IDs, z. B. `1,2,3`. Ohne Angabe wird über alle sichtbaren Stationen summiert.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Summe der `listeners`-Werte aller passenden, nicht versteckten Stationen.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "integer"
                },
                "example": 4231
              }
            }
          }
        }
      }
    },
    "/v1/json/showSchedule": {
      "get": {
        "tags": [
          "JSON"
        ],
        "summary": "Sendeplan",
        "parameters": [
          {
            "name": "stationID",
            "in": "query",
            "description": "Interne Stations-ID (`rgde_stations.id`).",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "network",
            "in": "query",
            "description": "Netzwerk-ID.",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Liste aller passenden Sendungen inkl. etwaiger Absagen.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ShowScheduleResponse"
                }
              }
            }
          },
          "500": {
            "description": "Interner Fehler.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/v1/json/newSongs": {
      "get": {
        "tags": [
          "JSON"
        ],
        "summary": "Manuell im ACP als \"neu\" markierte Songs",
        "description": "`stations` enthält bei jedem Song immer alle zugeordneten Sender, unabhängig vom per `stationID`/`network` gesetzten Filter - der Filter wirkt nur auf die Auswahl der Songs selbst.",
        "parameters": [
          {
            "name": "stationID",
            "in": "query",
            "description": "Interne Stations-ID. Filtert Songs, die (unter anderem) diesem Sender zugeordnet sind.",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "network",
            "in": "query",
            "description": "Netzwerk-ID. Filtert Songs, die (unter anderem) einem Sender dieses Netzwerks zugeordnet sind.",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Liste neuer Songs, neueste zuerst.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NewSongsResponse"
                }
              }
            }
          },
          "500": {
            "description": "Interner Fehler.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/v1/json/genres": {
      "get": {
        "tags": [
          "JSON"
        ],
        "summary": "Genres (redaktionelle Kategorien) eines Netzwerks",
        "description": "Im RadioACP unter Sender → Genres gepflegt. `genre_id` ist netzwerk-lokal (jedes Netzwerk vergibt eigene IDs) und entspricht `?genre=` sowie dem Feld `genres` in `stationData`. Sortiert nach `sort_order`. Ausgeblendete Genres (`hidden: true`, z. B. saisonal) sind enthalten, sollen aber nicht als Filter angezeigt werden.",
        "parameters": [
          {
            "name": "network",
            "in": "query",
            "description": "Netzwerk-ID. Ohne Angabe werden die Genres aller Netzwerke geliefert. Alternativ als Pfad: `/v1/{json|xml}/1/genres` bzw. `/v1/{json|xml}/lionfm/genres`.",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Genre-Liste.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GenreListResponse"
                }
              }
            }
          },
          "500": {
            "description": "Interner Fehler.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/v1/json/events": {
      "get": {
        "tags": [
          "JSON"
        ],
        "summary": "Events pro Sender",
        "description": "Veranstaltungen (z. B. RadioAid), im RadioACP unter Sendeplan → Events pro Sender gepflegt. Sortiert nach Datum und Startzeit. Events versteckter Sender werden nicht ausgeliefert.",
        "parameters": [
          {
            "name": "stationID",
            "in": "query",
            "description": "Interne Stations-ID – nur Events dieses Senders.",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "network",
            "in": "query",
            "description": "Netzwerk-ID. Alternativ als Pfad: `/v1/{json|xml}/1/events` bzw. `/v1/{json|xml}/lionfm/events`.",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "include_past",
            "in": "query",
            "description": "`1` = auch vergangene Events. Standard: nur Events, deren (End-)Datum heute oder später ist.",
            "schema": {
              "type": "integer",
              "enum": [
                1
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Event-Liste.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EventListResponse"
                }
              }
            }
          },
          "500": {
            "description": "Interner Fehler.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/v1/json/listenerCounter": {
      "get": {
        "tags": [
          "JSON"
        ],
        "summary": "Legacy-Hörerzähler (Cron-artiger Seiteneffekt-Endpunkt)",
        "description": "**Legacy/Altlast, nicht für neue Integrationen gedacht.** Jeder Aufruf fragt live die laut.fm-API für eine fest hinterlegte Liste von Sendern ab, schreibt die Hörerzahlen in `rgde_stations` zurück und überschreibt zwei statische Cache-Dateien (`listenerCounter.json`, `listenerCounterArray.json`) auf dem Server. Die zurückgegebenen Schlüssel (`station-1`, `station-2`, ...) entsprechen fest verdrahteten internen Stations-/Gruppen-IDs und sind nicht generisch erweiterbar. Kein Parameter.",
        "parameters": [],
        "responses": {
          "200": {
            "description": "Aktuelle Summen-Hörerzahlen je (teils zu Gruppen zusammengefasster) Station.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "description": "Feste Schlüsselmenge: `all` (Gesamtsumme) sowie `station-1`, `station-2`, `station-3`, `station-4`, `station-5`, `station-6`, `station-7`, `station-8`, `station-9`, `station-10`, `station-11`, `station-12`, `station-13`, `station-15`, `station-17` (jeweils `{\"listeners\": number}`).",
                  "properties": {
                    "all": {
                      "type": "integer"
                    }
                  },
                  "additionalProperties": {
                    "type": "object",
                    "properties": {
                      "listeners": {
                        "type": "integer"
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/json/status": {
      "get": {
        "tags": [
          "JSON"
        ],
        "summary": "Statischer Backend-Status (legacy, geschützt)",
        "description": "**Legacy/Altlast.** Erfordert den Query-Parameter `secKey` mit einem fest im Code hinterlegten Geheimwert. Bei falschem oder fehlendem `secKey` liefert der Endpunkt einen leeren Response-Body ohne Fehlermeldung oder angepassten Statuscode. Die zurückgegebenen Daten sind vollständig statisch im Code hinterlegt (kein echter Backend-Health-Check) und sollten nicht als Live-Status missverstanden werden.",
        "security": [
          {
            "secKeyAuth": []
          }
        ],
        "parameters": [],
        "responses": {
          "200": {
            "description": "Statische Liste von Sendern mit (fest hinterlegtem) Status. Bei falschem/fehlendem secKey: leerer Body.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "properties": {
                      "station": {
                        "type": "object",
                        "properties": {
                          "name": {
                            "type": "string"
                          },
                          "backend_running": {
                            "type": "boolean"
                          },
                          "backend_info": {
                            "type": "string",
                            "description": "Meist leer; bei Backup-Sendern Name des primären Backends."
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/xml/stationData": {
      "get": {
        "tags": [
          "XML"
        ],
        "summary": "Stationsdaten inkl. aktuellem Titel (XML)",
        "description": "XML-Gegenstück zu `/v1/json/stationData`. Unterstützt dieselben Parameter mit derselben Bedeutung, **außer** `array`: Dieser Parameter wird zwar entgegengenommen, hat in der XML-Variante aber keine Wirkung - die Antwort ist immer eine flache Liste von `<station_data>`-Elementen unter dem Wurzelelement `<xml>`, nie ein geschlüsseltes Objekt (XML kennt dieses Konzept ohnehin nicht). Die Genre-Zuordnung steht je Sender in `<genres><genre_id>…</genre_id></genres>`.",
        "parameters": [
          {
            "name": "network",
            "in": "query",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "station",
            "in": "query",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "stationID",
            "in": "query",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "genre",
            "in": "query",
            "schema": {
              "type": "integer"
            },
            "description": "Netzwerk-lokale Genre-ID (siehe `/v1/xml/genres`), benötigt `network`."
          },
          {
            "name": "topStations",
            "in": "query",
            "schema": {
              "type": "integer",
              "enum": [
                1,
                2
              ]
            }
          },
          {
            "name": "array",
            "in": "query",
            "description": "Wird angenommen, aber ohne Wirkung (siehe Beschreibung).",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "textFormat",
            "in": "query",
            "schema": {
              "type": "integer",
              "enum": [
                0,
                1,
                2,
                3
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "`<xml><station_data>...</station_data>...</xml>`",
            "content": {
              "application/xml": {
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    },
    "/v1/xml/songHistory": {
      "get": {
        "tags": [
          "XML"
        ],
        "summary": "Playlist-Verlauf (XML)",
        "description": "XML-Gegenstück zu `/v1/json/songHistory`, identisches Verhalten/Parameter. Wurzelelement `<xml>` mit einer Liste von `<data>`-Elementen.",
        "parameters": [
          {
            "name": "from",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "till",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "date",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "station",
            "in": "query",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "network",
            "in": "query",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "textFormat",
            "in": "query",
            "schema": {
              "type": "integer",
              "enum": [
                0,
                1,
                2,
                3
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "`<xml><data><time/><artist/><title/>[<cover/>]<genre/><lyrics/></data>...</xml>`",
            "content": {
              "application/xml": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "404": {
            "description": "Die per `network`+`station` angegebene Kombination existiert nicht.",
            "content": {
              "application/xml": {
                "schema": {
                  "type": "string"
                },
                "example": "<xml><error>Station nicht im angegebenen Netzwerk gefunden</error></xml>"
              }
            }
          }
        }
      }
    },
    "/v1/xml/showSchedule": {
      "get": {
        "tags": [
          "XML"
        ],
        "summary": "Sendeplan (XML)",
        "description": "XML-Gegenstück zu `/v1/json/showSchedule`, identische Parameter/Filterlogik.",
        "parameters": [
          {
            "name": "stationID",
            "in": "query",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "network",
            "in": "query",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "`<schedule><count/><shows><show>...<cancellations>...</cancellations></show>...</shows></schedule>`",
            "content": {
              "application/xml": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "500": {
            "description": "`<error><success>false</success><message>Internal server error</message></error>`",
            "content": {
              "application/xml": {
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    },
    "/v1/xml/newSongs": {
      "get": {
        "tags": [
          "XML"
        ],
        "summary": "Manuell als \"neu\" markierte Songs (XML)",
        "description": "XML-Gegenstück zu `/v1/json/newSongs`, identische Parameter/Filterlogik.",
        "parameters": [
          {
            "name": "stationID",
            "in": "query",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "network",
            "in": "query",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "`<newSongs><count/><songs><song><id/><artist/><title/><cover_url/><created_at/><stations><station><station_id/><display_name/><network_id/></station>...</stations></song>...</songs></newSongs>`",
            "content": {
              "application/xml": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "500": {
            "description": "`<error><success>false</success><message>Internal server error</message></error>`",
            "content": {
              "application/xml": {
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    },
    "/v1/xml/genres": {
      "get": {
        "tags": [
          "XML"
        ],
        "summary": "Genres eines Netzwerks (XML)",
        "description": "XML-Gegenstück zu `/v1/json/genres`, identische Parameter und Felder.",
        "parameters": [
          {
            "name": "network",
            "in": "query",
            "description": "Netzwerk-ID. Ohne Angabe werden die Genres aller Netzwerke geliefert. Alternativ als Pfad: `/v1/{json|xml}/1/genres` bzw. `/v1/{json|xml}/lionfm/genres`.",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "`<genres><count/><genre><genre_id/><genre_key/><genre_name/><genre_title/><genre_desc/><color/><image_url/><sort_order/><hidden/><network_id/><stations><station_id/>…</stations></genre>…</genres>`",
            "content": {
              "application/xml": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "500": {
            "description": "`<error><success>false</success><message>Internal server error</message></error>`",
            "content": {
              "application/xml": {
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    },
    "/v1/xml/events": {
      "get": {
        "tags": [
          "XML"
        ],
        "summary": "Events pro Sender (XML)",
        "description": "XML-Gegenstück zu `/v1/json/events`, identische Parameter und Felder.",
        "parameters": [
          {
            "name": "stationID",
            "in": "query",
            "description": "Interne Stations-ID – nur Events dieses Senders.",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "network",
            "in": "query",
            "description": "Netzwerk-ID. Alternativ als Pfad: `/v1/{json|xml}/1/events` bzw. `/v1/{json|xml}/lionfm/events`.",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "include_past",
            "in": "query",
            "description": "`1` = auch vergangene Events. Standard: nur Events, deren (End-)Datum heute oder später ist.",
            "schema": {
              "type": "integer",
              "enum": [
                1
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "`<events><count/><event><id/><station_id/><station_name/><network_id/><title/><start_date/><end_date/><start_time/><end_time/><location/><short_description/><description/><external_url/><image_url/><image_credit/></event>…</events>`",
            "content": {
              "application/xml": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "500": {
            "description": "`<error><success>false</success><message>Internal server error</message></error>`",
            "content": {
              "application/xml": {
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    },
    "/v1/xml/listenerCounter": {
      "get": {
        "tags": [
          "XML"
        ],
        "summary": "Legacy-Hörerzähler (statische, vorab generierte Datei)",
        "description": "**Legacy/Altlast, Sonderfall:** Im Gegensatz zu allen anderen Endpunkten dieser Spezifikation ist dies kein live berechnender PHP-Endpunkt, sondern liest/aktualisiert nur eine statische `listenerCounter.xml`-Datei, die von einem separaten Cron-Prozess (der die JSON-Datei aus `/v1/json/listenerCounter` spiegelt) erzeugt wird. Struktur und Feldnamen entsprechen 1:1 der JSON-Antwort von `/v1/json/listenerCounter`, nur als XML (Wurzelelement `<xml>`). Kein Parameter.",
        "parameters": [],
        "responses": {
          "200": {
            "description": "XML-Spiegel der `/v1/json/listenerCounter`-Antwort.",
            "content": {
              "application/xml": {
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "secKeyAuth": {
        "type": "apiKey",
        "in": "query",
        "name": "secKey",
        "description": "Fest im Code von status.php hinterlegter Geheimwert. Nicht in dieser Spezifikation veröffentlicht; bei Bedarf im Quellcode (v1/json/status.php) nachsehen."
      }
    },
    "schemas": {
      "NowPlaying": {
        "type": "object",
        "properties": {
          "artist": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "cover": {
            "type": "string",
            "format": "uri",
            "description": "Nur enthalten, wenn die Station zu Netzwerk 1 (LionFM) gehört UND der Request über `network=1` aufgelöst wurde."
          },
          "genre": {
            "type": "string"
          },
          "start": {
            "type": "string",
            "format": "date-time"
          },
          "end": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "StationData": {
        "type": "object",
        "properties": {
          "station_id": {
            "type": "integer",
            "description": "Interne `id`, oder bei gesetztem `network` die netzwerk-lokale `ext_id`."
          },
          "station_int_id": {
            "type": "integer",
            "description": "Nur enthalten, wenn `network` gesetzt ist: die interne `id` (zusätzlich zur netzwerk-lokalen `station_id`)."
          },
          "station_name": {
            "type": "string"
          },
          "station_displayname": {
            "type": "string"
          },
          "station_description": {
            "type": "string"
          },
          "station_genre": {
            "type": "string"
          },
          "genres": {
            "type": "array",
            "items": {
              "type": "integer"
            },
            "description": "Netzwerk-lokale IDs aller Genres, denen der Sender zugeordnet ist (nur IDs mit angelegtem Genre, Details über `/v1/json/genres`)."
          },
          "station_image": {
            "type": "string",
            "format": "uri",
            "description": "Eigenes ACP-Logo (`/v1/img/stations/<name>.png?v=<timestamp>`), sonst Fallback auf die alte externe CDN."
          },
          "listeners": {
            "type": "integer"
          },
          "live": {
            "type": "boolean",
            "description": "Entspricht `rgde_stations.is_live`."
          },
          "streamer": {
            "type": "string",
            "description": "Entspricht `rgde_stations.live_streamer`. Trägt ein `[LIVE] `-Präfix, wenn aktuell eine als live markierte Sendung läuft."
          },
          "lautfm_url": {
            "type": "string",
            "format": "uri"
          },
          "lautfm_stream_mp3": {
            "type": "string",
            "format": "uri"
          },
          "lautfm_stream_m3u": {
            "type": "string",
            "format": "uri"
          },
          "lautfm_stream_pls": {
            "type": "string",
            "format": "uri"
          },
          "color": {
            "type": "string"
          },
          "fontcolor": {
            "type": "string"
          },
          "now_playing": {
            "$ref": "#/components/schemas/NowPlaying"
          }
        }
      },
      "SongHistoryEntry": {
        "type": "object",
        "properties": {
          "time": {
            "type": "string",
            "example": "14:32"
          },
          "artist": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "cover": {
            "type": "string",
            "format": "uri",
            "description": "Nur enthalten, wenn die aufgelöste Station zu Netzwerk 1 (LionFM) gehört."
          },
          "genre": {
            "type": "string"
          },
          "lyrics": {
            "type": "string"
          }
        }
      },
      "Cancellation": {
        "type": "object",
        "properties": {
          "date": {
            "type": "string",
            "format": "date"
          },
          "reason": {
            "type": "string",
            "nullable": true
          }
        }
      },
      "Show": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "station_id": {
            "type": "integer"
          },
          "station_name": {
            "type": "string"
          },
          "show_name": {
            "type": "string"
          },
          "streamer_name": {
            "type": "string",
            "nullable": true
          },
          "description": {
            "type": "string",
            "nullable": true
          },
          "image_url": {
            "type": "string",
            "nullable": true
          },
          "color": {
            "type": "string",
            "nullable": true
          },
          "start_date": {
            "type": "string",
            "format": "date",
            "nullable": true
          },
          "end_date": {
            "type": "string",
            "format": "date",
            "nullable": true
          },
          "start_time": {
            "type": "string",
            "nullable": true
          },
          "end_time": {
            "type": "string",
            "nullable": true
          },
          "repeat_type": {
            "type": "string",
            "nullable": true
          },
          "repeat_weekdays": {
            "type": "string",
            "nullable": true
          },
          "monthly_position": {
            "type": "string",
            "nullable": true
          },
          "weekday": {
            "type": "integer",
            "nullable": true
          },
          "cancellations": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Cancellation"
            }
          }
        }
      },
      "ShowScheduleResponse": {
        "type": "object",
        "properties": {
          "success": {
            "type": "boolean"
          },
          "count": {
            "type": "integer"
          },
          "shows": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Show"
            }
          }
        }
      },
      "NewSongStationRef": {
        "type": "object",
        "properties": {
          "station_id": {
            "type": "integer"
          },
          "display_name": {
            "type": "string"
          },
          "network_id": {
            "type": "integer"
          }
        }
      },
      "NewSong": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "artist": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "cover_url": {
            "type": "string",
            "nullable": true
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "stations": {
            "type": "array",
            "description": "Immer alle Zuordnungen des Songs, unabhängig vom gesetzten Filter.",
            "items": {
              "$ref": "#/components/schemas/NewSongStationRef"
            }
          }
        }
      },
      "NewSongsResponse": {
        "type": "object",
        "properties": {
          "success": {
            "type": "boolean"
          },
          "count": {
            "type": "integer"
          },
          "songs": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/NewSong"
            }
          }
        }
      },
      "ErrorMessage": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string"
          }
        }
      },
      "ErrorResponse": {
        "type": "object",
        "properties": {
          "success": {
            "type": "boolean",
            "example": false
          },
          "message": {
            "type": "string"
          }
        }
      },
      "Genre": {
        "type": "object",
        "properties": {
          "genre_id": {
            "type": "integer",
            "description": "Netzwerk-lokale ID, entspricht `?genre=` in stationData."
          },
          "genre_key": {
            "type": "string",
            "example": "charts"
          },
          "genre_name": {
            "type": "string",
            "example": "Pop",
            "description": "Kurzer Name für Apps und Filter-Chips."
          },
          "genre_title": {
            "type": "string",
            "example": "Pop & Hits",
            "description": "Langer Name für Websites. Ohne eigenen Wert identisch mit `genre_name`."
          },
          "genre_desc": {
            "type": "string",
            "nullable": true
          },
          "color": {
            "type": "string",
            "nullable": true,
            "description": "Hex ohne `#`, z. B. `5B7CFA`."
          },
          "image_url": {
            "type": "string",
            "format": "uri",
            "nullable": true
          },
          "sort_order": {
            "type": "integer"
          },
          "hidden": {
            "type": "boolean",
            "description": "`true` = nicht als Filter anzeigen."
          },
          "network_id": {
            "type": "integer"
          },
          "stations": {
            "type": "array",
            "items": {
              "type": "integer"
            },
            "description": "`ext_id`s der sichtbaren Sender in diesem Genre (= `station_id` in `stationData?network=…`)."
          }
        }
      },
      "GenreListResponse": {
        "type": "object",
        "properties": {
          "success": {
            "type": "boolean"
          },
          "network_id": {
            "type": "integer",
            "nullable": true
          },
          "count": {
            "type": "integer"
          },
          "genres": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Genre"
            }
          }
        }
      },
      "Event": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "station_id": {
            "type": "integer"
          },
          "station_name": {
            "type": "string"
          },
          "network_id": {
            "type": "integer"
          },
          "title": {
            "type": "string",
            "example": "RadioAid 2026"
          },
          "start_date": {
            "type": "string",
            "format": "date"
          },
          "end_date": {
            "type": "string",
            "format": "date",
            "nullable": true,
            "description": "Nur bei mehrtägigen Events."
          },
          "start_time": {
            "type": "string",
            "nullable": true,
            "example": "18:00:00"
          },
          "end_time": {
            "type": "string",
            "nullable": true,
            "example": "23:30:00"
          },
          "location": {
            "type": "string",
            "nullable": true,
            "example": "Internet"
          },
          "short_description": {
            "type": "string",
            "nullable": true,
            "description": "1–3 Sätze, max. 400 Zeichen."
          },
          "description": {
            "type": "string",
            "nullable": true,
            "description": "Ausführliche Informationen, Klartext mit Zeilenumbrüchen."
          },
          "external_url": {
            "type": "string",
            "format": "uri",
            "nullable": true
          },
          "image_url": {
            "type": "string",
            "format": "uri",
            "nullable": true,
            "description": "JPG, max. 1600 px Kantenlänge."
          },
          "image_credit": {
            "type": "string",
            "nullable": true,
            "description": "Bildquelle / Nachweis, zusammen mit dem Bild anzeigen."
          }
        }
      },
      "EventListResponse": {
        "type": "object",
        "properties": {
          "success": {
            "type": "boolean"
          },
          "count": {
            "type": "integer"
          },
          "events": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Event"
            }
          }
        }
      }
    }
  }
}
