API-versiebeheer voor mobiele apps: endpoints veilig evolueren
API-versiebeheer voor mobiele apps uitgelegd met een eenvoudig uitrolplan, achterwaarts compatibele wijzigingen en deprecatiestappen zodat oudere appversies blijven werken.

Waarom API-wijzigingen mobiele gebruikers kapot maken
Mobiele apps updaten niet allemaal tegelijk. Zelfs als je vandaag een foutoplossing uitrolt, blijven veel mensen een oudere versie gebruiken gedurende dagen of weken. Sommigen schakelen automatische updates uit. Anderen hebben weinig opslagruimte. Weer anderen openen de app gewoon niet vaak. App store-reviewtijden en gefaseerde releases voegen nog meer vertraging toe.
Die kloof doet ertoe omdat je backend meestal sneller verandert dan je mobiele clients. Als de server een endpoint verandert en de oude app het nog steeds aanroept, kan de app kapot gaan ook al is er niets op de telefoon van de gebruiker veranderd.
Fouten verschijnen zelden als nette foutmeldingen. Meestal zien ze eruit als alledaagse productpijn:
- Inloggen of registreren mislukt na een backend-release
- Lijsten lijken leeg omdat een veld is hernoemd of verplaatst
- De app crasht als een ontbrekende waarde wordt gelezen
- Betalingen mislukken omdat validatie strenger werd
- Functies verdwijnen stilletjes omdat de response-structuur veranderde
Het doel van versioning is simpel: blijf serververbeteringen uitrollen zonder iedereen meteen te dwingen te updaten. Behandel je API als een langdurig contract. Nieuwe appversies moeten werken met nieuw servergedrag, en oudere versies moeten lang genoeg blijven werken voor echte updatecycli.
Voor de meeste consumentgerichte apps moet je verwachten dat meerdere appversies tegelijk ondersteund worden. Interne apps kunnen soms sneller bewegen, maar het is zelden instant. Plannen voor overlap houdt gefaseerde rollouts rustig in plaats van van elke backend-release een supportpiek te maken.
Wat “compatibel” betekent voor een API-contract
Een API-contract is de belofte tussen je mobiele app en je server: welke URL je aanroept, welke invoer geaccepteerd wordt, hoe de response eruitziet en wat elk veld betekent. Als de app op die belofte vertrouwt en de server verandert die, voelen gebruikers het als crashes, ontbrekende data of functies die niet meer werken.
Een wijziging is compatibel wanneer oudere appversies de API kunnen blijven gebruiken zonder codewijzigingen. In de praktijk betekent dat de server nog steeds begrijpt wat oude apps sturen en nog steeds responses terugstuurt die oude apps kunnen parsen.
Een snelle manier om veilige veranderingen van risicovolle te scheiden:
- Breaking changes: een veld verwijderen of hernoemen, een type veranderen (nummer naar string), een optioneel veld verplicht maken, het foutformaat veranderen, validatie aanscherpen op een manier die oude apps niet halen.
- Meestal veilige veranderingen: een nieuw optioneel veld toevoegen, een nieuw endpoint toevoegen, zowel oude als nieuwe requestformaten accepteren, nieuwe enumwaarden toevoegen (alleen als de app onbekende waarden als “other” behandelt).
Compatibiliteit heeft ook een end-of-life-plan nodig. Het is prima om oud gedrag uit te faseren, maar het moet gepland zijn (bijvoorbeeld: “houd v1 90 dagen aan nadat v2 is uitgebracht”) zodat je kunt evolueren zonder gebruikers te verrassen.
Veelvoorkomende aanpakken voor versioning en hun afwegingen
Versioning draait om het geven van een stabiel contract aan oudere builds terwijl je vooruit gaat. Er zijn een paar gebruikelijke benaderingen, en elke legt de complexiteit op een andere plek.
URL-versioning
De versie in het pad plaatsen (zoals /v1/ en /v2/) is het gemakkelijkst te zien en te debuggen. Het werkt ook goed met caching, logging en routing omdat de versie deel uitmaakt van de URL. Het nadeel is dat teams parallelle handlers langer kunnen blijven onderhouden dan verwacht, zelfs als het verschil klein is.
Header-based versioning
Bij header-versioning stuurt de client een versie in een header (bijvoorbeeld een Accept-header of een custom header). URL's blijven schoon en je kunt de API evolueren zonder elk pad te veranderen. Het nadeel is zichtbaarheid: proxies, logs en mensen missen de versie vaak tenzij je er zorgvuldig mee omgaat, en mobiele clients moeten de header op elk verzoek betrouwbaar zetten.
Query-parameter versioning
Query-versioning (zoals ?v=2) lijkt simpel, maar het wordt rommelig. Parameters worden gekopieerd naar bookmarks, analytics-tools en scripts, en je kunt meerdere “versies” rond zien zweven zonder duidelijke eigenaar.
Als je een simpele vergelijking wilt:
- URL-versioning: het makkelijkst te inspecteren, maar kan langlevende parallelle API's creëren
- Header-versioning: schone URL's, maar moeilijker te troubleshooten
- Query-versioning: snel te starten, makkelijk te misbruiken
Featureflags zijn een ander hulpmiddel. Ze laten je gedrag veranderen achter hetzelfde contract (bijvoorbeeld een nieuw ranking-algoritme) zonder een nieuwe API-versie te maken. Ze vervangen versioning niet wanneer request- of response-structuren moeten veranderen.
Kies één aanpak en houd je eraan. Consistentie is belangrijker dan de “perfecte” keuze.
Vuistregels voor achterwaarts compatibele wijzigingen
De veiligste mindset is: oudere clients moeten blijven werken, zelfs als ze nooit van je nieuwe feature horen. Dat betekent meestal dingen toevoegen, niet veranderen wat al bestaat.
Geef de voorkeur aan additieve wijzigingen: nieuwe velden, nieuwe endpoints, nieuwe optionele parameters. Wanneer je iets toevoegt, maak het dan echt optioneel vanuit de server. Als een oudere app het niet stuurt, moet de server exact hetzelfde gedrag vertonen als voorheen.
Een paar gewoonten voorkomen de meeste breuken:
- Voeg velden toe, maar verander het type of de betekenis van bestaande velden niet.
- Behandel ontbrekende invoer als normaal en gebruik verstandige defaults.
- Negeer onbekende requestvelden zodat oude en nieuwe clients kunnen samenleven.
- Houd foutformaten stabiel. Als je ze moet veranderen, versieer dan het error-payload.
- Als gedrag moet veranderen, introduceer een nieuw endpoint of versie in plaats van een “stille” aanpassing.
Vermijd het veranderen van de betekenis van een bestaand veld zonder een versiebump. Bijvoorbeeld: als status=1 eerder “betaald” betekende en je gebruikt het voortaan voor “geautoriseerd”, maken oudere apps verkeerde beslissingen en merk je het misschien pas als gebruikers klagen.
Hernoemingen en verwijderingen hebben een plan nodig. Het veiligste patroon is het oude veld te behouden en het nieuwe ernaast toe te voegen voor een tijdje. Vul beide in responses, accepteer beide in requests en log wie het oude veld nog gebruikt. Verwijder het oude veld pas als de deprecatieperiode is verstreken.
Een kleine maar krachtige gewoonte: wanneer je een nieuwe verplichte bedrijfsregel introduceert, maak dan niet meteen de client verantwoordelijk. Leg de regel eerst op de server met een default, en eis later dat de client de nieuwe waarde meezendt zodra de meeste gebruikers zijn geüpdatet.
Stel een eenvoudig versioning- en deprecatiebeleid op
Versioning werkt het best als de regels saai en opgeschreven zijn. Houd het beleid kort genoeg zodat product-, mobile- en backendteams het daadwerkelijk volgen.
Begin met supportwindows. Bepaal hoe lang je oudere API-versies draait nadat een nieuwe versie live gaat (bijvoorbeeld 6–12 maanden), plus uitzonderingen (security-issues, wettelijke wijzigingen).
Definieer vervolgens hoe je clients waarschuwt voordat je ze breekt. Kies één deprecatie-signaal en gebruik het overal. Veelvoorkomende opties zijn een response-header zoals Deprecation: true met een retire-datum, of een JSON-veld zoals "deprecation": {"will_stop_working_on": "2026-04-01"} in geselecteerde responses. Wat telt is consistentie: clients kunnen het detecteren, dashboards kunnen het rapporteren en supportteams kunnen het uitleggen.
Stel een minimum ondersteunde appversie vast en wees expliciet over handhaving. Vermijd onverwachte harde blokkades. Een praktische aanpak is:
- Geef een zachte waarschuwing terug (bijvoorbeeld een veld dat in-app een updateprompt triggert).
- Handhaaf pas na een gecommuniceerde deadline.
Als je verzoeken blokkeert, geef dan een duidelijk error-payload terug met een menselijke boodschap en een machineleesbare code.
Tot slot, bepaal wie breaking changes kan goedkeuren en welke documentatie vereist is. Houd het simpel:
- Eén eigenaar keurt breaking changes goed.
- Een korte wijzigingsnotitie legt uit wat veranderde, wie erdoor wordt beïnvloed en het migratiepad.
- Een testplan bevat ten minste één oudere appversie.
- Een pensioendatum wordt gezet wanneer deprecatie start.
Stapsgewijs uitrolplan dat oude apps laat werken
Mobiele gebruikers updaten niet op dag één. De veiligste aanpak is een nieuwe API uitrollen terwijl je de oude onaangeroerd laat, en daarna het verkeer geleidelijk overzetten.
Bepaal eerst wat v2 verandert en sluit v1-gedrag vast. Behandel v1 als een belofte: dezelfde velden, dezelfde betekenissen, dezelfde foutcodes. Als v2 een andere response-structuur nodig heeft, pas v1 dan niet aan om v2 te laten passen.
Draai daarna v2 parallel. Dat kan betekenen aparte routes (zoals /v1/... en /v2/...) of aparte handlers achter dezelfde gateway. Houd gedeelde logica op één plek, maar houd het contract gescheiden zodat een v2-refactor v1 niet per ongeluk verandert.
Update vervolgens de mobiele app zodat die v2 prefereert. Bouw een eenvoudige fallback: als v2 “not supported” teruggeeft (of een andere bekende fout), probeer v1 opnieuw. Dit helpt bij gefaseerde releases en wanneer netwerken in de praktijk rommelig zijn.
Na het releasen van de app, monitor adoptie en fouten. Nuttige controles zijn onder meer:
- v1 versus v2 requestvolume per appversie
- foutpercentage en latency voor v2
- response parsing failures
- crashes gerelateerd aan netwerkschermen
Als v2 stabiel is, voeg duidelijke deprecatie-waarschuwingen toe voor v1 en communiceer een tijdlijn. Zet v1 pas uit wanneer het gebruik onder een drempel daalt die acceptabel is (bijvoorbeeld onder 1–2% voor meerdere weken).
Voorbeeld: je verandert GET /orders om filtering en nieuwe statussen te ondersteunen. v2 voegt status_details toe terwijl v1 hetzelfde blijft. De nieuwe app roept v2 aan, maar als er een edgecase optreedt valt hij terug op v1 en toont nog steeds de bestellijst.
Implementatietips aan de serverkant
De meeste rollout-breuken ontstaan omdat versiehandhaving verspreid staat over controllers, helpers en databasecode. Houd de beslissing “welke versie is dit verzoek?” op één plek en maak de rest van de logica voorspelbaar.
Zet version-routing achter één poortwachter
Kies één signaal (URL-segment, header of app buildnummer) en normaliseer het vroeg. Routeer naar de juiste handler in één module of middleware zodat elk verzoek hetzelfde pad volgt.
Een praktisch patroon:
- Parse de versie één keer (en log het).
- Map versie naar een handler (v1, v2, ...) in één registry.
- Houd gedeelde utilities version-agnostisch (datumparsers, auth-checks), niet response-shape-logic.
Wees voorzichtig bij het delen van code tussen versies. Het fixen van een v2-bug in “gedeelde” code kan per ongeluk v1-gedrag veranderen. Als logica outputvelden of validatieregels beïnvloedt, hou het dan versiegebonden of dek het af met versie-specifieke tests.
Houd dataveranderingen compatibel tijdens de rollout
Database-migraties moeten werken voor beide versies tegelijk. Voeg eerst kolommen toe, backfill indien nodig en verwijder of verscherp constraints pas later. Vermijd hernoemen of betekeniswijzigingen midden in een rollout. Als je formaten moet wisselen, overweeg dan beide formaten tijdelijk te ondersteunen totdat de meeste mobiele clients overgestapt zijn.
Maak fouten voorspelbaar. Oudere apps behandelen onbekende fouten vaak als “er is iets mis”. Gebruik consistente statuscodes, stabiele foutidentifiers en korte berichten die de client helpen beslissen wat te doen (retry, re-auth, update prompt tonen).
Tot slot: bescherm tegen ontbrekende velden die oudere apps niet versturen. Gebruik veilige defaults en valideer met duidelijke, stabiele foutdetails.
Overwegingen aan de mobiele kant die versioning beïnvloeden
Omdat gebruikers weken op een oude build kunnen blijven, moet versioning aannemen dat meerdere clientversies tegelijk je servers bereiken.
Een grote winst is tolerantie aan de clientzijde. Als de app crasht of faalt bij parsing wanneer de server een veld toevoegt, voel je dat als “willekeurige” rollout-bugs.
- Negeer onbekende JSON-velden.
- Behandel ontbrekende velden als normaal en gebruik defaults.
- Ga veilig om met nulls (velden kunnen tijdens migraties nullable worden).
- Vertrouw niet op array-volgorde tenzij het contract dat garandeert.
- Houd foutafhandeling gebruiksvriendelijk (een retry-state is beter dan een leeg scherm).
Netwerkgedrag doet er ook toe. Tijdens een rollout kun je tijdelijk verschillende serverversies achter load balancers of caches hebben, en mobiele netwerken versterken kleine problemen.
Kies duidelijke timeout- en retryregels: korte timeouts voor leescalls, iets langere voor uploads, en beperkte retries met backoff. Maak idempotentie standaard voor create- of payment-achtige calls zodat een retry niet dubbel indient.
Auth-wijzigingen zijn de snelste manier om oudere apps uit te sluiten. Als je tokenformaat, vereiste scopes of sessieregels verandert, houd dan een overlap-window waarin zowel oude als nieuwe tokens werken. Als je sleutels of claims moet roteren, plan dan een gefaseerde migratie in plaats van een cutover op één dag.
Stuur app-metadata mee met elk verzoek (bijvoorbeeld appversie en platform). Dat maakt het makkelijker om gerichte waarschuwingen terug te sturen zonder de hele API te forken.
Monitoren en gefaseerde rollouts zonder verrassingen
Een gefaseerde rollout werkt alleen als je kunt zien wat verschillende appversies doen. Het doel is simpel: weet wie nog op oudere endpoints zit en vang problemen voordat ze iedereen raken.
Begin met gebruik bijhouden per API-versie elke dag. Tel niet alleen totale requests. Houd actieve apparaten bij en breek key endpoints uit zoals login, profiel en betalingen. Dit vertelt je of een oude versie nog “leeft” ook als het totale verkeer klein lijkt.
Kijk vervolgens naar fouten uitgesplitst per versie en type. Een stijging in 4xx betekent vaak een contractmismatch (verplicht veld veranderde, enum-waarden versprongen, auth-regels aangescherpt). Een stijging in 5xx wijst vaak op serverregressies (slechte deploy, trage queries, dependency-fouten). Beide per versie zien helpt je snel de juiste oplossing te kiezen.
Gebruik staged rollouts in appstores om het blast radius te beperken. Vergroot exposure stapsgewijs en kijk na elke stap naar dezelfde dashboards (bijvoorbeeld na 5%, 25%, 50%). Als de nieuwste versie problemen toont, stop de rollout voordat het een volledige outage wordt.
Heb rollback-triggers op papier voordat er een incident is, niet onderweg. Veelvoorkomende triggers zijn:
- foutpercentage boven een vaste drempel gedurende 15–30 minuten
- inlogsuccepercent daalt (of token refresh-fouten nemen toe)
- betalingen falen vaker (of checkout-timeouts nemen toe)
- een piek in supporttickets gekoppeld aan een specifieke versie
- latency stijgt op een kritisch endpoint
Houd een kort incident-playbook voor versie-gerelateerde outages: wie te pagineren, hoe een risicovolle flag uit te schakelen, welke serverrelease terug te zetten en hoe de deprecatieperiode te verlengen als oudere clients nog actief zijn.
Voorbeeld: een endpoint evolueren tijdens een echte release
Checkout is een klassieker. Je begint met een eenvoudige flow, voegt daarna een nieuwe betaalstap toe (zoals sterkere authenticatie) en hernoemt velden om ze beter bij de business te laten passen.
Stel dat je mobiele app POST /checkout aanroept.
Wat blijft in v1 vs wat verandert in v2
In v1 behoud je het bestaande request en gedrag zodat oudere appversies betalingen kunnen afronden zonder verrassingen. In v2 introduceer je de nieuwe flow en schonere namen.
- v1 behoudt:
amount,currency,card_token, en een enkele response zoalsstatus=paid|failed. - v2 voegt toe:
payment_method_id(ter vervanging vancard_token) en eennext_action-veld zodat de app een extra stap kan afhandelen (verify, retry, redirect). - v2 hernoemt:
amountnaartotal_amountencurrencynaarbilling_currency.
Oudere apps blijven werken omdat de server veilige defaults toepast. Als een v1-request niets weet van next_action, voltooit de server de betaling waar mogelijk en retourneert hetzelfde v1-stijl resultaat. Als de nieuwe stap verplicht is, krijgt v1 een duidelijke, stabiele foutcode zoals requires_update in plaats van een verwarrende generieke fout.
Adoptie, pensioen en rollback
Houd adoptie bij per versie: welk aandeel van checkout-calls naar v2 gaat, foutpercentages en hoeveel gebruikers nog builds draaien die alleen v1 ondersteunen. Als v2-consumptie consequent hoog is (bijvoorbeeld 95%+ voor meerdere weken) en v1-gebruik laag is, kies dan een v1-pensioendatum en communiceer die (release-opmerkingen, in-app messaging).
Als er iets misgaat na de launch, moet rollback saai zijn:
- Routeer meer verkeer terug naar v1-gedrag.
- Schakel de nieuwe betaalstap uit met een server-side flag.
- Blijf beide veldsets accepteren en log wat je automatisch hebt geconverteerd.
Veelgemaakte fouten die stille breuk veroorzaken
De meeste mobiele API-fouten zijn niet luid. Het verzoek slaagt, de app blijft draaien, maar gebruikers zien ontbrekende data, verkeerde totalen of knoppen die niets doen. Deze issues zijn moeilijk te ontdekken omdat ze vaak oudere appversies tijdens een gefaseerde rollout treffen.
Veelvoorkomende oorzaken:
- Velden veranderen of verwijderen (of hun type veranderen) zonder een duidelijk versieplan.
- Een nieuw requestveld direct verplicht maken, zodat oudere apps worden afgewezen.
- Een database-migratie uitrollen die ervan uitgaat dat alleen de nieuwe app bestaat.
- v1 buiten gebruik stellen op basis van installs, niet actieve usage.
- Achtergrondjobs en webhooks vergeten die nog oude payloads aanroepen.
Een concreet voorbeeld: je responseveld total was een string ("12.50") en je verandert het naar een nummer (12.5). Nieuwe apps zien er goed uit. Oudere apps behandelen het misschien als nul, verbergen het of crashen alleen op bepaalde schermen. Tenzij je clientfouten per appversie bewaakt, kan het ongemerkt blijven.
Snelle checklist en volgende stappen
Versioning gaat minder over slimme endpoint-namen en meer over het elke release herhalen van dezelfde veiligheidscontroles.
Snelle checks vóór release
- Houd wijzigingen additief. Verwijder of hernoem geen velden die oudere apps al lezen.
- Zorg voor veilige defaults zodat ontbrekende nieuwe velden hetzelfde gedrag geven als de oude flow.
- Houd foutresponses stabiel (status + vorm + betekenis).
- Wees voorzichtig met enums en verander niet wat een bestaande waarde betekent.
- Replay een paar echte requests van oudere appversies en bevestig dat responses nog steeds parsen.
Snelle checks tijdens rollout en vóór pensioen
- Volg adoptie per appversie. Je wilt een duidelijke curve van v1 naar v2, geen platte lijn.
- Houd foutpercentages per versie in de gaten. Een piek betekent vaak dat parsing of validatie oudere clients brak.
- Los de top-failing endpoint eerst op, en vergroot dan de rollout.
- Zet pas buiten gebruik wanneer actief gebruik echt laag is en communiceer de datum.
- Verwijder fallback-code als laatste, na de pensioentermijn.
Schrijf je versioning- en deprecatiebeleid op één pagina en zet de checklist om in een releasegate die je team elke keer volgt.
Als je interne tools of klantgerichte apps bouwt met een no-code platform, helpt het nog steeds om de API als contract met een duidelijke deprecatieperiode te behandelen. Voor teams die AppMaster (appmaster.io) gebruiken, is het vaak makkelijker om v1 en v2 naast elkaar te houden omdat je backend en clientapps kunt regenereren naarmate eisen veranderen en toch oudere contracten tijdens de rollout kunt laten draaien.
FAQ
Mobiele gebruikers updaten niet allemaal tegelijk, dus oudere appbuilds blijven je backend aanroepen nadat je wijzigingen hebt uitgerold. Als je een endpoint, validatie of responsschema verandert, kunnen die oudere builds niet meekomen en falen ze op manieren die eruitzien als lege schermen, crashes of mislukte betalingen.
“Compatibel” betekent dat een oudere app dezelfde verzoeken kan blijven doen en nog steeds antwoorden krijgt die hij correct kan parsen en gebruiken, zonder codewijzigingen. Het handigste denkmodel is dat je API een contract is: je mag nieuwe mogelijkheden toevoegen, maar verander niet de betekenis van bestaande velden en gedrag voor huidige clients.
Een wijziging is breaking wanneer iets verandert waarop een bestaande app vertrouwt, zoals het verwijderen of hernoemen van velden, het veranderen van een veldtype, het aanscherpen van validatie zodat oude verzoeken falen, of het veranderen van het foutpayload-formaat. Als een oudere app het antwoord niet kan parsen of niet kan voldoen aan de verzoekregels, is het breaking, ook al werkt je server technisch gezien.
URL-versioning is meestal de makkelijkste standaard omdat de versie zichtbaar is in logs, debugging-tools en routing — je vergeet het minder snel. Header-based versioning werkt ook, maar is makkelijker te missen bij troubleshooting en vereist dat elke clientrequest de header correct zet.
Kies een duidelijk supportwindow dat aansluit bij echt mobiel updategedrag en houd je eraan; veel teams kiezen maanden in plaats van dagen. Het belangrijkste is een gepubliceerde pensioendatum en het meten van actieve usage, zodat je niet moet gokken wanneer het veilig is om een oudere versie uit te schakelen.
Gebruik één consistente deprecatie-indicator zodat clients en dashboards die betrouwbaar kunnen detecteren, bijvoorbeeld een stabiele response-header of een klein JSON-veld dat een pensioendatum bevat. Houd het simpel en voorspelbaar zodat support en productteams het zonder ingewikkelde uitleg kunnen communiceren.
Geef de voorkeur aan additieve wijzigingen: voeg nieuwe optionele velden of nieuwe endpoints toe en houd oude velden werkend met dezelfde betekenis. Als je een hernoeming nodig hebt, draai beide velden parallel voor een tijdje en vul beide in zodat oudere apps geen data verliezen terwijl nieuwere apps overstappen.
Ontwerp migraties zodat beide API-versies tegelijk kunnen werken: voeg eerst kolommen toe, backfill indien nodig, en verscherp later pas constraints of verwijder oude velden. Vermijd hernoemen of betekenisveranderingen midden in een rollout, want dan schrijft de ene app mogelijk data die de andere niet kan lezen.
Maak de app tolerant: negeer onbekende JSON-velden, behandel ontbrekende velden als normaal met veilige defaults en ga netjes om met null-waarden. Dit vermindert “random” rollout-bugs wanneer de server velden toevoegt of responses tijdelijk variëren tijdens gefaseerde deploys.
Houd gebruik en fouten bij per API-versie en appversie, vooral voor login en betalingen, en vergroot staged rollouts alleen als de data stabiel is. Een veilig rolloutplan houdt v1-gedrag ongewijzigd, draait v2 parallel en verplaatst clients geleidelijk met een duidelijke fallbackstrategie totdat adoptie hoog genoeg is om v1 met vertrouwen buiten gebruik te stellen.


