API reference

Rate limits

The request buckets of your organization, the RateLimit headers, handling a 429, and how to ask for a higher limit.

Open as Markdown
On this page
  1. The buckets
  2. Read the headers
  3. When a limit is reached
  4. The daily cap in test mode
  5. Handle a 429
  6. See your limits
  7. Request a higher limit
  8. Other limits

The API limits how many requests your organization sends per minute, so that one integration's burst never slows down the others. Limits count per organization and per mode: every key of your organization shares them, and test mode has its own. Using the dashboard does not spend them, and neither do the webhooks we send you.

The buckets

Each request counts in one or more buckets. It must find room in every bucket it belongs to; a request refused by one bucket spends nothing in the others.

BucketCoversLive, per minuteTest, per minute
All requestsEvery API request1,200600
WritesEvery POST, PUT and DELETE300150
Payment creationPOST /v1/payment_intents and POST /v1/checkout/sessions12060
Search and exportsList searches and exports3030

A bucket refills at its rate per minute, and holds 10 seconds' worth on top for short bursts, rounded to a whole request. A limit of 120 per minute refills 2 requests a second and lets up to 140 through at once.

These are the defaults; yours may differ. Developers → API limits shows the values that apply to your organization.

Read the headers

Once your key is accepted, every answer carries three headers about the tightest bucket of the request, a 429 included:

  • RateLimit-Limit: the bucket's limit per minute;
  • RateLimit-Remaining: the requests you can still send right now. A full bucket holds its burst on top of its limit, so this figure can be higher than RateLimit-Limit: up to 699 for a limit of 600;
  • RateLimit-Reset: the seconds until the bucket is full again.

A request refused before it is counted carries none of them: a key that is missing, refused or not allowed to make the request, an API version that does not exist, an address the API does not know.

Read RateLimit-Remaining to slow down before you reach the limit, rather than after. During an incident on our side the count can be less even for a moment: take the headers as a guide, and a 429 as the rule.

  • 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(())
}
Response
HTTP/1.1 200 OK
Content-Type: application/json
Request-Id: req_01k7e6a2b5c8d1f4g7h0j3k6mx
RateLimit-Limit: 600
RateLimit-Remaining: 699
RateLimit-Reset: 1

When a limit is reached

The API answers 429 with a rate_limit_error whose code is rate_limit_exceeded. The message names the bucket, its limit and how long to wait, and the Retry-After header gives that wait in seconds. Nothing was done: the request can be sent again as it is.

Response429 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"
  }
}

The daily cap in test mode

On top of its rate limits, test mode has a daily cap per organization: 10,000 requests and 1,000 payment creations a day, counted in Morocco time. Past it, the API answers 429 with the code test_daily_cap_reached until midnight, Morocco time. Live mode has no daily cap.

The cap protects the shared sandbox. An automated test suite rarely reaches it; a load test does: run load tests against your own code, not against the sandbox.

Response429 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"
  }
}

Handle a 429

  1. Wait the number of seconds in Retry-After, plus a little randomness, so that several servers do not retry at the same instant. A rate limit asks for a few seconds at most. A wait of minutes or hours is the daily cap, which lasts until midnight, Morocco time: do not sleep on it. Stop, and report the error.
  2. Send the same request again, with the same Idempotency-Key. A refusal for the rate limit is not kept with the key, so the retry runs, and the operation still happens once. See Idempotency.
  3. Give up after a few tries, and log the request_id.

To stay under the limits:

  • spread batch jobs over time rather than sending them at once;
  • watch RateLimit-Remaining;
  • learn about payments from webhooks rather than reading them again and again.
  • 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(())
}

See your limits

Developers → API limits, on the dashboard, shows for each bucket:

  • its limit per minute and its burst;
  • where the limit comes from: Global default, Plan, or Custom until … when we set one for you;
  • your peak use and the number of 429 answers over the last 24 hours.

In test mode the page also shows how much of the daily cap you have used.

Request a higher limit

When your traffic grows, or ahead of a campaign, ask for more on Developers → API limits → Request a higher limit:

  1. the bucket, and the mode: live, test or both;
  2. the value you need: twice or five times the current limit, or your own figure;
  3. until when: a date, or no end;
  4. the reason, such as "Ramadan campaign, about 3× normal traffic";
  5. an email address for our answer.

Our team looks at your usage and your account, then approves or refuses. A large raise needs a second person's approval on our side, which can take a little longer. The decision arrives by email and applies within seconds; the bucket's source then reads Custom. You can have one open request per bucket and mode, and cancel it while it is open.

The Owner, Administrators and Developers of your organization can ask. Ask a few days before a campaign rather than on the day.

We may also lower a limit when an integration harms the platform. We tell you by email when we do.

Other limits

These protect the platform and do not change per organization:

  • sign-in, sign-up, the hosted payment page and the voucher page are limited per visitor;
  • an organization holds at most 16 webhook endpoints per mode;
  • at most five webhook deliveries are in flight to one endpoint at a time;
  • a list returns at most 100 objects per request (limit);
  • an Idempotency-Key is at most 255 characters.