Spielplan-API für Vereine

Die Spielplan-API liefert Vereinen ihre anstehenden Spiele aus TeamSL. Zu jedem Spiel gehören der Stand der Schiedsrichter-Ansetzungen und die Entfernung der angesetzten Schiedsrichter:innen zur Halle. Die Daten kommen als JSON für eigene Programme oder als CSV für Excel und Google Sheets.

Zugang nur mit API-Schlüssel: Vereine beantragen ihren Schlüssel per E-Mail bei support@nbbv.de. Details unter Zugang.

Du willst nur den Spielplan eines Teams oder Vereins im Kalender sehen? Dafür brauchst du keinen Schlüssel: Kalender-Abo.

Zugang

Für die API braucht dein Verein einen persönlichen API-Schlüssel. Er beginnt mit spa_ und gilt nur für die Vereinsnummer deines Vereins. Spielgemeinschaften, an denen dein Verein beteiligt ist, können wir auf Wunsch mit aufnehmen.

Schlüssel beantragen

API-Schlüssel müssen per E-Mail an support@nbbv.de beantragt werden. Bitte gib an:

  • Vereinsname und Vereinsnummer
  • Name und Funktion der Person, die den Schlüssel nutzt, mit Kontakt-E-Mail
  • ggf. Spielgemeinschaften, an denen dein Verein beteiligt ist und die mit abgefragt werden sollen
  • kurz den Verwendungszweck (z. B. Übersicht der Heimspiele, Fahrtkostenplanung)

Schlüssel per E-Mail beantragen

Behandle den Schlüssel wie ein Passwort. Gib ihn nicht weiter und veröffentliche keine Links oder Tabellen, die ihn enthalten. Ist ein Schlüssel in falsche Hände geraten, sperren wir ihn und stellen einen neuen aus.

Den Schlüssel übergibst du bei jeder Anfrage auf eine von zwei Arten:

  • Als HTTP-Header X-API-Key: spa_… (empfohlen, für Programme und Skripte)
  • Als Parameter api_key=spa_… in der Adresse, wenn sich kein Header setzen lässt, etwa im Browser oder in Google Sheets. Der Schlüssel steht dann aber im Browserverlauf.

Schnellstart

Die Spiele der nächsten 7 Tage, zum Anschauen im Browser:

https://nbbv-basketball.de/spielplan-api/v1/spiele?vereinsnummer=720000&tage=7&api_key=DEIN_SCHLÜSSEL

Mit dem Baukasten stellst du dir die Adresse selbst zusammen:

Ersetze DEIN_SCHLÜSSEL durch deinen API-Schlüssel. Der Baukasten sendet nichts, er setzt nur die Adresse zusammen.

Parameter

GET https://nbbv-basketball.de/spielplan-api/v1/spiele

ParameterWerteStandardBedeutung
vereinsnummerz. B. 720000 oder 0720000Verein des SchlüsselsVereinsnummer aus TeamSL. Muss zum Schlüssel passen.
modusalle, heim, auswaertsalleHeim- und Auswärtsspiele, nur Heim- oder nur Auswärtsspiele. Heimspiele einer Spielgemeinschaft zählen als Heimspiele.
tage0 bis 3657Blick nach vorn ab heute. tage=1 heißt: nur heute. tage=7 heißt: heute und die folgenden 6 Tage. tage=0 geht nur zusammen mit zurueck.
zurueck0 bis 3650Blick zurück: die Tage vor heute, mit Ergebnissen. zurueck=7&tage=0 liefert die letzte Woche, zurueck=365&tage=365 die ganze Saison.
formatjson, csvjsonAusgabeformat
trennersemikolon, kommasemikolonNur bei CSV: Semikolon mit Dezimalkomma für Excel, Komma mit Dezimalpunkt für Google Sheets und andere Programme.
api_keydein Schlüssel–Nur, wenn du den Header X-API-Key nicht setzen kannst.

Die Spiele sind nach Datum und Uhrzeit sortiert. Zeiten gelten in deutscher Ortszeit.

Spiele eines Teams

Für die Spiele genau eines Teams, etwa für eine Team-Seite, gibt es /v1/teamspiele. Du gibst Team und Liga an und bekommst dieselben Daten im selben Format wie bei /v1/spiele, nur für dieses Team.

https://nbbv-basketball.de/spielplan-api/v1/teamspiele?vereinsnummer=720000&team=TSV+Musterstadt+2&liga=54136&tage=365
ParameterBedeutung
vereinsnummerwie bei /v1/spiele
teamPflicht. Teamname genau wie in TeamSL, URL-kodiert (Leerzeichen als + oder %20)
ligaPflicht. Liga-ID (empfohlen) oder der genaue Liga- bzw. Kurzname
modus, tage, format, trennerwie bei /v1/spiele

Die Liga ist nötig, weil derselbe Teamname in mehreren Ligen vorkommen kann, zum Beispiel in Liga und Pokal.

Teamname und Liga nachschlagen

Die genauen Namen und Liga-IDs liefert die Teamliste deines Vereins. Sie enthält alle Teams mit künftigen Spielen, je Liga:

https://nbbv-basketball.de/spielplan-api/v1/teamliste?vereinsnummer=720000
{
  "verein": { "vereinsnummer": 720000, "name": "TSV Musterstadt" },
  "anzahl": 12,
  "teams": [
    {
      "team": "TSV Musterstadt 2",
      "liga": { "id": 54136, "name": "Landesliga Herren 1", "kurz": "LLH1", "altersklasse": "Senioren", "geschlecht": "männlich" },
      "spiele": 14,
      "naechstesSpiel": "2026-10-03",
      "abfrage": "vereinsnummer=720000&team=TSV+Musterstadt+2&liga=54136"
    }
  ]
}

Der Wert in abfrage ist fertig kodiert. Hänge ihn an /v1/teamspiele? an und ergänze bei Bedarf tage oder format.

Die Teamnamen kommen aus TeamSL. Ändert der Verein dort einen Namen, ändert sich auch die Abfrage.

Antwort als JSON

Beispiel, gekürzt auf ein Spiel:

{
  "verein": { "vereinsnummer": 720000, "name": "TSV Musterstadt" },
  "zeitraum": { "von": "2026-09-28", "bis": "2026-10-04", "tage": 7 },
  "modus": "alle",
  "stand": "2026-09-28T07:50:12Z",
  "anzahl": 13,
  "spiele": [
    {
      "spielId": 2800001,
      "geaendertAm": "2026-09-27T18:04:11Z",
      "datum": "2026-10-03",
      "wochentag": "Sa",
      "uhrzeit": "16:00",
      "heimspiel": true,
      "liga": {
        "name": "Landesliga Herren 1", "kurz": "LLH1",
        "altersklasse": "Senioren", "geschlecht": "männlich",
        "bezirk": null, "verband": "NBBV"
      },
      "spielnr": 42,
      "spieltag": 3,
      "heim": {
        "team": "TSV Musterstadt 2", "verein": "TSV Musterstadt", "vereinsnummer": 720000,
        "trikot": { "hemd": "blau", "hose": "blau" }
      },
      "gast": {
        "team": "SC Beispielort", "verein": "SC Beispielort", "vereinsnummer": 710000,
        "trikot": { "hemd": "weiß", "hose": "schwarz" }
      },
      "halle": { "name": "Sporthalle am Markt", "strasse": "Marktstraße 1", "plz": "30159", "ort": "Hannover" },
      "abgesagt": false,
      "verzicht": false,
      "spielortGeaendert": false,
      "spielzeitGeaendert": false,
      "ergebnis": null,
      "ansetzungenVerfuegbar": true,
      "ansetzungen": [
        { "position": "SR1", "status": "angesetzt", "bestaetigt": true, "verein": null, "entfernungKm": 8.8 },
        { "position": "SR2", "status": "Verein zuständig", "bestaetigt": null, "verein": "TSV Musterstadt", "entfernungKm": null }
      ]
    }
  ]
}
FeldBedeutung
standZeitpunkt des letzten Abgleichs mit TeamSL (UTC, ISO 8601)
spielIdEindeutige Spiel-ID aus TeamSL, gleich bleibend auch bei Verlegungen
geaendertAmZeitpunkt der letzten Änderung an diesem Spiel (UTC, ISO 8601): Termin, Halle, Teams, Status, Ansetzungen oder Entfernung. Ein Abgleich ohne Änderung setzt ihn nicht neu. So erkennst du, was sich seit deinem letzten Abruf geändert hat.
spielnr, spieltagSpielnummer und Spieltag laut Spielplan
heimspieltrue, wenn dein Verein (oder deine Spielgemeinschaft) Heimverein ist
heim.trikot, gast.trikotTrikotfarben laut TeamSL: Heimteam in Heimfarben, Gastteam in Auswärtsfarben. null, wenn der Verein keine Farben hinterlegt hat.
liga.kurzKurzname der Liga wie in der SR-Ansetzung
abgesagt, verzichtSpiel abgesagt bzw. Verzicht eines Teams
spielortGeaendert, spielzeitGeaendertSpielort oder Tipoff-Zeit wurde gegenüber dem ursprünglichen Plan geändert
ergebnisBei gespielten Spielen der Endstand, z. B. { "heim": 58, "gast": 85, "bestaetigt": true }. bestaetigt ist false, solange das Ergebnis in TeamSL noch nicht bestätigt ist. Bei künftigen Spielen null.
ansetzungenVerfuegbarfalse, wenn die Ansetzungen beim letzten Abgleich nicht geladen werden konnten. Dann fehlen ansetzungen oder sind veraltet.
ansetzungenEine Zeile je Position, siehe nächster Abschnitt

Felder, die TeamSL nicht liefert, sind null.

Ansetzungen und Entfernung

SR1 und SR2 sind immer enthalten. SR3, Kommissar:in (KOM) und SR-Coach (COA) erscheinen nur, wenn die Position im Spiel besetzt oder vergeben ist.

statusBedeutung
angesetztEine Person ist angesetzt. bestaetigt sagt, ob sie die Ansetzung in TeamSL bestätigt hat.
vorläufig angesetztDie Ansetzung ist noch vorläufig.
offen angebotenDie Position wird in der Spielebörse angeboten. verein nennt den Verein, der sie abgegeben hat.
Verein zuständigDie Position ist einem Verein zugeteilt, der sie selbst besetzen muss (verein).
nicht besetztNoch niemand angesetzt und kein Verein zuständig.

Entfernung

entfernungKm ist die Entfernung vom Wohnort der angesetzten Person zur Halle in Kilometern, auf eine Nachkommastelle gerundet. Es ist der Wert, den TeamSL in der Schiedsrichter-Ansetzung anzeigt, als einfache Strecke. Er hilft zum Beispiel, die Fahrtkosten abzuschätzen.

Die Entfernung ist null, wenn

  • niemand angesetzt ist,
  • die Person nicht zum Schiedsrichter-Pool des NBBV gehört (zum Beispiel bei Regionalliga-Ansetzungen aus anderen Landesverbänden),
  • TeamSL für die Person keine Geodaten hat,
  • die Entfernung zur Halle noch nicht ermittelt werden konnte, etwa bei Hallen außerhalb Niedersachsens und Bremens.

Antwort als CSV

Mit format=csv gibt es eine Zeile je Spiel. Standardmäßig ist die Datei mit Semikolon getrennt, nutzt das Dezimalkomma und beginnt mit einer Byte-Order-Mark, damit Excel Umlaute und Spalten direkt richtig erkennt. Mit trenner=komma kommen Komma, Dezimalpunkt und keine Byte-Order-Mark.

Spalten in dieser Reihenfolge:

SpaltenInhalt
spiel_id, datum, wochentag, uhrzeitwie im JSON
heim_auswaertsheim oder auswärts
liga, liga_kurz, altersklasse, geschlecht, spielnrLiga und Spielnummer
heim, heim_vereinsnummer, gast, gast_vereinsnummerTeams und Vereinsnummern
heim_trikot, gast_trikotTrikotfarben als „Hemd / Hose“
halle, halle_strasse, halle_plz, halle_ortSpielort
abgesagt, verzicht, spielort_geaendert, spielzeit_geaendertja oder nein
ergebnis_heim, ergebnis_gast, ergebnis_bestaetigtEndstand und ob er bestätigt ist, bei künftigen Spielen leer
geaendert_amwie geaendertAm im JSON
sr1_status, sr1_bestaetigt, sr1_verein, sr1_entfernung_kmAnsetzung SR1, ebenso für sr2_, sr3_, kom_ und coa_

Leere Zellen bedeuten: kein Wert vorhanden.

Beispiele

Excel (Power Query)

  1. Daten → Aus dem Web → Erweitert wählen.
  2. Als URL eintragen: https://nbbv-basketball.de/spielplan-api/v1/spiele?vereinsnummer=720000&tage=14&format=csv
  3. Unter HTTP-Anforderungsheaderparameter links X-API-Key und rechts deinen Schlüssel eintragen.
  4. Laden. Mit Daten → Alle aktualisieren holst du später den aktuellen Stand.

Google Sheets

In eine leere Zelle:

=IMPORTDATA("https://nbbv-basketball.de/spielplan-api/v1/spiele?vereinsnummer=720000&tage=14&format=csv&trenner=komma&api_key=DEIN_SCHLÜSSEL")

Google Sheets kann keinen Header setzen, deshalb steht der Schlüssel hier in der Formel. Teile die Tabelle nur mit Personen, die den Schlüssel sehen dürfen.

Kommandozeile (curl)

curl -H "X-API-Key: DEIN_SCHLÜSSEL" \
  "https://nbbv-basketball.de/spielplan-api/v1/spiele?vereinsnummer=720000&modus=heim&tage=14"

JavaScript

const res = await fetch(
  "https://nbbv-basketball.de/spielplan-api/v1/spiele?vereinsnummer=720000&tage=7",
  { headers: { "X-API-Key": process.env.SPIELPLAN_API_KEY } }
);
const { spiele } = await res.json();
for (const s of spiele) {
  console.log(s.datum, s.uhrzeit, s.heim.team, "–", s.gast.team);
}

Den Schlüssel nur serverseitig verwenden, nie in Webseiten-Code, den Besucher:innen einsehen können.

Python

import os, requests

r = requests.get(
    "https://nbbv-basketball.de/spielplan-api/v1/spiele",
    params={"vereinsnummer": 720000, "modus": "alle", "tage": 7},
    headers={"X-API-Key": os.environ["SPIELPLAN_API_KEY"]},
    timeout=30,
)
r.raise_for_status()
for s in r.json()["spiele"]:
    print(s["datum"], s["uhrzeit"], s["heim"]["team"], "–", s["gast"]["team"])

Fehlermeldungen und Limits

Bei Fehlern antwortet die API mit einem passenden HTTP-Status und einer kurzen Erklärung, zum Beispiel {"error": "tage muss eine ganze Zahl von 0 bis 365 sein"}.

StatusBedeutung
400Ein Parameter ist ungültig, unbekannt oder doppelt angegeben.
401Der API-Schlüssel fehlt oder ist ungültig bzw. gesperrt.
403Der Schlüssel gilt nicht für die angefragte Vereinsnummer, oder die Anfrage kam nicht per GET.
404Die Adresse gibt es nicht.
405Nur Abrufe per GET sind erlaubt.
429Zu viele Anfragen, siehe Limits. Bitte kurz warten.
500, 502Störung bei uns. Bitte später erneut versuchen und bei längeren Ausfällen Bescheid geben.

Limits

  • Höchstens 60 Abrufe pro Minute und Schlüssel.
  • Pro Absender höchstens 2 Anfragen pro Sekunde (kurze Spitzen bis 20 sind erlaubt) und 10 gleichzeitige Verbindungen.
  • Nach 20 Anfragen mit fehlendem oder falschem Schlüssel innerhalb von 10 Minuten wird der Absender für den Rest dieser 10 Minuten gesperrt.

Ob die API erreichbar ist und wann zuletzt abgeglichen wurde, zeigt ohne Schlüssel: /spielplan-api/v1/health

Aktualität und Grenzen

  • Spiele der nächsten rund 5 Wochen werden täglich von 7 bis 23 Uhr alle 30 Minuten mit TeamSL abgeglichen. Änderungen erscheinen dort also mit bis zu 30 Minuten Verzögerung, nachts erst am Morgen.
  • Alle späteren Spiele bis Saisonende werden einmal pro Nacht abgeglichen. Für sie können Termine und Ansetzungen bis zu einen Tag alt sein.
  • Maßgeblich ist immer TeamSL.
  • Die API liefert Spiele bis höchstens 365 Tage im Voraus, praktisch also bis Saisonende. Vergangene Spiele der laufenden Saison gibt es mit zurueck, jeweils mit Ergebnis. Ergebnisse der letzten 30 Tage werden tagsüber alle 30 Minuten übernommen, ältere nachts.
  • Spiele, die TeamSL noch nicht im Spielplan führt (z. B. spätere Pokal- oder Endrunden), erscheinen erst, sobald sie dort angelegt sind.
  • Verlegte Spiele erscheinen am neuen Termin, gestrichene Spiele verschwinden beim nächsten Abgleich.
  • Häufigeres Abfragen als alle 30 Minuten bringt keine neueren Daten.

Datenschutz

Die API gibt keine personenbezogenen Daten von Schiedsrichter:innen heraus: keine Namen, keine Kontaktdaten, keine Lizenznummern und keine Adressen. Zu jeder Position liefert sie nur den Status, den zuständigen Verein und die Entfernung zur Halle in Kilometern.

Wer angesetzt ist, sehen berechtigte Vereinsvertreter:innen wie bisher direkt in TeamSL.