When you build an integration with the Fatoora platform to issue and clear e-invoices, the architecture runs smoothly as long as request volume stays low. But the behavior changes when load rises: a batch of invoices at day-end close, a peak season, or reprocessing a backlogged queue. This is where rate limits come in. This guide explains what rate limits are, how to recognize an HTTP 429 Too Many Requests response, how to read the Retry-After header, and how to design throttling, queueing, and batching strategies for high-volume issuance that stays within the limits without losing a single invoice.
This article is part of the developer hub within Qoyod E-Invoicing. It assumes you have read Introduction to the E-Invoicing API andFatoora Platform API Endpoints, and understand the difference between the Clearance API and the Reporting API, in addition to Error Handling in the API which explains how to classify the responses coming back from the platform.
What rate limits mean in an API
A rate limit is a ceiling on the number of requests the server accepts from a single client within a defined time window. Its purpose is to protect the platform from being flooded, to guarantee fair distribution of resources across all integrated organizations, and to keep the service stable under pressure. Any API operating at national scale enforces some form of this ceiling, and the Fatoora platform is no exception.
The principle is simple at its core. The platform monitors the number of requests coming from you across a time window, and if you exceed the allowed ceiling it temporarily rejects the extra requests until the window renews. The rejection here is not an error in the invoice data, but a signal that you have sent at a faster pace than the rules allow at that moment.
The key point is to understand that a rate limit does not mean your invoice is wrong. The invoice may be perfectly valid and compliant with every validation rule, but it arrived at a time when you exceeded the allowed pace. That is why handling this rejection differs fundamentally from handling a rejection caused by invalid data. The first is resolved by resending after a delay, while the second requires correcting the data first.
The mechanisms for calculating the limit vary between systems. Some rely on a fixed window that counts requests per minute, for example, then resets the counter. Some rely on a smoother sliding window. And some apply a token bucket algorithm that grants you a balance which renews at a steady rate and allows short bursts above the average. Do not build your logic on assuming a specific mechanism; instead read the platform’s actual behavior from its responses and handle it as it is.
Fixed Window
Sliding Window
Leaky/Token Bucket
The balance renews as time passes
HTTP 429 Too Many Requests response
When you exceed the rate limit, the Fatoora platform responds with an HTTP status code of 429, which means “too many requests.” This code is the standard signal on the web for exceeding the ceiling, and it is completely different from data error codes (the 400 class) and server error codes (the 500 class).
Distinguishing between these three classes is the foundation of building a correct response:
- Code 429. You exceeded the rate limit. The request is valid but your pace is high. The fix: wait, then resend the same request without any change.
- The 400 class (such as 400 and 422). An error in the invoice data or the request structure. Resending without correction will always fail. The fix: correct the data first.
- The 500 class (such as 500 and 503). A temporary server error. The request may be valid and the service is momentarily faltering. The fix: retry with a time gap.
The common mistake is for a developer to treat every unsuccessful code the same way, retrying immediately on the 429 code at a faster pace, which increases the pressure and prolongs the block period. The right approach is to know that a 429 is asking you to slow down, not to insist.
Below is a simplified example of a limit-exceeded response as it might reach you:
Notice two elements: the status code 429 on the first line, and the Retry-After header that tells you exactly when you may try again. These two elements are what your system builds its decision upon.
The Retry-After header: when to try again
The Retry-After header is the standard mechanism by which the server tells you how long to wait before the next send. Respecting this header is the difference between a cooperative client that regains its ability to send quickly, and a stubborn client that prolongs its own block period.
The value comes in one of two forms. The first is a number of seconds, such as Retry-After: 30, which means “wait thirty seconds.” The second is a date and time in HTTP format, which means “do not send before this moment.” Your system must handle both forms. If the value is a number, it is relative seconds. If it is a string that looks like a date, compute the difference between it and the current time.
The golden rule: when the server sends Retry-After, use its value as is and do not guess a shorter time of your own. The server knows when your window renews, and you do not. Ignoring the value and retrying early guarantees you a fresh 429 and burns your balance on requests doomed to rejection.
Here is a logical model for handling the 429 response and respecting the header:
This model separates the wait decision from the rest of the handling logic. On a 429 code it does not correct data and does not log a validation error; instead it computes the wait period and reschedules the same request. And when the header is absent for any reason, it falls back to a reasonable fallback value instead of retrying immediately.
Client-side throttling strategy
Waiting after a 429 is a reactive response. It is better not to reach a 429 in the first place. This is where client-side throttling comes in: you tune your own send pace in advance so you stay under the ceiling with a safety margin, instead of waiting for the server to push you back.
The idea is to define a maximum send rate in your system and stick to it regardless of your queue size. If you have a thousand invoices ready, do not send them all in a single instant. Distribute them over regular intervals at a rate you know is safe. This converts a sudden, massive batch into a steady flow that the server absorbs without objection.
The simplest implementation is a fixed time interval between requests. If your safe rate is ten requests per second, put one hundred milliseconds between each request and the next. A more mature implementation uses a token bucket on the client side: a balance that renews at a steady rate, and each request consumes one unit, so if the balance runs out the request waits until it becomes available. This allows short bursts above the average without breaking the overall ceiling.
The big advantage of proactive throttling is that it makes your system’s behavior predictable. Instead of sharp peaks that hit the limit followed by block periods, you get a smooth flow that finishes the work in less total time, because you are not wasting minutes on forced waiting after every collision.
| Criterion | Sudden batch | Throttled flow |
|---|---|---|
| Pattern | All requests at once | Evenly distributed |
| Result | Hits the limit (429) | Stays under the limit |
| Total time | Slower due to rejection | Faster and more stable |
Queueing to manage sending
Throttling alone is not enough when the workload reaches its peak. You need a place to hold the invoices waiting their turn to be sent. That place is the queue. The queue separates the moment an invoice is created in your system from the moment it is actually sent to the Fatoora platform.
The benefit is that your system can generate invoices at any pace without directly pressuring the platform. Each new invoice is placed in the queue, then a single sender pulls it at a throttled rate under the limit. This decouples production speed from send speed, so you do not have to slow down your internal work just because the platform has a ceiling.
The queue also solves the 429 problem elegantly. When the server responds with a limit exceeded, you do not lose the invoice; instead you return it to the queue with a time delay equal to the Retry-After value. The sender will pick it up again after the interval elapses. This makes handling the limit a natural part of the queue cycle rather than an emergency.
When designing the queue, pay attention to ordering. E-invoicing links each invoice to the hash of the previous invoice to guarantee the integrity of the sequence. So if your invoices belong to a single device and must be sent in the correct order, do not use parallel senders that break the sequence. Make sending sequential within a single chain, and review the Clearance API to understand when clearance is immediate and how it affects send ordering.
Add to the queue a mechanism for graduated retries on transient errors. An invoice that fails due to a temporary server error returns to the queue with an increasing delay. Meanwhile, one that fails due to a data error exits the send path into the manual correction path, because resending it as is serves no purpose. This separation protects the queue from a broken invoice that keeps circulating endlessly.
Batching wisely
Some APIs may support sending several invoices in a single request. This reduces the number of requests and therefore eases the pressure on the rate limit. But batching is a double-edged sword, and using it without understanding may complicate error handling instead of simplifying it.
The benefit is clear: one hundred invoices in ten batches are lighter on the limit than one hundred separate requests. But the trade-off is that a single batch’s response may carry mixed results. Some of the batch’s invoices succeed and some are rejected. That is why your system must read the result of each invoice within the batch individually, rather than treating the batch as a single block that succeeds or fails together.
The practical rule: do not assume that a successful request means every invoice in it succeeded. Read the results array inside the response body, match each result to its invoice identifier, and handle any rejected ones through the correct path. Ignoring this step means that rejected invoices may pass as if they had succeeded, so the problem is discovered too late.
Also consider that the hash sequence imposes a constraint on batches. If the invoices in the batch belong to a single chain, their order within the batch matters, and rejecting an invoice in the middle of the chain may halt what comes after it. Keep the batch size reasonable and consistent with the sequence logic, and do not overload hundreds of invoices into a single request because its response becomes hard to read and diagnose.
Designing high-volume issuance within the limits
When the previous elements come together, you get an architecture capable of high-volume issuance without breaking the limits. The idea is to build a multi-layered production line, each layer responsible for a part, so responsibilities do not pile up in one place.
The first layer generates invoices from your system at any pace and places them in the queue with a “pending send” status. The second layer is a throttled sender that pulls from the queue at a safe rate under the limit and respects the sequence order when required. The third layer is a response handler that classifies each result: a success closes the invoice, or a 429 returns it to the queue with a Retry-After delay, or a data error routes it to the correction path, or a server error retries it in a graduated manner.
Add to that a monitoring layer that measures the actual send rate, the ratio of 429 responses, and the queue length. These indicators tell you early if you are approaching the limit, so you lower the rate before you collide with it. Monitoring turns rate tuning from a fixed guess into a live adaptation to the platform’s behavior at every moment.
During peak times, such as month-end or a specific season, the queue may swell. This is where the value of separating production from sending appears. The queue absorbs the peak, and the sender drains it steadily under the limit. The result is that every invoice arrives, even if it takes a little longer, without losing an invoice or breaking a sequence.
To test this design before production, use the sandbox environment. Build a peak scenario that simulates a large batch, and observe how your system behaves against deliberate 429 responses. Review the Introduction to the E-Invoicing API to learn how to start the integration from the ground up, then progress toward load tests before actual operation.
Invoice generation
Queue
Throttled sender
Response handler
Common mistakes in dealing with rate limits
There are recurring patterns that break integrations when facing the limit. Knowing them in advance spares you costly lessons in production.
Immediate retry after a 429. The most common mistake. When the server rejects the request, some developers resend immediately and at a higher pace, thinking it is a transient problem. The result is the opposite: greater pressure and a longer block period. Always respect Retry-After.
Ignoring the Retry-After header. Some systems use a fixed delay of their own and ignore what the server sends. This may be shorter than needed, repeating the rejection, or longer than needed, slowing the work for no reason. The value coming from the server is more accurate.
Parallel sending that breaks the sequence. To speed up sending, some resort to many parallel senders. This raises the pressure on the limit, and more dangerously, it may break the hash sequence order, so the chain is rejected. Keep sending sequential within a single chain.
Losing invoices on rejection. A system without a queue loses the invoice when the server responds with a 429, or leaves it hanging without resending. A persistent queue guarantees that every invoice rejected due to the limit returns to its turn later.
Confusing rejection due to the limit with rejection due to the data. Treating a 429 as if it were a data error pushes you to needlessly correct a sound invoice. And treating a data error as if it were a 429 draws you into an endless retry for an invoice that will never be accepted. Distinguish between the two classes from the status code first.
Exponential backoff and jitter
When you need to retry on a temporary server error from the five-hundred class, do not repeat the attempt with the same interval every time. Use exponential backoff: make the wait period double with each failed attempt. The first attempts wait one second, then two, then four, and so on up to a reasonable maximum. This gives the faltering server time to recover instead of drowning it in successive attempts.
Add to the backoff a simple randomness (jitter). If the service goes down momentarily and thousands of clients retry at the same instant, they all collide with the server at once the moment it recovers, taking it down again. Randomness distributes the retry attempts over a time window instead of stacking them at a single point. In practice, add a small random amount to the computed wait period before each retry.
Pay attention to the important distinction: exponential backoff is for transient errors from the server class, whereas the 429 code has its own rule. On a 429, respect the Retry-After value coming from the server first, and do not replace it with your exponential calculation. Make exponential backoff your fallback path when there is no explicit header, or when the error is from the server class rather than the limit class.
Also set a cap on the number of attempts. An invoice that fails after a reasonable number of retries should exit to the manual review path instead of circulating endlessly. Retrying without a cap turns a transient error into an infinite loop that consumes resources and hides the real problem from your team.
Monitoring the rate in production
A rate strategy is not complete without a monitoring layer that tells you your system’s actual behavior. Measurement turns rate tuning from a fixed number you guess once into a live adaptation to reality. Three indicators deserve continuous tracking.
Actual send rate. How many requests you actually send per second or per minute. Compare it to the ceiling you think is safe. If the two numbers converge, you are on the edge of collision and need to lower the rate or expand the queue.
The ratio of 429 responses. Any rise in this ratio is an early warning that you are exceeding the limit. The healthy ratio is close to zero, because proactive throttling is supposed to prevent reaching the limit in the first place. Its rise means your tuned rate is higher than it should be.
Queue length and wait time within it. A queue that keeps swelling without draining is an indicator that the production rate exceeds the safe send rate for a long period. This is normal in a short peak, but if it persists you need either to raise the safe rate if the platform allows, or to redistribute the work over calmer windows.
Tie these indicators to alerts. An alert when the 429 ratio exceeds a certain threshold, and an alert when the queue length exceeds a limit that threatens delay past reporting deadlines. Monitoring without alerting is a log read after the fact, whereas an alert gives you the chance to intervene before the pressure turns into late invoices.
What Qoyod handles on your behalf
Everything above describes what you must build if you integrate directly with the Fatoora platform yourself. But a large part of this complexity disappears when you issue your invoices through Qoyod. The platform handles the integration layer with the Fatoora platform, including managing the send pace, so you do not build a queue, throttling logic, or 429 handling from scratch.
Qoyod manages the CSID certificate, the digital signature, clearance, and reporting with the Fatoora platform automatically. When your issuance volumes rise, the platform takes care of organizing the flow of requests toward the Fatoora platform in a way that maintains compliance and avoids colliding with the limits, without you intervening in the details of the rate, delay, or rescheduling.
This separation means your team focuses on business logic: creating correct invoices, managing customers, and reporting, while Qoyod takes care of the technical layer of the integration. But if you are building your own direct integration for reasons specific to you, what this guide covers is your map for designing high-volume issuance that holds up in production.
Let Qoyod manage the integration with the Fatoora platform on your behalf
Qoyod handles the signing, clearance, and organizing the send flow toward the Fatoora platform automatically, so you issue your invoices at any volume without building a queue or throttling logic yourself.
Start your free trial and issue your invoices at high volume without worrying about limits
Next steps in the developer hub
Rate limits are one link in the chain of building a robust integration. Now that you understand how to stay under the ceiling, complementary topics remain that make your integration production-ready.
Start withError Handling in the API to understand how to classify every response coming from the platform and build a fix map for each code. Then review Sending the Invoice via API to master the full send cycle from building the request to reading the result. And look at Clearance API andReporting API to understand the difference between the two paths and its effect on send ordering and volume.
And when you move to actual operation, make sure your authentication layer renews its tokens before they expire so the send chain does not stall. Review Authentication in the Invoicing API to learn how to build this layer reliably.
A practical summary
Rate limits are not an obstacle but a clear rule of play. The 429 code asks you to slow down, the Retry-After header tells you when to return, the queue guarantees you do not lose an invoice, proactive throttling spares you the collision in the first place, and batching eases the pressure when read by its individual results. Combine these elements into a layered architecture that separates production from sending from response handling, and you will issue at high volume without losing an invoice or breaking a sequence. And if you want to bypass this whole build, issuing through Qoyod moves this technical layer to the platform and leaves your business logic to you.