Een 429 Too Many Requests is geen technische storing die je kunt wegmasseren met nog een retry. Het is een signaal dat capaciteit schaars is. Daarmee ontstaat een bedrijfsbesluit: welke order mag wachten, welke factuur mag niet doorgaan en wie verklaart later wat er gebeurde?
Mijn stelling is scherp: een API-limiet wordt een bedrijfsregel zodra de koppeling geld, voorraad, facturen of klantbeloftes raakt. Backoff en een queue houden verkeer beheersbaar, maar alleen eigenaarschap, stopcriteria en bewijs maken herstel betrouwbaar. De technische route staat in backoff, batching en herstelbare orderflows. De vraag eronder is belangrijker: wat mag je bedrijf nog doen wanneer de andere kant even geen ruimte heeft?
API-limieten zijn bedrijfsregels omdat ze bepalen welke bedrijfsacties op tijd kunnen doorgaan. Een queue verdeelt schaarse capaciteit, een stopregel beschermt geld en klantbeloftes, en een verwerkingsbewijs maakt herstel controleerbaar. Zonder die drie lagen is backoff alleen uitgestelde onzekerheid: de koppeling wacht wel, maar niemand weet welke order of factuur voorrang heeft en wanneer verdergaan verantwoord is.
Een limiet is een allocatieregel
De HTTP-standaard definieert 429 als te veel verzoeken binnen een bepaalde tijd. Dat zegt iets over het verkeer, niet over jouw bedrijf. De standaard schrijft niet voor hoe de server telt. Een limiet kan per gebruiker, resource, endpoint of hele server gelden. Retry-After kan aangeven hoelang je moet wachten, maar vertelt niet of een order, voorraadmutatie of factuur daarna automatisch mag worden vrijgegeven.
Dat verschil verdwijnt snel in een generieke retry-handler. Die ziet een foutcode en zet het verzoek achteraan. Voor een rapportage-import is dat vaak prima. Voor een betaalde order kan dezelfde keuze betekenen dat de fulfilment te laat start. Voor een factuur kan het betekenen dat de order wel zichtbaar is, maar de financiële registratie nog niet bestaat. De fout zit dan niet in het wachten. De fout zit in het doen alsof iedere wachtende handeling dezelfde waarde heeft.
De providerregels maken dat onderscheid concreet. Shopify documenteert verschillende limietmechanismen per API, met buckets die per API en abonnement een andere grootte en herstelsnelheid hebben. De GraphQL Admin API werkt bijvoorbeeld met querykosten. De actuele tabel noemt 100 punten per seconde voor Standard, 200 voor Advanced, 1.000 voor Shopify Plus en 2.000 voor Commerce Components. Een eenvoudige orderquery en een zware bulkquery gebruiken dus niet dezelfde hoeveelheid schaarse ruimte.
Dat getal is geen capaciteit voor je bedrijf. Het is capaciteit voor één combinatie van app en winkel, onder voorwaarden van de leverancier. Mijn vertaling naar de praktijk is daarom: reserveer de schaarse ruimte voor de acties met de hoogste bedrijfswaarde. Een actuele orderstatus krijgt voorrang op een historische backfill. Een voorraadcorrectie die een klantbelofte raakt krijgt voorrang op een analyse-export. Een betaal- of factuuractie krijgt een andere behandeling dan een leesactie die morgen ook nog kan.
Een queue is daarmee geen technische wachtrij meer. Het is een allocatieregel die je expliciet moet kunnen uitleggen. Als dat niet kan, verdeelt de volgorde van binnenkomst stilletjes je omzet, voorraad en klantbelofte.
Een 429 zegt niet wat je moet stoppen
De gevaarlijkste aanname is dat dezelfde HTTP-status voor iedere route dezelfde reactie verdient. Dat klopt al niet binnen één provider. Stripe onderscheidt in zijn actuele documentatie rate limits van concurrency limits. De live API heeft standaard ruimte voor 50 gelijktijdige verzoeken en geeft met een header aan of de limiet globaal, endpointgebonden, resourcegebonden of een concurrencyprobleem is. Eén 429 kan dus betekenen dat je minder snel moet sturen. Een andere 429 kan betekenen dat je minder verzoeken tegelijk moet uitvoeren. Dat vraagt om een andere ingreep.
Ook lezen en schrijven hebben niet dezelfde risico’s. Microsoft Graph beschrijft dat writes kunnen worden afgeremd terwijl reads nog zijn toegestaan. In een JSON-batch kan de buitenste aanvraag 200 teruggeven terwijl afzonderlijke onderdelen 429 bevatten. Wie alleen naar de buitenste status kijkt, kan een batch als geslaagd registreren terwijl de order- of factuurmutatie is blijven liggen.
Daarom hoort in mijn ontwerp niet de vraag “hoe vaak retry ik?”, maar “welke bedrijfsactie probeer ik opnieuw?”. Een leesactie kan vaak veilig later opnieuw. Een write die een order aanmaakt, voorraad verlaagt of een factuur boekt heeft minstens een zakelijke sleutel, een bekende status en een grens waarna nieuwe neveneffecten stoppen. Dezelfde limiet kan dus een import vertragen, een voorraadpublicatie blokkeren of een factuurstroom naar controle sturen.
Dat is geen overdreven governance. Het is een eenvoudige erkenning dat capaciteit niet neutraal is. Als de provider nog wel kan lezen, gebruik ik die ruimte om de toestand te onderzoeken. Ik gebruik haar niet automatisch om een onzekere write opnieuw uit te voeren.
Wachten is geen herstelbesluit
Backoff is waardevol. Het voorkomt dat een volle bucket nog voller wordt en geeft de provider ruimte om te herstellen. De fout ontstaat wanneer backoff als eindpunt wordt behandeld. Een item dat twaalf uur wacht, is niet meer hetzelfde bedrijfsprobleem als een item dat twaalf seconden wacht. De klantbelofte, de voorraadpositie, de factuurperiode of het betaalbewijs kan ondertussen zijn veranderd.
Ik wil daarom per stroom een maximale leeftijd van onzekerheid zien. Niet als universele technische norm, maar als bedrijfsgrens. Een order die nog niet naar het magazijn is bevestigd, kan misschien dertig minuten wachten. Een voorraadstand die een verkoopkanaal voedt, kan na een kortere periode verouderd zijn. Een factuur die niet aantoonbaar aan de juiste order en betaling hangt, hoort niet vanzelf door naar boeking omdat de retry-teller nog niet is uitgeput.
Bij geldstromen is dat onderscheid extra hard. Een retry moet dezelfde zakelijke handeling herkennen voordat hij een tweede boeking kan maken. De Nederlandse overheidsrichtlijn over idempotency beschrijft bovendien dat de zakelijke operatie en het opslaan van de sleutel met de response in één atomaire transactie horen. Zonder die transactionele koppeling kan een race de operatie alsnog twee keer uitvoeren.
Dat maakt de stopregel concreet. Als een externe write na een timeout onzeker is, maak ik geen nieuwe sleutel omdat de wachtrij toevallig opnieuw draait. Ik zoek eerst het doelobject, het bestaande verwerkingsrecord en de bronreferentie. Als de actie aantoonbaar niet is uitgevoerd, hergebruik ik dezelfde zakelijke sleutel. Als ik de uitkomst niet kan bewijzen, zet ik de handeling stil met een eigenaar en een uiterste beslisdatum.
Een order mag dus best als ontvangen worden geregistreerd zonder dat hij al naar een ERP of magazijn is geschreven. Een factuur mag best wachten op de bevestiging van de order en betaling. Wat niet mag, is een groene status geven aan een keten waarvan alleen de eerste stap zeker is.
Het bewijs begint vóór de retry
Een retry-log achteraf is geen herstelbewijs. Tegen de tijd dat iemand de log opent, kan de provider de sleutel hebben vergeten, kan een tweede worker dezelfde actie hebben opgepakt of kan de bron al een nieuwe versie bevatten. De verwerkingsinformatie moet daarom bestaan voordat de externe write vertrekt.
Ik wil per kritieke handeling minimaal kunnen terugvinden: de brongebeurtenis, de zakelijke sleutel, de gewenste doelactie, de providerlimiet of foutreden, het aantal pogingen, het doel-ID, de laatste bekende response, de volgende toegestane actie en de persoon of rol die een uitzondering mag sluiten. Dat is geen nieuw boekhoudpakket. Het is een klein geheugen voor beslissingen die je anders uit losse logs, dashboards en mailboxen moet reconstrueren.
Ook een succesvolle response is niet automatisch bewijs dat de juiste werkelijkheid is gepubliceerd. Een rate limit kan zijn opgelost terwijl een oudere voorraadlezing klaarstaat om te worden geschreven. Bronversie, voorwaardelijke write en read-back horen samen wanneer oude data een nieuwe waarde kan overschrijven. De limiet zegt iets over de toegang tot de API. Ze zegt niets over de actualiteit van de payload.
Dat wordt zichtbaar bij een API-upgrade. Tijdens een gecontroleerde overgang waarin orders, betalingen, voorraad en fulfilment eerst naast elkaar worden gereconcilieerd is een geslaagde request slechts één bewijsstuk. De echte vrijgave volgt pas wanneer de bedrijfsuitkomst klopt en iemand met mandaat de uitzonderingen heeft beoordeeld.
De eigenaar van die beoordeling is niet automatisch de ontwikkelaar. Finance kan eigenaar zijn van een onzekere factuur of betaling. Operations kan beslissen over een order die niet naar fulfilment mag. Een technische eigenaar kan de queue en de providerrespons verklaren. De naam of rol hoort in het verwerkingsrecord te staan. Een status als failed zonder eigenaar is geen proces, maar een uitgestelde vraag.
Om eerlijk te zijn: een eigen beleid kan te zwaar worden
Het tegenargument is sterk. Niet iedere integratie verdient een eigen verwerkingsregister, prioriteitenmatrix of menselijke vrijgave. Een periodieke import van openbare productinformatie kan veilig wachten. Een provider-SDK kan retries, jitter en Retry-After al correct afhandelen. Wie voor ieder klein proces dezelfde controlelaag bouwt, maakt software duurder en medewerkers trager.
Daar ben ik het mee eens. Ik zou geen besluitkamer bouwen voor iedere GET en geen handmatige stap zetten op een effect dat veilig opnieuw kan. De grens ligt waar een retry geld, voorraad, omzet, een factuur, een leverbelofte of een externe toezegging kan veranderen. Vanaf daar is technische correctheid niet genoeg.
De betere keuze is dus geen zware controle voor alles. Het is een duidelijke risicogrens. Laag risico krijgt automatisch herstel binnen een leeftijdsgrens. Hoog risico krijgt een duurzame sleutel, bewijs van de uitkomst en een eigenaar voor de uitzondering. De techniek blijft zo licht waar dat kan, terwijl de bedrijfsregel zichtbaar wordt waar dat moet.
Mijn grens ligt bij de bedrijfsuitkomst
Ik behandel API-capaciteit als een beperkte voorraad. Elke stroom krijgt een prioriteit, een maximale wachttijd en een antwoord op de vraag wat er mag stoppen. De queue mag een volgorde uitvoeren, maar niet zelf bepalen wat financieel of operationeel belangrijk is.
Ik laat een order of factuur niet verdergaan omdat een worker opnieuw heeft geprobeerd. Ik laat hem verdergaan wanneer de zakelijke sleutel, het doelresultaat en de bron bij elkaar passen. Bij een onbekende write zoek ik bewijs. Bij een verouderde bron start ik geen blinde replay. Bij een overschreden leeftijd gaat de handeling naar de rol die de gevolgen kan dragen.
Dat is de reden dat ik rate limits bedrijfsregels noem. De provider levert een technisch plafond. Mijn systeem moet daar een bedrijfsgrens van maken: deze actie wacht, deze actie stopt, deze actie mag met dezelfde sleutel opnieuw, en voor deze actie moet iemand beslissen.
Een koppeling is niet betrouwbaar omdat hij onder normale belasting snel antwoordt. Hij is betrouwbaar wanneer capaciteit verdwijnt en je nog steeds kunt aanwijzen welke werkelijkheid veilig is aangeraakt, welke belofte bewust is uitgesteld en wie het volgende herstelbesluit neemt.
Veelgestelde vragen
Maak je koppeling bestuurbaar
Ik denk mee over de bedrijfsregels achter je API-koppelingen, ontwerp de juiste bewijs- en herstelstroom en realiseer die van eerste idee tot livegang. Van prioriteit en stopcriterium tot een live integratie die ook onder druk uitlegbaar blijft.
Dit artikel is geproduceerd samen met het Agent Team. Meer over de redactie.
