Limits and Quotas
To prevent abuse, avoid slowdowns, and ensure that all users make responsible use of our APIs, we enforce quota limits. If you perform too many requests you may exceed your quota; in this case, you will receive an error as a response until when your quota will be restored.
We enforce two different kinds of limits, based on the time range that they apply to.
📆 Long-term usage limits
Long-term usage limits are meant to avoid improper usage of the APIs, by setting a limit on the maximum amount of requests in a certain period.
If the app is public every user that has access to a specific company through a specific app shares (and consumes) the same quotas as other users that have access to the same company through the same app, if the app is private the quotas are no more related to the app but only to the company, this means that creating a new private app won't increase your quotas.
The quota limits are the following:
API limit type | Limit |
---|---|
Requests per hour | 1.000 requests per hour on a company-app couple |
Requests per month | 40.000 requests per month on a company-app couple for public apps |
Requests per month | 40.000 requests per month per company for private apps |
Long-term limits use fixed time windows: these quotas are reset at the beginning of a new hour or month.
If the limit is exceeded, the API returns a 403 Forbidden HTTP status code; the response also includes a Retry-After header, indicating how long you should wait before retrying the request (in seconds).
⏱ Short-term rate limits
Short-term rate limits are meant to avoid usage spikes that could affect the system availability, by preventing applications from sending too many requests in a short-time interval.
This means that every app and user that has access to a specific company shares (and consumes) the same quotas with other apps and users that have access to the same company.
The quota limit is the following:
API limit type | Limit |
---|---|
Requests every 5 minutes | 300 requests every 5 minutes |
Short-term usage limits use a sliding-window algorithm, check the additional resources for further info.
If the limit is exceeded, the API returns a 429 Too Many Requests HTTP status code. The response also includes a Retry-After header, indicating how long you should wait before the request (in seconds).
📑 HTTP Headers and response codes
Every response contains several HTTP headers containing info about long-term usage limits.
Header | Description |
---|---|
RateLimit-HourlyRemaining | The number of requests remaining for the current hour. |
RateLimit-HourlyLimit | The maximum number of requests you are permitted to make per hour. |
RateLimit-MonthlyRemaining | The number of requests remaining for the current month. |
RateLimit-MonthlyLimit | The maximum number of requests you are permitted to make per month. |
Here you can find an example of the HTTP response:
HTTP/1.1 200 OK
Date: Tue, 05 May 2020 17:27:06 GMT
Status: 200 OK
RateLimit-HourlyRemaining: 840
RateLimit-HourlyLimit: 1000
RateLimit-MonthlyRemaining: 1430
RateLimit-MonthlyLimit: 20000
🌊 Is it still not enough?
We designed our quotas to be able to satisfy the needs of the majority of the use cases. Nevertheless, in some cases, the default quotas could prove to be scarce, and in this situation, the only way is to increment the quota limit.
If you think that your use case requires a higher quota, you can try to request us additional quota. It isn't automatic though, you'll have to explain your use case in detail to our team and demonstrate why our quotas are not sufficient to resolve it.
We can increment only the long-term usage limits.
If you're facing an issue with short-term rate quota, e.g. you're obtaining a 429 Too Many Requests error response, it means that you're not managing our APIs correctly. If that's the case, we'll reject every additional quota request we'll receive, and you should instead read the next section.
💆♂️ Keep calm and deal with quotas!
As explained above, if the rate limit will be exceeded you'll receive a 429 response to your requests. This is not a fatal error and we expect you to retry the request after a short interval; if the requests will keep arriving too quickly, your requests will result in another error result, and so on.
It is then important to gradually increase the delay between requests to overcome this issue, this is usually done by applying exponential back-off to your requests.
Here you can find some code examples that you can use to introduce exponential back-off on your code:
- C#
- Go
- Java
- JavaScript
- PHP
- Python
- Ruby
- TypeScript
// We apply exponential backoff to our C# SDK
// https://github.com/fattureincloud/fattureincloud-csharp-sdk/
// We suppose to use the http://www.thepollyproject.org
// and the https://github.com/Polly-Contrib/Polly.Contrib.WaitAndRetry libraries
// to implement the exponential back-off
// to install them using the .Net cli:
// dotnet add package Polly
// dotnet add package Polly.Contrib.WaitAndRetry
using System;
using Polly;
using It.FattureInCloud.Sdk.Api;
using It.FattureInCloud.Sdk.Model;
using It.FattureInCloud.Sdk.Client;
using Polly.Contrib.WaitAndRetry;
namespace Backoff
{
class Program
{
static void Main(string[] args)
{
Configuration config = new Configuration();
config.AccessToken = "YOUR_ACCESS_TOKEN";
var apiInstance = new ProductsApi(config);
var companyId = 11;
var maxRetryAttempts = 5;
var pauseBetweenFailures = Backoff.ExponentialBackoff(TimeSpan.FromSeconds(2), retryCount: maxRetryAttempts);
var retryPolicy = Policy
.Handle<ApiException>()
.WaitAndRetry(pauseBetweenFailures);
retryPolicy.Execute(() =>
{
ListProductsResponse result = apiInstance.ListProducts(companyId);
Console.Write("\n successful");
Console.Write(result);
});
}
}
}
// We apply exponential backoff to our Go SDK
// https://github.com/fattureincloud/fattureincloud-go-sdk/
// We suppose to use the https://pkg.go.dev/github.com/cenkalti/backoff/v4 library
// to implement the exponential back-off
// to install it:
// go get github.com/cenkalti/backoff/v4
package main
import (
"context"
"encoding/json"
"fmt"
"os"
backoff "github.com/cenkalti/backoff/v4"
fattureincloudapi "github.com/fattureincloud/fattureincloud-go-sdk/v2/api"
)
var (
companyId = int32(2)
auth = context.WithValue(context.Background(), fattureincloudapi.ContextAccessToken, "YOUR_ACCESS_TOKEN")
configuration = fattureincloudapi.NewConfiguration()
apiClient = fattureincloudapi.NewAPIClient(configuration)
)
func main() {
operation := func() error {
resp, _, err := apiClient.ProductsAPI.ListProducts(auth, companyId).Execute()
if resp != nil {
json.NewEncoder(os.Stdout).Encode(resp)
} else {
fmt.Println(err)
}
return err
}
err := backoff.Retry(operation, backoff.NewExponentialBackOff())
if err != nil {
fmt.Fprintf(os.Stderr, "Error %v\n", err)
return
}
}
// We apply exponential backoff to our Java SDK
// https://github.com/fattureincloud/fattureincloud-java-sdk/
// We suppose to use the https://resilience4j.readme.io library to implement the exponential back-off
// to install it see: https://search.maven.org/artifact/io.github.resilience4j/resilience4j-retry/1.7.1/jar
import io.github.resilience4j.core.IntervalFunction;
import io.github.resilience4j.retry.Retry;
import io.github.resilience4j.retry.RetryConfig;
import io.github.resilience4j.retry.RetryRegistry;
import io.vavr.CheckedFunction0;
import it.fattureincloud.sdk.ApiClient;
import it.fattureincloud.sdk.ApiException;
import it.fattureincloud.sdk.Configuration;
import it.fattureincloud.sdk.api.ProductsApi;
import it.fattureincloud.sdk.auth.OAuth;
import it.fattureincloud.sdk.model.ListProductsResponse;
public class Application {
public static void main(String[] args) throws Throwable {
ApiClient defaultClient = Configuration.getDefaultApiClient();
OAuth OAuth2AuthenticationCodeFlow = (OAuth) defaultClient.getAuthentication("OAuth2AuthenticationCodeFlow");
OAuth2AuthenticationCodeFlow.setAccessToken("a/eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyZWYiOiJoMWJVQWJiVmpDT3ZqWmliYXlhOGMzcEQ2aEVEeENPcSIsImV4cCI6MTY0MjA4NDgzNH0.mBOAhimqtRV6WurlfVWNj9Sq7zOBZvGqSzV1swG0AN4");
RetryConfig config = RetryConfig.custom()
.maxAttempts(10)
.retryExceptions(ApiException.class)
.intervalFunction(IntervalFunction.ofExponentialBackoff(1000, 2))
.build();
RetryRegistry registry = RetryRegistry.of(config);
Retry retry = registry.retry("listProducts", config);
Retry.EventPublisher publisher = retry.getEventPublisher();
publisher.onRetry(event -> System.out.println(event.toString()));
ProductsApi apiInstance = new ProductsApi(defaultClient);
CheckedFunction0<ListProductsResponse> retryingListSuppliers =
Retry.decorateCheckedSupplier(retry,
() -> apiInstance.listProducts(2, null, null, null, null, null));
System.out.println(retryingListSuppliers.apply().getData());
}
}
// We apply exponential backoff to our JavaScript SDK
// https://github.com/fattureincloud/fattureincloud-js-sdk/
let defaultClient = fattureInCloudSdk.ApiClient.instance;
let OAuth2AuthenticationCodeFlow =
defaultClient.authentications["OAuth2AuthenticationCodeFlow"];
OAuth2AuthenticationCodeFlow.accessToken = "YOUR_ACCESS_TOKEN";
let productsApiInstance = new fattureInCloudSdk.ProductsApi();
var companyId = 16;
var opts = {};
const delay = (retryCount) =>
new Promise((resolve) => setTimeout(resolve, 2 ** retryCount * 1000));
const getProd = async (retryCount = 0, lastError = null) => {
if (retryCount > 20) throw new Error(lastError);
try {
return await productsApiInstance.listProducts(companyId, opts);
} catch (e) {
await delay(retryCount);
return getProd(retryCount + 1, e);
}
};
console.log(await getProd());
// We apply exponential backoff to our PHP SDK
// https://github.com/fattureincloud/fattureincloud-php-sdk/
// We suppose to use the https://github.com/stechstudio/backoff library
// to implement the exponential back-off
// to install: composer require stechstudio/backoff
$backoff = new Backoff(10, 'exponential', 10000, true);
$accessToken = "YOUR_ACCESS_TOKEN";
$config = Configuration::getDefaultConfiguration()->setAccessToken($accessToken);
$service = new UserApi(new Client(), $config);
$result = $backoff->run(function() {
return $this->service->listUserCompanies();
});
return $result; // it contains the result of the closure
# We apply exponential backoff to our Python SDK
# https://github.com/fattureincloud/fattureincloud-python-sdk/
# We suppose to use the https://github.com/litl/backoff library to implement the exponential back-off
# to install: pip install backoff
import fattureincloud_python_sdk
from fattureincloud_python_sdk.api import products_api
from fattureincloud_python_sdk.exceptions import ApiException
import backoff
import collections
collections.Callable = collections.abc.Callable # needed if you are using python > 3.10
@backoff.on_exception(backoff.expo, ApiException, max_tries=10)
def get_products(configuration, company_id):
with fattureincloud_python_sdk.ApiClient(configuration) as api_client:
products_api_instance = products_api.ProductsApi(api_client)
products_api_instance.list_products(company_id)
return
configuration = fattureincloud_python_sdk.Configuration()
configuration.access_token = "YOUR_ACCESS_TOKEN"
configuration.retries = 0 # needed to implement custom backoff
company_id = 11
get_products(configuration, company_id)
# We apply exponential backoff to our Ruby SDK
# https://github.com/fattureincloud/fattureincloud-ruby-sdk/
require 'fattureincloud_ruby_sdk'
FattureInCloud_Ruby_Sdk.configure do |config|
# Configure OAuth2 access token for authorization: OAuth2AuthenticationCodeFlow
config.access_token = "YOUR_TOKEN"
end
suppliers_api_instance = FattureInCloud_Ruby_Sdk::SuppliersApi.new
retries = 0
max_retries = 20
company_id = 2
begin
company_suppliers = suppliers_api_instance.list_suppliers(company_id)
puts company_suppliers
rescue FattureInCloud_Ruby_Sdk::ApiError => e
if retries <= max_retries
retries += 1
sleep 2 ** retries
retry
else
raise "Giving up on the server after #{retries} retries. Got error: #{e.message}"
end
end
// We apply exponential backoff to our TypeScript SDK
// https://github.com/fattureincloud/fattureincloud-ts-sdk/
import {
Configuration,
ProductsApi,
} from "@fattureincloud/fattureincloud-ts-sdk";
const apiConfig = new Configuration({
accessToken: "YOUR_ACCESS_TOKEN",
});
let productsApiInstance = new ProductsApi(apiConfig);
var companyId = 2;
var opts = {};
const delay = (retryCount: number) =>
new Promise((resolve) => setTimeout(resolve, 2 ** retryCount * 1000));
const getProd: any = async (retryCount = 0, lastError?: string) => {
if (retryCount > 20) throw new Error(lastError);
try {
return await productsApiInstance.listProducts(companyId);
} catch (e: any) {
await delay(retryCount);
return getProd(++retryCount, e.message);
}
};
console.log(await getProd());
You can set the Access Token in the dedicated section, for more informations look here.
Alternatively, you can use the Retry-After header to wait the right amount of time before sending your request again.
📚 Additional Resources
- Rate limiting using the Fixed Window algorithm
- Rate limiting using the Sliding Window algorithm
- Exponential Backoff
- Exponential Backoff And Jitter
- Better Retries with Exponential Backoff and Jitter for Java
- Retry-After Header
- 403 Forbidden
- 429 Too Many Requests
- Windowing examples in the Apache Beam documentation
- Retry with resilience4j