Beste practices voor API-versionering, voor aanroepers
7 min lezen
API-versionering is de praktijk om een oud contract werkend te houden nadat je het hebt veranderd, zodat aanroepers op hun eigen schema kunnen overstappen in plaats van op het jouwe. Die zin bevat de twee beslissingen die ertoe doen: wat telt als het contract veranderen, en hoelang het oude blijft werken. Waar het versienummer leeft, waar de meeste versioneringsdiscussies over gaan, is de minst belangrijke van de drie en de makkelijkste om goed te krijgen.
Wanneer zou je een API moeten versioneren?
Versioneer een API alleen wanneer een verandering een correcte aanroeper zou breken. Additieve veranderingen, nieuwe velden, nieuwe endpoints, nieuwe optionele parameters, hebben geen versie nodig; aanroepers geschreven tegen het oude contract blijven werken en de nieuwe mogelijkheid is er gewoon. Een breaking change heeft er wel een nodig, want het alternatief is dat een aanroeper het van een fout verneemt. Elke release versioneren, ook de additieve, leert aanroepers dat versies ruis zijn, en ze stoppen met de berichten te lezen die ertoe doen.
De praktische test is dezelfde als in het artikel over breaking changes: als een aanroeper die alleen op gedocumenteerd gedrag vertrouwde iets moet veranderen om te blijven werken, heeft de verandering een versie nodig. Zo niet, breng het uit onder de huidige versie en schrijf een changelog-entry.
Welk API-versioneringsschema zou je moeten gebruiken?
Gebruik het schema dat jullie aanroepers het makkelijkst kunnen zien en instellen, wat voor de meeste publieke API’s een versie in het URL-pad of een gedateerde versie-header is. De vier gangbare schema’s verschillen minder in mogelijkheden dan in wat ze van de aanroeper vragen, en dat is de juiste basis om te kiezen.
| Schema | Voorbeeld | Wat de aanroeper moet doen | Wie het gebruikt |
|---|---|---|---|
| URL-pad | /v2/invoices | De URL veranderen bij het migreren | De meeste publieke REST-API’s |
| Versie-header | X-GitHub-Api-Version: 2022-11-28 | Een header sturen, of de standaard accepteren | GitHub |
| Gedateerde accountversie | Stripe-Version: 2026-08-26 | Een datum vastzetten per verzoek of per account | Stripe |
| Queryparameter | /invoices?version=2 | Een parameter toevoegen | Oudere API’s; nu zelden gekozen |
| Mediatype | Accept: application/vnd.example.v2+json | Contenttypes onderhandelen | Puristen; weinig aanroepers beheersen dit |
URL-pad is het meest zichtbaar en het minst flexibel. Elke aanroeper kan zien in welke versie hij zit door een logregel te lezen, en een versiesprong is een zoek-en-vervang. De kosten: het hele oppervlak verschuift ineens, je kunt het contract van één endpoint niet veranderen zonder een nieuwe versie te slaan voor alle, dus URL-versies zijn doorgaans zeldzaam en groot.
Versie-header houdt URL’s stabiel en laat de server een standaard kiezen voor aanroepers die
niets sturen, zoals
GitHubs REST-API-versionering
werkt: een op datum genoemde versie in X-GitHub-Api-Version, met de oudst ondersteunde versie als
standaard zodat ongeversioneerde aanroepers niet breken. De kosten: de versie is onzichtbaar in een
URL en makkelijk te vergeten in een nieuwe client.
Gedateerde accountversie is het header-schema plus één toevoeging: de versie wordt tegen het
account opgeslagen, zodat elk verzoek hem krijgt zonder iets te sturen.
Stripes API-versionering zet elk account vast op de
versie waarmee het is aangemaakt en laat een verzoek dat overschrijven met Stripe-Version. Dit is
het aanroeper-vriendelijkste schema en het meeste werk om te draaien, omdat de server moet
vertalen tussen elke ondersteunde versie en de huidige.
Queryparameter en mediatype werken beide en falen beide de zichtbaarheidstest op andere wijze: een queryparameter valt makkelijk weg bij het bouwen van een URL, en een mediatype-versie is onzichtbaar voor bijna elke tool waarmee een aanroeper debugt. Het datumschema van Stripe is het bekendste voorbeeld van de datumaanpak, en hoe Stripe zijn API versioneert loopt het door.
Hoe doe je API-versionering in de praktijk?
In de praktijk is een versie een genoemde set gedragingen, en de server mapt elk verzoek op één daarvan. De stappen zijn hetzelfde ongeacht welk schema de naam draagt.
- Benoem versies op datum of geheel getal, niet op semantische versie. Een web-API is geen
pakket. Aanroepers kunnen geen minor-versie van een URL vastpinnen, dus
v2of2026-08-26zegt alles wat een aanroeper nodig heeft, en semantische versionering impliceert een compatibiliteitsbelofte die het schema niet kan nakomen. - Houd de versie buiten de codepaden die zich er niet om bekommeren. Een versie zou aan de rand een vertaallaag moeten kiezen, niet de bedrijfslogica splitsen. Twee volledige kopieën van de codebase is hoe een versie onderhoudbaar wordt.
- Geef elke versie een standaard en een document. Aanroepers die geen versie sturen krijgen de oudst ondersteunde, nooit de nieuwste, zodat een niet-vastgepinde client niet breekt op releasedag. Elke versie heeft een pagina die zegt wat er is veranderd ten opzichte van de vorige.
- Stel een supportvenster in en publiceer het. Googles versioneringsrichtlijn, AIP-185, vraagt om een redelijke, goed gecommuniceerde overgangsperiode en raadt 180 dagen aan, zelfs voor bètafunctionaliteit. Kies een venster, schrijf het op, en pas het toe zonder per versie opnieuw te onderhandelen.
- Trek versies met pensioen zoals je endpoints met pensioen trekt. Een versie voorbij zijn
venster krijgt dezelfde behandeling als elke gedeprecieerde API:
een aankondiging, een
Sunset-header (RFC 8594) op elk antwoord, een halverwege-herinnering aan de overgebleven aanroepers, en een verwijderdatum die standhoudt.
Wat zijn v1 en v2 in een REST-API?
v1 en v2 zijn namen voor twee contracten die dezelfde server tegelijk ondersteunt. Een v2
bestaat omdat iets in v1 niet kon worden veranderd zonder zijn aanroepers te breken, dus ging de
verandering in een nieuw contract en bleef het oude werken. De nummers impliceren niet dat v2
compleet is of dat v1 dood is; beide zijn alleen waar als de documentatie het zegt. Een v3 die
elk kwartaal verschijnt is een teken dat additieve veranderingen worden geversioneerd, of dat het
contract nooit ontworpen was om verandering op te vangen. gRPC
lost hetzelfde probleem anders op: gRPC- en Protobuf-API-wijzigingen
behandelt versionering via de packagenaam in een .proto-bestand in plaats van een URL-pad, en
een wire-formaat waar een veld hernoemen gratis is maar hernummeren een breaking change die geen
enkele REST-aanroeper als riskant zou herkennen.
Wat zou een versieverandering moeten aankondigen?
Een versieverandering zou moeten aankondigen wat breekt, wie het treft, hoe te migreren, en hoelang de vorige versie blijft werken. De entry heeft dezelfde vorm als elke andere breaking-change-entry, plus één regel met het supportvenster. Hier is er een voor een header-geversioneerde API:
API-versie 2026-11-01 is beschikbaar. Versie 2025-06-15 wordt ondersteund tot 1 november 2027. Nieuw in 2026-11-01:
GET /invoicesgeeftamountin kleinste eenheden terug als geheel getal in plaats van decimale string, en het gedeprecieerde veldcustomer_namewordt verwijderd ten gunste van hetcustomer-object. Treft aanroepers op 2025-06-15 dieamountals string parsen, wat de standaard is voor niet-vastgepinde clients aangemaakt voor juni 2025. Migratie: parseamountals geheel getal en lees de naam uitcustomer.name. PinX-Api-Version: 2026-11-01wanneer je klaar bent. Er verandert niets voor aanroepers die geen versie vastpinnen.
De laatste zin is degene die de meeste lezers laat stoppen met lezen, en hoort thuis in elke versieaankondiging. De pagina changelog-voorbeelden bevat entries van API’s die zo versioneren, en het verschil tussen de goede en de rest zit meestal in die laatste zin.
Wie wordt ingelicht wanneer een versie verandert?
Iedereen op de oude versie, individueel, en de changelog voor iedereen anders. Een versieverandering is het enige geval waarin “we hebben erover gepost” gegarandeerd precies de aanroepers mist die ertoe doen: degenen die twee jaar geleden een versie vastpinden en sindsdien geen release notes meer hebben gelezen. Gebruiksdata beantwoordt wie zij zijn; het bericht moet ze bereiken waar hun code is, in de response-headers en in een bericht aan de accounteigenaar.
In de loop die wij draaien wordt de entry die een versie aankondigt opgesteld uit de pull request die hem uitbrengt, beoordeeld door een mens, en gepubliceerd op feed en widget, waar een geversioneerde client hem als JSON kan lezen. Iedereen wiens widgetfeedback om de verandering vroeg, of de bug meldde die het oplost, en een GitHub-issue werd dat de pull request sluit, wordt ingelicht op dat issue zodra de entry live gaat. Het mechanisme is hetzelfde als voor elke entry; een versiesprong is gewoon de entry met de hoogste inzet.
FAQ
Zou elke API-verandering een nieuwe versie moeten krijgen? Nee. Alleen breaking changes. Additieve veranderingen worden uitgebracht onder de huidige versie met een changelog-entry. Additieve veranderingen versioneren traint aanroepers om versies te negeren.
Is URL-versionering beter dan header-versionering? URL-versionering is makkelijker te zien voor aanroepers en moeilijker voor jou om stukje bij beetje te laten evolueren; header-versionering is andersom. Voor een publieke API met veel kleine clients faalt URL-versionering minder vaak. Voor een grote API met vertaallaag schaalt de gedateerde header-versie beter.
Hoeveel versies zouden tegelijk ondersteund moeten worden? Zo weinig als jullie supportvenster toelaat, en nooit een onbeperkt aantal. Twee of drie parallelle versies is normaal; meer dan dat betekent meestal dat versies niet worden ingetrokken.
Wat zouden niet-geversioneerde verzoeken moeten krijgen? De oudst ondersteunde versie, zodat bestaande niet-vastgepinde clients blijven werken, met een response-header die ze vertelt welke versie ze ontvingen.
De technische beweringen in dit artikel zijn niet onafhankelijk gecontroleerd. Klopt er iets niet, laat het ons weten, dan corrigeren we het.