Publieke koppeling
API Documentatie
De KNZB Competitiemodule biedt een publieke, read-only JSON API voor verenigingen, webmasters en leveranciers. De endpoints bevatten uitsluitend openbare competitiegegevens, vereisen geen login en zijn bedoeld voor clubwebsites, dashboards, ClubCast-integraties en narrowcasting.
Er is een Wordpress plugin beschikbaar om data te ontsluiten: KNZB Competitiestand (versie 1.4.2, getest met WordPress 7.1.3: stand, competitieanalyse, club-MVP's en vergelijkende uitslag). Vanaf 1.4.0 meldt WordPress nieuwe versies zelf bij Plugins en onder Dashboard → Updates (anonieme controle van api/knzb-competitiestand.json); oudere versies nog één keer handmatig bijwerken.
Bij /api/mvp.php en /api/vergelijkend.php heeft elke zwemmer of start het veld naam_publiek (true/false), volgens de instelling "Namen op MVP-stories" (metadata.namen_instelling). Externe weergaven, zoals de WordPress-plugin, tonen bij false geen naam maar vereniging en geboortejaar.
Alle responses bevatten api_version: "1.0". De API gebruikt server-side caching van 300 seconden. Ververs gegevens daarom bij voorkeur niet vaker dan één keer per vijf minuten.
Endpoint-overzicht
| Endpoint | Doel | Voorbeeld |
|---|---|---|
/api/teamstand.php | Stand van de context waarin één team uitkomt. Zonder team wordt team 1 gebruikt. | ?depotnr=05-014 of ?depotnr=05-014&team=2 |
/api/contextstand.php | Volledige stand van een klasse/divisie/poule. | ?klasse=1B |
/api/vereniging.php | Overzicht van alle competitieteams van één vereniging. | ?depotnr=05-014 of ?club=ZV+De+Waterkip |
/api/seizoenen.php | Beschikbare seizoenen. | geen parameters |
/api/klasses.php | Beschikbare klassecodes. | geen parameters |
/api/analyse.php | Competitieanalyse per leeftijdsgroep (krachtenkaart): matrix, profiel per team, ontwikkelpunten, potentieel en geboortejaar-cohorten. Alleen geaggregeerde cijfers, geen zwemmersnamen. | ?klasse=E&ronde=1 of ?depotnr=05-014&team=1&ronde=totaal |
/api/mvp.php | MVP's: zwemmers met de meeste punten boven het veld (per start het verschil met de mediaan van het programmanummer in de klasse, opgeteld; zonder estafettes). Ook landelijk over alle klassen. | ?klasse=E&geslacht=dames&limit=5 of ?depotnr=05-014&team=1&min_starts=2 of ?klasse=landelijk |
/api/vergelijkend.php | Vergelijkende uitslag: alle wedstrijden van een klasse en ronde samengevoegd tot één uitslag per programmanummer, met tussentijden en estafettevolgorde. Ook over alle klassen en over het seizoen. | ?klasse=E&ronde=1&programma=3 · ?bereik=alle&ronde=seizoen |
/api/telformulier.php | POST. Upload een LENEX/LXF; retourneert het telformulier/de wedstrijdstand. Niveau, ronde en tijdsoort worden automatisch bepaald. | multipart "file" of XML-body |
Interactieve hulpmodule
Live API Tester
Vul hieronder een depotnummer, verenigingsnaam, teamnummer of klasse in en test direct welke JSON de API teruggeeft.
Tip: gebruik deze tester om de juiste URL te bepalen en plak daarna de embed-code op de website van je vereniging.
{}
Parameters
| Endpoint | Parameter | Type | Verplicht | Voorbeeld |
|---|---|---|---|---|
| teamstand | depotnr | depotnummer | ja* | 05-014 |
| teamstand | club | exacte verenigingsnaam | ja* | ZV+De+Waterkip |
| teamstand | team | nummer | nee | 1; leeg = automatisch |
| contextstand | klasse | code | ja | E, 1A, A1 |
| vereniging | depotnr of club | depotnummer of exacte verenigingsnaam | ja | 05-014 of ZV+De+Waterkip |
| analyse | klasse | code | ja, tenzij depotnr | E, 1A, C5 |
| analyse | ronde | 1–5 of totaal | nee | leeg = laatst verwerkte ronde |
| analyse | depotnr + team | depotnummer + nummer | nee | 05-014 + 1: alleen het profiel van dat team; team=0 = enig team zonder nummer |
| mvp | klasse | code of landelijk | ja, tenzij depotnr | E; bij een poule (C5) alleen zwemmers van die poule, mediaan van de hele klasse. landelijk = alle klassen met een landelijke mediaan |
| mvp | maat | gemiddeld|boven_veld | nee | standaard gemiddeld: gerangschikt op gemiddelde punten per start (gemiddeld, altijd positief), met min_starts standaard 2. boven_veld: op de MVP-score (punten boven het veld) |
| mvp | alleen_klasse · mediaan | E|1|A|B|C · landelijk|klasse | nee | alleen bij klasse=landelijk |
| mvp | ronde | 1–5 of seizoen | nee | leeg = laatst verwerkte ronde |
| mvp | geslacht · leeftijdsgroep · geboortejaar | samen|heren|dames · o14|o16|o18|sen · jaar | nee | geslacht=dames&leeftijdsgroep=o16 |
| mvp | depotnr + team · min_starts · limit | depotnummer + nummer · getal · getal | nee | min_starts standaard 1, limit standaard 10 (0 = alles) |
| vergelijkend | klasse + ronde | code + 1–5 | ja | klasse=E&ronde=1; klasse=C = alle C-poules samen |
| vergelijkend | programma · starts | nummer · tellend|alle | nee | starts standaard tellend (gelijk aan de telformulieren) |
| vergelijkend | geboortejaar · limit | jaar · getal | nee | met geboortejaar ook plaats_veld; estafettes vallen weg. limit standaard 10 per nummer, 0 = alles |
| vergelijkend | bereik · ronde=seizoen | klasse|alle · seizoen | nee | bereik=alle: alle klassen samen (dan geen klasse nodig). ronde=seizoen: beste tijd per zwemmer per onderdeel over alle rondes |
| vergelijkend | onderdeel · enkelvoudig | sleutel · 1 | nee | onderdeel (bij seizoen) uit metadata.keuzes; enkelvoudig=1 toont bij alle klassen ook nummers die maar één klasse zwemt |
| vergelijkend | club · zoek | 05-014 · tekst | nee | club: ook de starts van die vereniging buiten de limit; zoek (min. 3 tekens) filtert op zwemmersnaam met behoud van de plaats |
| analyse | vergelijk | klassecode | nee (vereist depotnr) | E: plaats per leeftijdsgroep als het team in die klasse had gezwommen |
* Gebruik óf depotnr óf club. Depotnummer heeft de voorkeur omdat verenigingsnamen spellinggevoelig zijn. Spaties en bijzondere tekens in namen moeten URL-gecodeerd worden; de live tester doet dit automatisch.
Voorbeeldresponse teamstand
{
"api_version": "1.0",
"success": true,
"context": {
"seizoen": "2026-2027",
"klasse": "Eerste Divisie B",
"groep": "1B"
},
"requested_team": {
"naam": "DWK",
"depotnr": "05-014",
"team": 1,
"positie": 1
},
"stand": [
{
"positie": 1,
"naam": "DWK",
"depotnr": "05-014",
"team": 1,
"punten": 72888,
"ronde_1": 22873,
"ronde_2": 24178,
"ronde_3": 25837,
"ronde_4": null,
"ronde_5": null,
"pd_status": "promotie",
"pd_label": "Promoveert naar Eredivisie",
"is_requested": true
}
]
}
Voorbeeldresponse vereniging
{
"api_version": "1.0",
"success": true,
"vereniging": {
"naam": "DWK",
"depotnr": "05-014"
},
"seizoen": "2026-2027",
"teams": [
{
"team": 1,
"klasse": "1B",
"klasse_label": "Eerste Divisie B",
"positie": 1,
"punten": 72888,
"pd_status": "promotie",
"pd_label": "Promoveert naar Eredivisie"
}
]
}
Competitieanalyse (/api/analyse.php)
De analyse legt alle telformulieren van een klasse of poule naast elkaar. Per team: de punten per leeftijdsgroep (o14, o16, o18, senioren) en deelgroep (jongens/heren, meisjes/dames, estafette), het verschil met het gemiddelde van de klasse, de plaats, en per programmanummer het verschil met de mediaan van het veld. De bron is hetzelfde telformulier als op de standpagina (basistijdpunten per start), dus de punten zijn vergelijkbaar tussen wedstrijdlocaties.
- matrix: per team per groep
waarde,verschil,verschil_pct,stap(−3 … +3: <−25%, −25 tot −15, −15 tot −5, ±5, +5 tot +15, +15 tot +25, >+25%) enplaats. Bijronde=totaalookplaats_per_ronde. - profielen: per programmanummer de punten, plaats, gemiddelde, mediaan en beste totaal; de ontwikkelpunten (grootste negatieve verschillen met de mediaan, zonder lege starts); het potentieel (som van de achterstand op de mediaan); de geboortejaar-cohorten en de afhankelijkheid (aantal zwemmers en aandeel van de top 3).
- lege_starts: starts zonder geldige tijd (vervangende 100 punten). Die staan apart en tellen niet als gewone lage score.
- metadata.afwijking_officieel: teams waarvan het officiële puntentotaal afwijkt van het telformulier (bijvoorbeeld door een handmatige correctie).
Privacy: de API bevat geen zwemmersnamen, tijden of startnummers; alleen geaggregeerde cijfers en geboortejaren. Vergelijken met een andere klasse gebeurt alleen over de programmanummers die in beide klassen gelijk zijn; nummers_vergeleken en volledig laten zien hoe volledig de vergelijking is.
Voorbeeldresponse (ingekort)
{
"api_version": "1.0",
"success": true,
"metadata": {
"seizoen": "2026-2027",
"klasse": "E",
"klasse_label": "Eredivisie",
"ronde": 1,
"rondes_verwerkt": [1],
"aantal_teams": 18,
"aantal_starts": 720
},
"matrix": {
"groepen": [{ "key": "o14", "label": "12–13 jaar (o14)", "gemiddelde": 3926, "deelgroepen": [ … ] }],
"teams": [{
"naam": "DWK 1", "depotnr": "05-014", "team": 1, "totaal": 22375, "plaats": 11,
"groepen": { "o14": { "waarde": 3561, "verschil": -365, "verschil_pct": -9.3, "stap": -1, "plaats": 11 } }
}]
},
"profielen": [{ "naam": "DWK 1", "potentieel": 1451, "ontwikkelpunten": [ … ], "cohorten": [ … ] }],
"lege_starts": [{ "naam": "ACZ", "nr": 31, "onderdeel": "200m schoolslag jongens" }]
}
MVP's (/api/mvp.php)
Maatstaf. Standaard (maat=gemiddeld) wordt gerangschikt op de gemiddelde basistijdpunten per tellende individuele start (veld gemiddeld, minimaal 2 starts). Dat getal is altijd positief, ook voor zwemmers in een sterk veld. Met maat=boven_veld wordt gerangschikt op de MVP-score hieronder. Beide velden staan altijd in de respons.
Punten boven het veld: per individuele start het verschil met de mediaan van dat programmanummer in de klasse (alle poules samen), opgeteld. Estafettes tellen niet mee; een start zonder geldige tijd telt niet mee voor de zwemmer, maar wel (met 100 punten) voor de mediaan. Bij ronde=seizoen is de score de som van de rondescores. Gelijke (afgeronde) score = zelfde plaats; daarbinnen eerst de zwemmer met meer starts.
Namen, geboortejaren en tijden zijn openbaar, net als op de telformulieren. Startnummers staan nooit in de respons. Met depotnr bevat elke zwemmer ook aandeel_teampunten (aandeel in de individuele punten van het team).
Voorbeeldresponse (ingekort)
{
"api_version": "1.0",
"success": true,
"metadata": { "klasse": "E", "ronde": 1, "geslacht": "samen", "aantal_zwemmers": 401, "medianen": [ … ] },
"zwemmers": [{
"plaats": 1, "naam": "Yill van Bezouw", "geboortejaar": 2011, "team_naam": "PSV 1", "geslacht": "dames",
"mvp_score": 638, "punten": 1777, "aantal_starts": 2,
"starts": [{ "programmanummer": 17, "onderdeel": "200m vrije slag", "leeftijdsgroep": "o16", "tijd": "1:58.45", "punten": 937, "verschil": 382 }]
}]
}
Landelijk (klasse=landelijk)
Alle klassen samen. Per ronde en programmanummer is er één landelijke mediaan over de tellende individuele starts van alle klassen die dat nummer zwemmen (binnen een ronde is een nummer in elke klasse hetzelfde onderdeel). Lege starts tellen hier níet mee in de mediaan: het aantal plekken per nummer verschilt per klasse, en in de lagere klassen is een groot deel leeg. De landelijke score is daarom niet gelijk aan de score op de klassepagina. Elke zwemmer heeft een klasse (bv. 1A).
Extra parameters: alleen_klasse=E|1|A|B|C (alleen zwemmers uit die klasse, met de landelijke mediaan), depotnr (alle teams van die vereniging) en mediaan=klasse (de mediaan van de eigen klasse, zoals op de klassepagina; de lijst sorteert dan alleen alle klassen door elkaar). Bij een koude cache: metadata.onvolledig = true met wedstrijden_geladen/wedstrijden_totaal; vraag dan opnieuw op.
GET /api/mvp.php?klasse=landelijk&ronde=1&limit=3
"zwemmers": [
{ "plaats": 1, "naam": "Tiago Fonseca Gomes", "klasse": "E", "team_naam": "PSV 1", "mvp_score": 1076, … },
{ "plaats": 2, "naam": "Giovanni Phillipson", "klasse": "E", "mvp_score": 983, … },
{ "plaats": 3, "naam": "Femke Spiering", "klasse": "E", "mvp_score": 938, … }
]
Vergelijkende uitslag (/api/vergelijkend.php)
Een competitieronde wordt per klasse op meerdere locaties gezwommen. Dit endpoint voegt alle wedstrijden van een klasse (of poule) en ronde samen tot één uitslag per programmanummer, alsof iedereen in hetzelfde bad zwom. Bron: de officiële LXF-uitslagen uit de webkalender. In de modus starts=tellend staan precies de starts van de telformulieren (herkend aan de telformulieren van de competitieservice); starts=alle geeft ook niet-tellende starts en starts buiten mededinging (bm: true, alleen bij status EXH in de uitslag).
Gelijke tijden delen een plaats. DSQ, DNS en DNF staan in niet_gerangschikt; afgemelde zwemmers (WDR/SICK) zijn geen start. Per start: tellend, de tussentijden (cumulatief) en bij estafettes de zwemmers in volgorde. Namen, geboortejaren en tijden zijn openbaar (zoals op de webkalender); startnummers en volledige geboortedata staan nooit in de respons.
Voorbeeldresponse (ingekort)
{
"api_version": "1.0",
"success": true,
"metadata": { "klasse": "E", "ronde": 1, "starts": "tellend", "aantal_gerangschikt": 717,
"wedstrijden": [ { "wedid": 29171, "plaats": "Amersfoort", "datum": "2026-10-03" } ] },
"programmanummers": [{
"programmanummer": 3, "onderdeel": "100m wisselslag heren", "leeftijdsgroep": "sen",
"starts": [{ "plaats": 1, "naam": "Giovanni Phillipson", "geboortejaar": 2008, "team_naam": "Team Noord Holland (SG) 1",
"tijd": "54.89", "punten": 839, "tellend": true, "bm": false, "wedid": 29071, "tussentijden": [ … ] }],
"niet_gerangschikt": [ … ]
}]
}
Alle klassen en seizoen
Met bereik=alle worden alle klassen samengevoegd. Binnen een ronde is een programmanummer in elke klasse hetzelfde onderdeel (afstand, slag, geslacht en categorie). Per nummer staat in klassen welke klassen het zwemmen (op_programma) en hoeveel starts per klasse gerangschikt zijn; elke start heeft een klasse (bv. B2). Standaard alleen nummers die minstens twee klassen zwemmen.
Met ronde=seizoen wordt per onderdeel gegroepeerd, want het programma verschilt per ronde. Per zwemmer (of estafetteteam) telt de beste tijd; ronde en aantal_keer staan bij de start en voorkomens bij het onderdeel.
Landelijk zijn dat tientallen uitslagen. Zijn die nog niet eerder opgevraagd, dan haalt één verzoek er hoogstens 8 op bij de webkalender en antwoordt met "metadata": { "onvolledig": true, "wedstrijden_geladen": 16, "wedstrijden_totaal": 49 }. Vraag dan hetzelfde verzoek opnieuw op. Zo'n antwoord wordt niet gecachet.
GET /api/vergelijkend.php?bereik=alle&ronde=1&programma=5&limit=3
"programmanummers": [{
"sleutel": "5", "programmanummer": 5, "onderdeel": "100m wisselslag jongens",
"klassen": [ { "klasse": "E", "op_programma": true, "gerangschikt": 36 }, … , { "klasse": "C", "op_programma": true, "gerangschikt": 28 } ],
"aantal_gerangschikt": 267,
"starts": [ { "plaats": 1, "naam": "Tobian Vos", "klasse": "E", "ronde": 1, "tijd": "57.89", … } ]
}]
Embed Widget
Plaats onderstaande code op een clubwebsite om automatisch de stand van één team te tonen. De widget gebruikt pure JavaScript, laadt geen externe libraries en doet één API-call.
<script
src="https://zwemcompetitie.knzb.nl/competitieservice/embed/stand.js"
data-depotnr="05-014"
data-team="1">
</script>
data-depotnr is het depotnummer van de vereniging. data-team is optioneel; zonder teamnummer wordt automatisch het enige team van de vereniging gebruikt, of team 1 bij meerdere teams. Gebruik bij voorkeur data-depotnr. Als alleen een verenigingsnaam beschikbaar is, kan data-club="ZV De Waterkip" worden gebruikt.
Teamwidget zonder teamnummer
<script
src="https://zwemcompetitie.knzb.nl/competitieservice/embed/stand.js"
data-depotnr="05-014">
</script>
Teamwidget met verenigingsnaam
<script
src="https://zwemcompetitie.knzb.nl/competitieservice/embed/stand.js"
data-club="ZV De Waterkip">
</script>
Voorbereid voor verenigingswidget
<script
src="https://zwemcompetitie.knzb.nl/competitieservice/embed/stand.js"
data-depotnr="05-014"
data-view="vereniging">
</script>
Deze verenigingsweergave is alvast gedocumenteerd voor toekomstige uitbreiding van de widget. Het onderliggende endpoint /api/vereniging.php is al beschikbaar.
Telformulier-API (POST)
Met /api/telformulier.php stuur je een LENEX/LXF-bestand in en krijg je het telformulier/de wedstrijdstand terug. Depot- en teamnummer worden uit het bestand gehaald; het niveau komt uit de competitie-indeling, de ronde uit de wedstrijddatum en de tijdsoort uit de inhoud (resultaten → uitslag, alleen inschrijftijden → prognose). Het endpoint is read-only en schrijft niets naar de database.
Aanroep
- Methode:
POST. - Bestand als
multipart/form-data, veldfile(.lxf/.lef/.lenex/.xml), of de rauwe LENEX-XML als request-body metContent-Type: application/xml. - Maximale bestandsgrootte: 8 MB.
?detail=1voegt per ploeg het XML-telformulier toe aan de JSON.?format=xmlgeeft het telformulier in het bestaande KNZB-XML-formaat in plaats van JSON.
Voorbeeld (multipart-upload)
curl -X POST \
-F "file=@uitslag-ronde1.lxf" \
"https://zwemcompetitie.knzb.nl/competitieservice/api/telformulier.php"
Voorbeeld (rauwe XML-body, XML-telformulier terug)
curl -X POST \
-H "Content-Type: application/xml" \
--data-binary @wedstrijd.lef \
"https://zwemcompetitie.knzb.nl/competitieservice/api/telformulier.php?format=xml"
Voorbeeldresponse (JSON)
{
"api_version": "1.0",
"success": true,
"modus": "prognose",
"wedstrijd": {
"naam": "KNZB Competitie Ronde 1",
"datum": "2026-10-03",
"ronde": 1,
"baan": "SCM",
"seizoen": "2026-2027"
},
"stand": [
{
"positie": 1,
"depotnr": "05-014",
"team": 1,
"naam": "DWK",
"klasse": "1",
"klasse_label": "Eerste Divisie B",
"punten": 24178
}
],
"overgeslagen": [],
"waarschuwingen": []
}
Ploegen die niet in de competitie-indeling van het actieve seizoen staan, komen in overgeslagen en tellen niet mee. Lukt de ronde- of tijdsoortbepaling niet, dan volgt success: false met een foutmelding.
Technische details
- Content-Type:
application/json; charset=utf-8. - CORS staat standaard open met
Access-Control-Allow-Origin: *. Dit kan later inincludes/api_functions.phpworden beperkt tot toegestane domeinen. - Cachemap:
cache/api/, cacheduur: 300 seconden. - De standen-endpoints zijn
GETen gecachet (300 s)./api/telformulier.phpisPOSTen niet gecachet. Alle endpoints zijn read-only (geen database-writes). - Foutresponses gebruiken
success: falsemet een foutmelding en passende HTTP-statuscode.