Référence de l’API

Limites de débit

Les compartiments de requêtes de votre organisation, les en-têtes RateLimit, la gestion d’un 429, et comment demander une limite plus haute.

Ouvrir en Markdown
Sur cette page
  1. Les compartiments
  2. Lire les en-têtes
  3. Quand une limite est atteinte
  4. Le plafond quotidien du mode test
  5. Gérer un 429
  6. Voir vos limites
  7. Demander une limite plus haute
  8. Autres limites

L’API limite le nombre de requêtes que votre organisation envoie par minute, pour que le pic d’une intégration ne ralentisse jamais les autres. Les limites comptent par organisation et par mode : toutes les clés de votre organisation les partagent, et le mode test a les siennes. Utiliser le tableau de bord ne les consomme pas, pas plus que les webhooks que nous vous envoyons.

Les compartiments

Chaque requête compte dans un ou plusieurs compartiments (buckets). Elle doit trouver de la place dans chacun de ses compartiments ; une requête refusée par un compartiment ne consomme rien dans les autres.

CompartimentCouvreProduction, par minuteTest, par minute
Toutes les requêtesChaque requête à l’API1 200600
ÉcrituresChaque POST, PUT et DELETE300150
Créations de paiementPOST /v1/payment_intents et POST /v1/checkout/sessions12060
Recherches et exportsLes recherches dans les listes et les exports3030

Un compartiment se remplit à son rythme par minute, et garde 10 secondes de réserve en plus pour les pics courts, arrondies à la requête entière. Une limite de 120 par minute se remplit de 2 requêtes par seconde et en laisse passer jusqu’à 140 d’un coup.

Ce sont les valeurs par défaut ; les vôtres peuvent différer. Développeurs → Limites de l’API montre les valeurs qui s’appliquent à votre organisation.

Lire les en-têtes

Une fois votre clé acceptée, chaque réponse porte trois en-têtes sur le compartiment le plus serré de la requête, un 429 compris :

  • RateLimit-Limit : la limite du compartiment par minute ;
  • RateLimit-Remaining : les requêtes que vous pouvez encore envoyer tout de suite. Un compartiment plein garde sa réserve en plus de sa limite : ce chiffre peut donc dépasser RateLimit-Limit, jusqu’à 699 pour une limite de 600 ;
  • RateLimit-Reset : les secondes avant que le compartiment soit de nouveau plein.

Une requête refusée avant d’être comptée n’en porte aucun : une clé absente, refusée ou qui n’a pas le droit de faire la requête, une version de l’API qui n’existe pas, une adresse que l’API ne connaît pas.

Lisez RateLimit-Remaining pour ralentir avant d’atteindre la limite, plutôt qu’après. Pendant un incident de notre côté, le compte peut être moins régulier un moment : prenez les en-têtes comme un repère, et un 429 comme la règle.

  • curl
  • Node.js
  • TypeScript
  • Python
  • PHP
  • Ruby
  • Go
  • Java
  • C#
  • Rust
headers.mjsheaders.tsheaders.pyheaders.phpheaders.rbheaders.goHeaders.javaHeaders.csmain.rs
curl -i https://pay-api.adpromax.com/v1/account \
  -u "$APX_SECRET_KEY:"
const response = await fetch('https://pay-api.adpromax.com/v1/account', {
  headers: { Authorization: `Bearer ${process.env.APX_SECRET_KEY}` },
})
const names = ['RateLimit-Limit', 'RateLimit-Remaining', 'RateLimit-Reset']
for (const name of names)
  console.log(`${name}: ${response.headers.get(name)}`)
const response: Response = await fetch('https://pay-api.adpromax.com/v1/account', {
  headers: { Authorization: `Bearer ${process.env.APX_SECRET_KEY}` },
})
const names = ['RateLimit-Limit', 'RateLimit-Remaining', 'RateLimit-Reset']
for (const name of names) {
  const value: string | null = response.headers.get(name)
  console.log(`${name}: ${value}`)
}
import os

import requests

response = requests.get(
    "https://pay-api.adpromax.com/v1/account",
    auth=(os.environ["APX_SECRET_KEY"], ""),
)
for name in ("RateLimit-Limit", "RateLimit-Remaining", "RateLimit-Reset"):
    print(f"{name}: {response.headers.get(name)}")
<?php
$headers = [];
$ch = curl_init('https://pay-api.adpromax.com/v1/account');
curl_setopt_array($ch, [
    CURLOPT_USERPWD => getenv('APX_SECRET_KEY') . ':',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HEADERFUNCTION => function ($ch, $line) use (&$headers) {
        $parts = explode(':', $line, 2);
        if (count($parts) === 2) {
            $headers[strtolower($parts[0])] = trim($parts[1]);
        }
        return strlen($line);
    },
]);
curl_exec($ch);
foreach (['RateLimit-Limit', 'RateLimit-Remaining', 'RateLimit-Reset'] as $name) {
    echo $name, ': ', $headers[strtolower($name)] ?? '', PHP_EOL;
}
require 'net/http'

uri = URI('https://pay-api.adpromax.com/v1/account')
request = Net::HTTP::Get.new(uri)
request.basic_auth(ENV.fetch('APX_SECRET_KEY'), '')

response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == 'https') do |http|
  http.request(request)
end
%w[RateLimit-Limit RateLimit-Remaining RateLimit-Reset].each do |name|
  puts "#{name}: #{response[name]}"
end
package main

import (
	"fmt"
	"log"
	"net/http"
	"os"
)

func main() {
	req, err := http.NewRequest(http.MethodGet, "https://pay-api.adpromax.com/v1/account", nil)
	if err != nil {
		log.Fatal(err)
	}
	req.SetBasicAuth(os.Getenv("APX_SECRET_KEY"), "")

	res, err := http.DefaultClient.Do(req)
	if err != nil {
		log.Fatal(err)
	}
	defer res.Body.Close()

	names := []string{"RateLimit-Limit", "RateLimit-Remaining", "RateLimit-Reset"}
	for _, name := range names {
		fmt.Printf("%s: %s\n", name, res.Header.Get(name))
	}
}
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.List;

public class Headers {
    public static void main(String[] args) throws Exception {
        HttpRequest request = HttpRequest.newBuilder(URI.create("https://pay-api.adpromax.com/v1/account"))
            .header("Authorization", "Bearer " + System.getenv("APX_SECRET_KEY"))
            .GET()
            .build();
        HttpResponse<String> response = HttpClient.newHttpClient()
            .send(request, HttpResponse.BodyHandlers.ofString());

        List<String> names = List.of(
            "RateLimit-Limit", "RateLimit-Remaining", "RateLimit-Reset");
        for (String name : names) {
            System.out.println(name + ": "
                + response.headers().firstValue(name).orElse(""));
        }
    }
}
using System.Net.Http.Headers;

using var http = new HttpClient();
http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
    "Bearer", Environment.GetEnvironmentVariable("APX_SECRET_KEY"));

using var response = await http.GetAsync("https://pay-api.adpromax.com/v1/account");
string[] names = ["RateLimit-Limit", "RateLimit-Remaining", "RateLimit-Reset"];
foreach (var name in names)
{
    response.Headers.TryGetValues(name, out var values);
    Console.WriteLine($"{name}: {values?.FirstOrDefault()}");
}
// Cargo.toml: reqwest = { version = "0.12", features = ["blocking"] }
fn main() -> Result<(), Box<dyn std::error::Error>> {
    let response = reqwest::blocking::Client::new()
        .get("https://pay-api.adpromax.com/v1/account")
        .bearer_auth(std::env::var("APX_SECRET_KEY")?)
        .send()?;

    for name in ["RateLimit-Limit", "RateLimit-Remaining", "RateLimit-Reset"] {
        let value = response.headers().get(name).and_then(|v| v.to_str().ok());
        println!("{name}: {}", value.unwrap_or(""));
    }
    Ok(())
}
Réponse
HTTP/1.1 200 OK
Content-Type: application/json
Request-Id: req_01k7e6a2b5c8d1f4g7h0j3k6mx
RateLimit-Limit: 600
RateLimit-Remaining: 699
RateLimit-Reset: 1

Quand une limite est atteinte

L’API répond 429 avec une rate_limit_error dont le code est rate_limit_exceeded. Le message nomme le compartiment, sa limite et l’attente, et l’en-tête Retry-After donne cette attente en secondes. Rien n’a été fait : la requête peut être renvoyée telle quelle.

Réponse429 Too Many Requests
{
  "error": {
    "type": "rate_limit_error",
    "code": "rate_limit_exceeded",
    "message": "Too many payment creation requests: the limit is 60 per minute in test mode. Try again in 1 second.",
    "doc_url": "https://pay-docs.adpromax.com/errors#rate_limit_exceeded",
    "request_id": "req_01k7e6c9d3f6g9h2j5k8m1n4pq"
  }
}

Le plafond quotidien du mode test

En plus de ses limites de débit, le mode test a un plafond quotidien par organisation : 10 000 requêtes et 1 000 créations de paiement par jour, comptées en heure du Maroc. Au-delà, l’API répond 429 avec le code test_daily_cap_reached jusqu’à minuit, heure du Maroc. La production n’a pas de plafond quotidien.

Le plafond protège le bac à sable partagé. Une suite de tests automatisés l’atteint rarement ; un test de charge, si : faites vos tests de charge sur votre propre code, pas sur le bac à sable.

Réponse429 Too Many Requests
{
  "error": {
    "type": "rate_limit_error",
    "code": "test_daily_cap_reached",
    "message": "Test mode's daily cap of 1,000 payment creations is reached. It resets at midnight, Morocco time.",
    "doc_url": "https://pay-docs.adpromax.com/errors#test_daily_cap_reached",
    "request_id": "req_01k7e6f5g8h1j4k7m0n3p6q9rs"
  }
}

Gérer un 429

  1. Attendez le nombre de secondes de Retry-After, plus un peu de hasard, pour que plusieurs serveurs ne réessaient pas au même instant. Une limite de débit demande quelques secondes au plus. Une attente de plusieurs minutes ou heures, c’est le plafond quotidien, qui dure jusqu’à minuit, heure du Maroc : n’attendez pas dessus. Arrêtez, et remontez l’erreur.
  2. Renvoyez la même requête, avec la même Idempotency-Key. Un refus pour la limite de débit n’est pas gardé avec la clé : le nouvel essai s’exécute, et l’opération n’a toujours lieu qu’une fois. Voir Idempotence.
  3. Abandonnez après quelques essais, et journalisez le request_id.

Pour rester sous les limites :

  • étalez les traitements par lots dans le temps au lieu de tout envoyer d’un coup ;
  • surveillez RateLimit-Remaining ;
  • apprenez l’issue des paiements par les webhooks plutôt qu’en les relisant sans cesse.
  • curl
  • Node.js
  • TypeScript
  • Python
  • PHP
  • Ruby
  • Go
  • Java
  • C#
  • Rust
retry-429.mjsretry-429.tsretry_429.pyretry_429.phpretry_429.rbretry_429.goRetry429.javaRetry429.csmain.rs
# --retry sends it again after a 429, waiting as long as Retry-After says;
# --retry-max-time gives up when that wait is longer (test mode's daily cap)
curl https://pay-api.adpromax.com/v1/payment_intents \
  -u "$APX_SECRET_KEY:" \
  --retry 5 \
  --retry-max-time 60 \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: order-1046-create" \
  -d '{"amount": 25000, "currency": "mad",
       "payment_method_types": ["card"]}'
const api = 'https://pay-api.adpromax.com/v1'
const LONGEST_WAIT = 60 // seconds

// Sends a request again after a 429, waiting as long as Retry-After says.
async function send(path, init, attempts = 5) {
  for (let attempt = 1; ; attempt++) {
    const response = await fetch(`${api}${path}`, init)
    if (response.status !== 429 || attempt === attempts)
      return response
    const wait = Number(response.headers.get('Retry-After')) || 1
    if (wait > LONGEST_WAIT) // test mode's daily cap, until midnight
      return response
    const jitter = Math.random() * 500
    await new Promise(resolve => setTimeout(resolve, wait * 1000 + jitter))
  }
}

const response = await send('/payment_intents', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${process.env.APX_SECRET_KEY}`,
    'Content-Type': 'application/json',
    // The same key on every try: the payment is created once.
    'Idempotency-Key': 'order-1046-create',
  },
  body: JSON.stringify({
    amount: 25000,
    currency: 'mad',
    payment_method_types: ['card'],
  }),
})
console.log(response.status, response.headers.get('RateLimit-Remaining'))
const api = 'https://pay-api.adpromax.com/v1'
const LONGEST_WAIT = 60 // seconds

// Sends a request again after a 429, waiting as long as Retry-After says.
async function send(
  path: string,
  init: RequestInit,
  attempts = 5,
): Promise<Response> {
  for (let attempt = 1; ; attempt++) {
    const response = await fetch(`${api}${path}`, init)
    if (response.status !== 429 || attempt === attempts)
      return response
    const wait = Number(response.headers.get('Retry-After')) || 1
    if (wait > LONGEST_WAIT) // test mode's daily cap, until midnight
      return response
    const jitter = Math.random() * 500
    await new Promise(resolve => setTimeout(resolve, wait * 1000 + jitter))
  }
}

const response = await send('/payment_intents', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${process.env.APX_SECRET_KEY}`,
    'Content-Type': 'application/json',
    // The same key on every try: the payment is created once.
    'Idempotency-Key': 'order-1046-create',
  },
  body: JSON.stringify({
    amount: 25000,
    currency: 'mad',
    payment_method_types: ['card'],
  }),
})
console.log(response.status, response.headers.get('RateLimit-Remaining'))
import os
import random
import time

import requests

API = "https://pay-api.adpromax.com/v1"
AUTH = (os.environ["APX_SECRET_KEY"], "")
LONGEST_WAIT = 60  # seconds


# Sends a request again after a 429, waiting as long as Retry-After says.
def send(path, body, key, attempts=5):
    for attempt in range(1, attempts + 1):
        response = requests.post(
            f"{API}{path}", auth=AUTH, json=body,
            # The same key on every try: the payment is created once.
            headers={"Idempotency-Key": key},
        )
        if response.status_code != 429 or attempt == attempts:
            return response
        wait = int(response.headers.get("Retry-After", 1))
        if wait > LONGEST_WAIT:  # test mode's daily cap, until midnight
            return response
        time.sleep(wait + random.random() / 2)


response = send("/payment_intents", {
    "amount": 25000,
    "currency": "mad",
    "payment_method_types": ["card"],
}, "order-1046-create")
print(response.status_code, response.headers.get("RateLimit-Remaining"))
<?php
const LONGEST_WAIT = 60; // seconds

// Sends a request again after a 429, waiting as long as Retry-After says.
function send(string $path, array $body, string $key, int $attempts = 5): array
{
    for ($attempt = 1; ; $attempt++) {
        $retryAfter = 1;
        $ch = curl_init('https://pay-api.adpromax.com/v1' . $path);
        curl_setopt_array($ch, [
            CURLOPT_USERPWD => getenv('APX_SECRET_KEY') . ':',
            CURLOPT_POST => true,
            CURLOPT_HTTPHEADER => [
                'Content-Type: application/json',
                // The same key on every try: the payment is created once.
                'Idempotency-Key: ' . $key,
            ],
            CURLOPT_POSTFIELDS => json_encode($body),
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_HEADERFUNCTION => function ($ch, $line) use (&$retryAfter) {
                if (stripos($line, 'Retry-After:') === 0) {
                    $retryAfter = (int) trim(substr($line, 12));
                }
                return strlen($line);
            },
        ]);
        $answer = json_decode(curl_exec($ch), true);
        $status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
        if ($status !== 429 || $attempt === $attempts) {
            return [$status, $answer];
        }
        if ($retryAfter > LONGEST_WAIT) { // test mode's daily cap, until midnight
            return [$status, $answer];
        }
        usleep(($retryAfter * 1000 + random_int(0, 500)) * 1000);
    }
}

[$status, $intent] = send('/payment_intents', [
    'amount' => 25000,
    'currency' => 'mad',
    'payment_method_types' => ['card'],
], 'order-1046-create');
echo $status, PHP_EOL;
require 'json'
require 'net/http'

API = 'https://pay-api.adpromax.com/v1'.freeze
LONGEST_WAIT = 60 # seconds

# Sends a request again after a 429, waiting as long as Retry-After says.
def send_again(path, body, key, attempts = 5)
  uri = URI("#{API}#{path}")
  request = Net::HTTP::Post.new(uri)
  request.basic_auth(ENV.fetch('APX_SECRET_KEY'), '')
  request['Content-Type'] = 'application/json'
  # The same key on every try: the payment is created once.
  request['Idempotency-Key'] = key
  request.body = JSON.generate(body)

  1.upto(attempts) do |attempt|
    response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == 'https') do |http|
      http.request(request)
    end
    return response if response.code != '429' || attempt == attempts

    wait = (response['Retry-After'] || 1).to_i
    # A longer wait is test mode's daily cap, until midnight.
    return response if wait > LONGEST_WAIT

    sleep(wait + rand / 2)
  end
end

response = send_again('/payment_intents', {
  amount: 25000,
  currency: 'mad',
  payment_method_types: ['card']
}, 'order-1046-create')
puts "#{response.code} #{response['RateLimit-Remaining']}"
package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"log"
	"math/rand"
	"net/http"
	"os"
	"strconv"
	"time"
)

const api = "https://pay-api.adpromax.com/v1"

const longestWait = 60 // seconds

// send posts a request again after a 429, waiting as long as Retry-After says.
func send(
	path string, body any, key string, attempts int,
) (*http.Response, error) {
	payload, err := json.Marshal(body)
	if err != nil {
		return nil, err
	}
	for attempt := 1; ; attempt++ {
		req, err := http.NewRequest(http.MethodPost, api+path,
			bytes.NewReader(payload))
		if err != nil {
			return nil, err
		}
		req.SetBasicAuth(os.Getenv("APX_SECRET_KEY"), "")
		req.Header.Set("Content-Type", "application/json")
		// The same key on every try: the payment is created once.
		req.Header.Set("Idempotency-Key", key)

		res, err := http.DefaultClient.Do(req)
		if err != nil {
			return nil, err
		}
		if res.StatusCode != http.StatusTooManyRequests || attempt == attempts {
			return res, nil
		}
		wait, err := strconv.Atoi(res.Header.Get("Retry-After"))
		if err != nil {
			wait = 1
		}
		if wait > longestWait { // test mode's daily cap, until midnight
			return res, nil
		}
		res.Body.Close()
		jitter := time.Duration(rand.Intn(500)) * time.Millisecond
		time.Sleep(time.Duration(wait)*time.Second + jitter)
	}
}

func main() {
	res, err := send("/payment_intents", map[string]any{
		"amount":               25000,
		"currency":             "mad",
		"payment_method_types": []string{"card"},
	}, "order-1046-create", 5)
	if err != nil {
		log.Fatal(err)
	}
	defer res.Body.Close()
	fmt.Println(res.StatusCode, res.Header.Get("RateLimit-Remaining"))
}
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.concurrent.ThreadLocalRandom;

public class Retry429 {
    static final String API = "https://pay-api.adpromax.com/v1";
    static final HttpClient HTTP = HttpClient.newHttpClient();
    static final long LONGEST_WAIT = 60; // seconds

    // Sends a request again after a 429, waiting as long as Retry-After says.
    static HttpResponse<String> send(
            String path, String body, String key, int attempts) throws Exception {
        HttpRequest request = HttpRequest.newBuilder(URI.create(API + path))
            .header("Authorization", "Bearer " + System.getenv("APX_SECRET_KEY"))
            .header("Content-Type", "application/json")
            // The same key on every try: the payment is created once.
            .header("Idempotency-Key", key)
            .POST(HttpRequest.BodyPublishers.ofString(body))
            .build();
        for (int attempt = 1; ; attempt++) {
            HttpResponse<String> response =
                HTTP.send(request, HttpResponse.BodyHandlers.ofString());
            if (response.statusCode() != 429 || attempt == attempts) {
                return response;
            }
            long wait = response.headers()
                .firstValueAsLong("Retry-After").orElse(1);
            if (wait > LONGEST_WAIT) { // test mode's daily cap, until midnight
                return response;
            }
            long jitter = ThreadLocalRandom.current().nextLong(500);
            Thread.sleep(wait * 1000 + jitter);
        }
    }

    public static void main(String[] args) throws Exception {
        HttpResponse<String> response = send("/payment_intents", """
            {
              "amount": 25000,
              "currency": "mad",
              "payment_method_types": ["card"]
            }
            """, "order-1046-create", 5);
        System.out.println(response.statusCode() + " "
            + response.headers().firstValue("RateLimit-Remaining").orElse(""));
    }
}
using System.Net;
using System.Net.Http.Headers;
using System.Net.Http.Json;

const string Api = "https://pay-api.adpromax.com/v1";
var longestWait = TimeSpan.FromSeconds(60);
using var http = new HttpClient();
http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
    "Bearer", Environment.GetEnvironmentVariable("APX_SECRET_KEY"));

// Sends a request again after a 429, waiting as long as Retry-After says.
async Task<HttpResponseMessage> Send(
    string path, object body, string key, int attempts = 5)
{
    for (var attempt = 1; ; attempt++)
    {
        using var request = new HttpRequestMessage(HttpMethod.Post, Api + path);
        // The same key on every try: the payment is created once.
        request.Headers.Add("Idempotency-Key", key);
        request.Content = JsonContent.Create(body);

        var answer = await http.SendAsync(request);
        var limited = answer.StatusCode == HttpStatusCode.TooManyRequests;
        if (!limited || attempt == attempts)
            return answer;
        var wait = answer.Headers.RetryAfter?.Delta
            ?? TimeSpan.FromSeconds(1);
        if (wait > longestWait) // test mode's daily cap, until midnight
            return answer;
        var jitter = TimeSpan.FromMilliseconds(Random.Shared.Next(500));
        await Task.Delay(wait + jitter);
    }
}

using var response = await Send("/payment_intents", new
{
    amount = 25000,
    currency = "mad",
    payment_method_types = new[] { "card" },
}, "order-1046-create");
response.Headers.TryGetValues("RateLimit-Remaining", out var remaining);
Console.WriteLine($"{(int)response.StatusCode} {remaining?.FirstOrDefault()}");
// Cargo.toml: reqwest = { version = "0.12", features = ["blocking", "json"] }
//             serde_json = "1"
//             rand = "0.9"
use std::{thread::sleep, time::Duration};

use reqwest::blocking::{Client, Response};
use serde_json::{json, Value};

const API: &str = "https://pay-api.adpromax.com/v1";
const LONGEST_WAIT: u64 = 60; // seconds

// Sends a request again after a 429, waiting as long as Retry-After says.
fn send(
    path: &str,
    body: &Value,
    key: &str,
    attempts: u32,
) -> reqwest::Result<Response> {
    let client = Client::new();
    let secret = std::env::var("APX_SECRET_KEY").unwrap_or_default();
    let mut attempt = 1;
    loop {
        let response = client
            .post(format!("{API}{path}"))
            .bearer_auth(&secret)
            // The same key on every try: the payment is created once.
            .header("Idempotency-Key", key)
            .json(body)
            .send()?;
        if response.status().as_u16() != 429 || attempt == attempts {
            return Ok(response);
        }
        let wait: u64 = response
            .headers()
            .get("Retry-After")
            .and_then(|value| value.to_str().ok()?.parse().ok())
            .unwrap_or(1);
        if wait > LONGEST_WAIT {
            // test mode's daily cap, until midnight
            return Ok(response);
        }
        let jitter: u64 = rand::random_range(0..500);
        sleep(Duration::from_millis(wait * 1000 + jitter));
        attempt += 1;
    }
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let response = send(
        "/payment_intents",
        &json!({
            "amount": 25000,
            "currency": "mad",
            "payment_method_types": ["card"]
        }),
        "order-1046-create",
        5,
    )?;
    let remaining = response
        .headers()
        .get("RateLimit-Remaining")
        .and_then(|value| value.to_str().ok())
        .unwrap_or("");
    println!("{} {}", response.status().as_u16(), remaining);
    Ok(())
}

Voir vos limites

Développeurs → Limites de l’API, sur le tableau de bord, montre pour chaque compartiment :

  • sa limite par minute et sa réserve ;
  • d’où vient la limite : Valeur globale par défaut, Offre, ou Personnalisée jusqu’au … quand nous en avons fixé une pour vous ;
  • votre pic d’utilisation et le nombre de réponses 429 sur les dernières 24 heures.

En mode test, la page montre aussi la part du plafond quotidien que vous avez utilisée.

Demander une limite plus haute

Quand votre trafic grandit, ou avant une campagne, demandez davantage dans Développeurs → Limites de l’API → Demander une limite plus haute :

  1. le compartiment, et le mode : production, test ou les deux ;
  2. la valeur dont vous avez besoin : deux ou cinq fois la limite actuelle, ou votre propre chiffre ;
  3. jusqu’à quand : une date, ou sans fin ;
  4. la raison, par exemple « Campagne du Ramadan, environ 3 fois le trafic habituel » ;
  5. une adresse e-mail pour notre réponse.

Notre équipe examine votre utilisation et votre compte, puis approuve ou refuse. Une forte hausse demande l’approbation d’une deuxième personne de notre côté, ce qui peut prendre un peu plus de temps. La décision arrive par e-mail et s’applique en quelques secondes ; la source du compartiment devient alors Personnalisée. Vous pouvez avoir une demande ouverte par compartiment et par mode, et l’annuler tant qu’elle est ouverte.

Le Propriétaire, les Administrateurs et les Développeurs de votre organisation peuvent demander. Faites-le quelques jours avant une campagne plutôt que le jour même.

Nous pouvons aussi abaisser une limite quand une intégration nuit à la plateforme. Nous vous le disons alors par e-mail.

Autres limites

Elles protègent la plateforme et ne changent pas d’une organisation à l’autre :

  • la connexion, l’inscription, la page de paiement hébergée et la page du bon sont limitées par visiteur ;
  • une organisation garde au plus 16 endpoints de webhook par mode ;
  • au plus cinq envois de webhook sont en cours vers un même endpoint à la fois ;
  • une liste renvoie au plus 100 objets par requête (limit) ;
  • une Idempotency-Key fait au plus 255 caractères.