# Rate limits

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

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.

| Bucket             | Covers                                                      | Live, per minute | Test, per minute |
| ------------------ | ----------------------------------------------------------- | ---------------- | ---------------- |
| All requests       | Every API request                                           | 1,200            | 600              |
| Writes             | Every `POST`, `PUT` and `DELETE`                            | 300              | 150              |
| Payment creation   | `POST /v1/payment_intents` and `POST /v1/checkout/sessions` | 120              | 60               |
| Search and exports | List searches and exports                                   | 30               | 30               |

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.

```bash
curl -i https://pay-api.adpromax.com/v1/account \
  -u "$APX_SECRET_KEY:"
```

```js title="headers.mjs"
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)}`)
```

```ts title="headers.ts"
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}`)
}
```

```python title="headers.py"
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 title="headers.php"
<?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;
}
```

```ruby title="headers.rb"
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
```

```go title="headers.go"
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))
	}
}
```

```java title="Headers.java"
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(""));
        }
    }
}
```

```csharp title="Headers.cs"
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()}");
}
```

```rust title="main.rs"
// 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(())
}
```

```text 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.

```json title="429 Too Many Requests" response
{
  "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.

```json title="429 Too Many Requests" response
{
  "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](#the-daily-cap-in-test-mode), 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](https://pay-docs.adpromax.com/en/api-reference/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](https://pay-docs.adpromax.com/en/webhooks) rather than reading them again and again.

```bash
# --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"]}'
```

```js title="retry-429.mjs"
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'))
```

```ts title="retry-429.ts"
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'))
```

```python title="retry_429.py"
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 title="retry_429.php"
<?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;
```

```ruby title="retry_429.rb"
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']}"
```

```go title="retry_429.go"
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"))
}
```

```java title="Retry429.java"
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(""));
    }
}
```

```csharp title="Retry429.cs"
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()}");
```

```rust title="main.rs"
// 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.
