Pattern di rate limiting per API SaaS per limiti per utente, per organizzazione e per IP, con intestazioni chiare, corpi di errore e suggerimenti di rollout comprensibili ai clienti.

I rate limit e le quote possono sembrare la stessa cosa, quindi spesso vengono trattati come tali. Un rate limit indica quanto velocemente puoi chiamare un'API (richieste al secondo o al minuto). Una quota indica quanto puoi usare in un periodo più lungo (giornaliero, mensile o per ciclo di fatturazione). Entrambi sono normali, ma sembrano casuali quando le regole non sono visibili.
La lamentela classica è: “funzionava ieri”. L'uso raramente è costante. Un breve picco può far superare la soglia anche se il totale giornaliero sembra a posto. Immagina un cliente che esegue un report una volta al giorno, ma oggi il job ritenta dopo un timeout e genera 10× chiamate in 2 minuti. L'API lo blocca, e tutto ciò che vede è un errore improvviso.
La confusione peggiora quando gli errori sono vaghi. Se l'API ritorna 500 o un messaggio generico, i clienti pensano che il servizio sia giù, non che abbiano raggiunto un limite. Aprono ticket urgenti, costruiscono workaround o cambiano fornitore. Anche il 429 Too Many Requests può essere frustrante se non indica cosa fare dopo.
La maggior parte delle API SaaS limita il traffico per due motivi diversi:
Mescolare questi obiettivi porta a cattivi design. I controlli anti-abuso spesso sono per-IP o per-token e possono essere severi. Il shaping dell'uso normale è di solito per-utente o per-organizzazione e dovrebbe venire con indicazioni chiare: quale limite è stato superato, quando si resetta e come evitarlo.
Quando i clienti possono prevedere i limiti, pianificano di conseguenza. Quando non possono, ogni picco sembra un'API guasta.
I rate limit non sono solo uno strozzatore. Sono un sistema di sicurezza. Prima di scegliere i numeri, sii chiaro su cosa stai cercando di proteggere, perché ogni obiettivo porta a limiti diversi e aspettative diverse.
La disponibilità è spesso la priorità. Se pochi client possono generare picchi e mandare la tua API in timeout, ne soffrono tutti. I limiti qui dovrebbero mantenere i server reattivi durante i burst e far fallire velocemente invece di lasciare che le richieste si accumulino.
Il costo è il motore silenzioso dietro molte API. Alcune richieste sono economiche, altre costose (chiamate a LLM, elaborazione di file, scritture di storage, lookup a terze parti a pagamento). Per esempio, su una piattaforma come Koder.ai, un singolo utente può scatenare molte chiamate ai modelli tramite generazione di app basata su chat. Limiti che tracciano azioni costose possono prevenire bollette a sorpresa.
L'abuso appare diversamente dall'uso legittimo elevato. Credential stuffing, guess di token e scraping spesso si manifestano come molte piccole richieste da un ristretto insieme di IP o account. Qui vuoi limiti severi e blocchi rapidi.
La correttezza è importante nei sistemi multi-tenant. Un cliente rumoroso non dovrebbe degradare tutti gli altri. In pratica, questo significa spesso stratificare i controlli: una guardia per i burst per mantenere l'API sana minuto per minuto, una guardia sul costo per endpoint o azioni costose, una guardia anti-abuso focalizzata su auth e pattern sospetti, e una guardia di equità in modo che un'organizzazione non possa escludere le altre.
Un test semplice aiuta: scegli un endpoint e chiediti, “Se questa richiesta cresce di 10×, cosa si rompe prima?” La risposta ti dice quale obiettivo di protezione prioritizzare e quale dimensione (utente, org, IP) dovrebbe portare il limite.
La maggior parte dei team parte con un limite e poi scopre che danneggia le persone sbagliate. L'obiettivo è scegliere dimensioni che rispecchino l'uso reale: chi chiama, chi paga e cosa sembra abuso.
Dimensioni comuni nelle SaaS sono:
I limiti per utente riguardano l'equità all'interno di un tenant. Se una persona esegue una grande esportazione, dovrebbe subire il rallentamento più del resto del team.
I limiti per org riguardano budget e capacità. Anche se dieci utenti eseguono job contemporaneamente, l'org non dovrebbe generare picchi che rompano il tuo servizio o le tue assunzioni di prezzo.
I limiti per IP vanno trattati come rete di sicurezza, non come strumento di fatturazione. Gli IP possono essere condivisi (NAT d'ufficio, operatori mobili), quindi mantieni questi limiti generosi e usali principalmente per fermare abusi evidenti.
Quando combini dimensioni, decidi quale “vince” quando si applicano più limiti. Una regola pratica: rifiuta la richiesta se qualsiasi limite rilevante è superato e restituisci la ragione più azionabile. Se uno workspace ha superato la quota org, non dare la colpa all'utente o all'IP.
Esempio: un workspace Koder.ai su un piano Pro potrebbe permettere un flusso costante di build per org, limitando anche un singolo utente dal mandare centinaia di richieste in un minuto. Se un'integrazione partner usa un token condiviso, un limite per-token può impedirle di soffocare gli utenti interattivi.
La maggior parte dei problemi di rate limiting non riguarda la matematica. Riguarda la scelta di un comportamento che corrisponda a come i clienti chiamano la tua API, e poi mantenerlo prevedibile sotto carico.
Token bucket è un default comune perché permette brevi burst pur facendo rispettare una media costante a lungo termine. Un utente che aggiorna una dashboard può scatenare 10 richieste veloci. Token bucket lo consente se ha accumulato token, poi lo rallenta.
Leaky bucket è più severo. Livella il traffico in un flusso costante, utile quando il backend non può gestire i picchi (per esempio, generazione di report costosi). Il compromesso è che i clienti lo sentono prima, perché i burst diventano accodamento o rifiuto.
I contatori a finestra sono semplici, ma i dettagli contano. Le finestre fisse creano spigoli netti al confine (un utente può burstare a 12:00:59 e di nuovo a 12:01:00). Le finestre scorrevoli sembrano più eque e riducono gli spike al confine, ma richiedono più stato o strutture dati migliori.
Una classe separata di limiti è la concorrenza (richieste in volo). Questo ti protegge dalle connessioni lente dei client e dagli endpoint long-running. Un cliente può restare entro 60 richieste al minuto ma comunque sovraccaricarti mantenendo 200 richieste aperte contemporaneamente.
Nei sistemi reali, i team spesso combinano pochi controlli: un token bucket per il rate generale, un tetto di concorrenza per endpoint lenti o pesanti, e budget separati per gruppi di endpoint (letture economiche vs esportazioni costose). Se limiti solo per conteggio di richieste, un endpoint costoso può soffocare tutto il resto e far sembrare l'API casualmente rotta.
Le quote buone sembrano eque e prevedibili. I clienti non dovrebbero scoprire le regole solo dopo essere stati bloccati.
Mantieni la separazione chiara:
Molti team SaaS usano entrambi: un limite a breve per fermare i burst e una quota mensile legata al pricing.
Hard vs soft limit è principalmente una scelta di supporto. Un limite hard blocca immediatamente. Un limite soft avvisa prima, poi blocca più tardi. I limiti soft riducono i ticket arrabbiati perché le persone hanno la possibilità di correggere un bug o fare upgrade prima che un'integrazione si rompa.
Quando qualcuno sfora, il comportamento dovrebbe rispecchiare cosa stai proteggendo. Il blocco funziona quando l'uso eccessivo può danneggiare altri tenant o esplodere i costi. Degradare (processamento più lento o priorità inferiore) funziona quando preferisci mantenere le cose in movimento. “Fattura dopo” può funzionare quando l'uso è prevedibile e hai già un flusso di fatturazione.
I limiti basati sui tier funzionano meglio quando ogni tier ha una “forma d'uso attesa” chiara. Un piano gratuito potrebbe permettere piccole quote mensili e bassi burst rate, mentre i piani business e enterprise ottengono quote e burst più alti così i job in background possono finire velocemente. È simile a come i piani Free, Pro, Business e Enterprise di Koder.ai impostano aspettative diverse su quanto si può fare prima di salire di livello.
Supportare limiti personalizzati vale la pena fin da subito, specialmente per l'enterprise. Un approccio pulito è “default per piano, override per cliente”. Memorizza un override impostato dall'admin per org (e a volte per endpoint) e assicurati che sopravviva ai cambi di piano. Decidi anche chi può richiedere cambi e quanto velocemente entrano in vigore.
Esempio: un cliente importa 50.000 record l'ultimo giorno del mese. Se la sua quota mensile è quasi esaurita, un avviso soft all'80–90% gli dà il tempo di mettere in pausa. Un limite per-secondo impedisce che l'import inondi l'API. Un override approvato per l'organizzazione (temporaneo o permanente) mantiene il business in movimento.
Inizia scrivendo cosa conterai e a chi appartiene. La maggior parte dei team finisce con tre identità: l'utente autenticato, l'organizzazione cliente (o workspace) e l'IP client.
Un piano pratico:
Quando imposti i limiti, pensa in termini di tier e gruppi di endpoint, non in un unico numero globale. Un errore comune è affidarsi a contatori in-memory su più server applicativi. I contatori divergono e gli utenti vedono 429 “a caso”. Un archivio condiviso come Redis mantiene i limiti stabili tra le istanze, e i TTL mantengono i dati piccoli.
Il rollout conta. Inizia in modalità “solo report” (logga cosa sarebbe stato bloccato), poi applica a un gruppo di endpoint, poi espandi. Così eviti di svegliarti con una valanga di ticket di supporto.
Quando un cliente raggiunge un limite, il peggior risultato è la confusione: “La vostra API è giù, o ho sbagliato qualcosa?” Risposte chiare e coerenti riducono i ticket di supporto e aiutano le persone a correggere il comportamento del client.
Usa HTTP 429 Too Many Requests quando stai bloccando attivamente le chiamate. Mantieni il corpo della risposta prevedibile così SDK e dashboard possono leggerlo.
Ecco una semplice forma JSON che funziona bene per limiti per-user, per-org e per-IP:
{
"error": {
"code": "rate_limit_exceeded",
"message": "Rate limit exceeded for org. Try again later.",
"limit_scope": "org",
"reset_at": "2026-01-17T12:34:56Z",
"request_id": "req_01H..."
}
}
Le intestazioni dovrebbero spiegare la finestra corrente e cosa il client può fare dopo. Se aggiungi solo poche, inizia con: RateLimit-Limit, RateLimit-Remaining, RateLimit-Reset, Retry-After e X-Request-Id.
Esempio: il cron di un cliente gira ogni minuto e improvvisamente comincia a fallire. Con 429 più RateLimit-Remaining: 0 e Retry-After: 20, capiscono subito che è un limite, non un outage, e possono ritardare i retry di 20 secondi. Se condividono X-Request-Id con il supporto, puoi trovare l'evento velocemente.
Un dettaglio in più: restituisci le stesse intestazioni anche sulle richieste andate a buon fine. I clienti possono vedere di essere vicini al limite prima di raggiungerlo.
I client ben fatti fanno percepire i limiti come equi. I client pessimi trasformano un limite temporaneo in un outage martellando più forte.
Quando ricevi un 429, trattalo come un segnale per rallentare. Se la risposta indica quando riprovare (per esempio tramite Retry-After), aspetta almeno quel tempo. Se non lo fa, usa backoff esponenziale e aggiungi jitter (randomicità) così mille client non ritentano nello stesso istante.
Limita i retry: fissa un massimo per il ritardo tra tentativi (per esempio 30–60 secondi) e un limite al tempo totale di retry (per esempio smetti dopo 2 minuti e mostra un errore). Registra l'evento con i dettagli del limite così gli sviluppatori possono ottimizzare in seguito.
Non ritentare tutto. Molti errori non avranno successo senza una modifica o un'azione dell'utente: 400 validation errors, 401/403 errori di auth, 404 not found e 409 conflict che riflettono regole di business reali.
I retry sono rischiosi su endpoint write (create, charge, send email). Se un timeout avviene e il client ritenta, puoi creare duplicati. Usa idempotency key: il client invia una chiave unica per ogni azione logica, e il server restituisce lo stesso risultato per ripetizioni della stessa chiave.
Buone SDK possono semplificare questo mostrando agli sviluppatori ciò di cui hanno veramente bisogno: lo stato (429), quanto aspettare, se la richiesta è sicura da ritentare e un messaggio tipo “Rate limit exceeded for org. Retry after 8s or reduce concurrency.”
La maggior parte dei ticket di supporto sui limiti non riguarda il limite in sé. Riguarda le sorprese. Se gli utenti non possono prevedere cosa succederà dopo, assumono che l'API sia rotta o ingiusta.
Usare solo limiti basati su IP è un errore frequente. Molti team si trovano dietro un singolo IP pubblico (Wi‑Fi d'ufficio, operatori mobili, NAT cloud). Se capi per IP, un cliente occupato può bloccare tutti gli altri sulla stessa rete. Preferisci limiti per utente e per org, e usa per-IP principalmente come rete di sicurezza anti-abuso.
Un altro problema è trattare tutti gli endpoint allo stesso modo. Un GET economico e un job di esportazione pesante non dovrebbero condividere lo stesso budget. Altrimenti i clienti consumano la loro allowance navigando normalmente e poi vengono bloccati quando provano a eseguire un task reale. Separa i bucket per gruppi di endpoint o pesa le richieste in base al costo.
Il timing del reset deve essere esplicito. “Reset giornaliero” non basta. Quale fuso orario? Finestra rolling o reset a mezzanotte? Se fai reset calendariali, indica il fuso orario. Se fai rolling window, indica la durata della finestra.
Infine, errori vaghi creano caos. Restituire 500 o JSON generico fa ritentare le persone più forte. Usa 429 e includi intestazioni RateLimit così i client possono rallentare in modo intelligente.
Esempio: se un team costruisce un'integrazione Koder.ai da una rete aziendale condivisa, un cap solo per IP può bloccare tutta la loro org e sembrare un outage casuale. Dimensioni chiare e risposte 429 chiare prevengono questo.
Prima di attivare i limiti per tutti, fai un passaggio finale focalizzato sulla prevedibilità:
Un controllo di buon senso: se il tuo prodotto ha tier come Free, Pro, Business ed Enterprise (come Koder.ai), dovresti essere in grado di spiegare in linguaggio semplice cosa può fare un cliente normale al minuto e al giorno, e quali endpoint sono trattati diversamente.
Se non sai spiegare chiaramente un 429, i clienti assumeranno che l'API sia rotta, non che stia proteggendo il servizio.
Immagina una SaaS B2B dove le persone lavorano dentro uno workspace (org). Alcuni power user eseguono esportazioni pesanti e molti dipendenti stanno dietro un unico IP aziendale condiviso. Se limiti solo per IP, blocchi intere aziende. Se limiti solo per utente, uno script singolo può comunque danneggiare tutto lo workspace.
Un mix pratico è:
Quando qualcuno raggiunge un limite, il tuo messaggio dovrebbe dire cosa è successo, cosa fare dopo e quando ritentare. Il supporto dovrebbe poter sostenere una frase tipo:
“Request rate exceeded for workspace ACME. You can retry after 23 seconds. If you are running an export, reduce concurrency to 2 or schedule it off-peak. If this blocks normal use, reply with your workspace ID and timestamp and we can review your quota.”
Affianca quel messaggio a Retry-After e intestazioni RateLimit coerenti così i clienti non devono indovinare.
Un rollout che evita sorprese: prima solo osservazione, poi avviso (intestazioni e warning soft), poi applicazione (429 con timing di retry chiaro), poi taratura delle soglie per tier, quindi revisione dopo grandi lanci e onboard di clienti.
Se vuoi un modo rapido per trasformare queste idee in codice funzionante, una piattaforma vibe-coding come Koder.ai (koder.ai) può aiutarti a redigere una breve specifica di rate limit e generare middleware Go che la applichi in modo coerente tra i servizi.
Un rate limit limita la velocità con cui puoi fare richieste, ad esempio richieste al secondo o al minuto. Una quota limita quanto puoi usare in un periodo più lungo, come al giorno, al mese o per ciclo di fatturazione.
Se vuoi ridurre i casi del tipo “funzionava ieri”, mostra entrambi chiaramente e rendi esplicito il tempo di reset in modo che i clienti possano prevedere il comportamento.
Parti dal guasto che vuoi prevenire. Se i picchi causano timeout, hai bisogno di un controllo sui burst a breve termine; se certi endpoint generano costi, hai bisogno di un budget basato sui costi; se vedi brute force o scraping, servono controlli anti-abuso più severi.
Un modo veloce per decidere è chiedersi: “Se questo endpoint ricevesse 10× traffico, cosa si romperebbe prima: latenza, costi o sicurezza?” Poi progetta il limite attorno a quello.
Usa limiti per utente per impedire che una singola persona rallenti i colleghi, e limiti per organizzazione per mantenere uno spazio di lavoro entro un tetto prevedibile che rispecchi prezzi e capacità. Aggiungi limiti per token quando una chiave condivisa può sovrastare gli utenti interattivi.
Usa i limiti per IP come rete di sicurezza per abusi evidenti, perché le reti condivise possono far sì che i limiti basati su IP blocchino utenti innocenti.
Token bucket è un buon default quando vuoi permettere brevi burst ma far rispettare una media costante nel tempo. Si adatta a pattern UX comuni come dashboard che scatenano più richieste in rapida successione.
Se il tuo backend non tollera affatto i picchi, approcci più rigidi come leaky bucket o code esplicite possono esser più coerenti, ma saranno meno permissivi durante i burst.
Aggiungi un limite di concorrenza quando il danno viene da troppe richieste in volo piuttosto che dal conteggio delle richieste. È comune per endpoint lenti, polling lungo, streaming o grandi esportazioni.
I cap di concorrenza impediscono a un client di “stare entro 60 richieste/minuto” pur tenendo però centinaia di connessioni aperte.
Restituisci HTTP 429 quando stai attivamente limitando, e includi un corpo di errore chiaro che indichi quale ambito è stato superato (user, org, IP o token) e quando il client può riprovare. L'intestazione più utile è Retry-After, perché dice ai client esattamente quanto aspettare.
Restituisci inoltre intestazioni di rate limit anche sulle risposte riuscite così i clienti possono capire di essere vicini al limite prima di essere bloccati.
Una regola semplice: se Retry-After è presente, aspetta almeno quanto indicato prima di ritentare. Se non è presente, usa un backoff esponenziale con un po' di casualità per evitare che molti client ritentino contemporaneamente.
Limita i retry nel tempo e non ritentare a occhi chiusi su errori che non si risolveranno senza intervento, come problemi di autenticazione o di validazione.
Usa limiti hard quando l'eccesso può danneggiare altri clienti o generare costi immediati che non puoi assorbire. Usa limiti soft quando preferisci avvisare prima, dare il tempo di correggere un bug o permettere l'upgrade prima di bloccare.
Un pattern pratico è avvisare tra l'80–90% di utilizzo e poi applicare il blocco più tardi: così riduci ticket urgenti senza lasciare che l'uso incontrollato continui indefinitamente.
Mantieni i limiti per IP generosi e puntali soprattutto all'abuso, perché molte aziende reali condividono un IP pubblico dietro NAT, Wi‑Fi d'ufficio o operatori mobili. Limiti per IP troppo rigidi possono bloccare un intero cliente quando uno script si comporta male.
Per il shaping dell'uso normale, preferisci limiti per utente e per org e usa per IP solo come ultima difesa.
Fai rollout a tappe in modo da vedere l'impatto prima che i clienti soffrano. Parti con "solo report" per loggare cosa sarebbe stato bloccato, poi applica a un piccolo set di endpoint o a un sottoinsieme di tenant, e infine amplia.
Monitora i picchi di 429, l'aumento della latenza causata dal limiter e le identità più bloccate: questi segnali ti dicono dove le soglie o le dimensioni vanno corrette prima che si trasformi in una valanga di ticket.