> ## Documentation Index
> Fetch the complete documentation index at: https://developers.readyforcommerce.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Rate limits

> How many calls a key and an organization may make, what happens past them, and how to retry a write safely.

Each API key, each AI connection and each organization has a budget of calls a minute, by the kind of call:

| Calls a minute | Per API key | Per AI connection | Per organization |
| - | - | - | - |
| **Read:** a `GET` | 1200 | 300, reads and writes together | 2400 |
| **Write:** any other method | 600 | shared with reads | 1200 |
| **Bulk:** an operation that changes many records in one call | 60 | 60 | 120 |

* **A call spends from its key's budget, then from its organization's.** The organization's budget is shared by all its
  keys and [AI connections](https://developers.readyforcommerce.com/connect-an-ai.md), so one busy program can slow the others: give each program its own key
  and spread its calls.
* **People in the app spend none of it.** A person's work in the app never takes from these budgets, so a program
  that spends them all never locks your team out.
* **Budgets refill continuously,** not at the top of each minute, and hold only a few seconds' worth of calls at once:
  a steady pace gets more done than bursts.

## Know what is left

[Get the calling credential](https://developers.readyforcommerce.com/api-reference/core-get-credential.md) answers your key's `limits` and its `remaining`
calls of each kind, the lower of what its own budget and its organization's still hold. It counts against no limit
and takes no place among the calls in flight, so you may call it whenever you need to.

## Past a limit

A call past a budget answers `429` with the code `core.rate_limited` and a `Retry-After` header, the whole seconds to
wait. It did nothing: wait that long and send it again, never in a tight loop.

```bash curl theme={null}
# --retry follows Retry-After on a 429 or a 503
curl --retry 3 "https://app.readyforcommerce.com/api/v1/orgs/$RFC_ORG_ID/core/org" \
  --header "Authorization: Bearer $RFC_API_KEY"
```

```python Python theme={null}
import os
import time

import requests

url = f"https://app.readyforcommerce.com/api/v1/orgs/{os.environ['RFC_ORG_ID']}/core/org"
headers = {"Authorization": f"Bearer {os.environ['RFC_API_KEY']}"}
for _ in range(3):
    response = requests.get(url, headers=headers)
    if response.status_code not in (429, 503):
        break
    time.sleep(int(response.headers["Retry-After"]))
print(response.json())
```

```php PHP theme={null}
<?php
$url = 'https://app.readyforcommerce.com/api/v1/orgs/' . getenv('RFC_ORG_ID') . '/core/org';
for ($attempt = 0; $attempt < 3; $attempt++) {
    $wait = 0;
    $curl = curl_init($url);
    curl_setopt_array($curl, [
        CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('RFC_API_KEY')],
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_HEADERFUNCTION => function ($curl, $line) use (&$wait) {
            if (stripos($line, 'Retry-After:') === 0) {
                $wait = (int) trim(substr($line, 12));
            }
            return strlen($line);
        },
    ]);
    $body = curl_exec($curl);
    $status = curl_getinfo($curl, CURLINFO_RESPONSE_CODE);
    if ($status !== 429 && $status !== 503) {
        break;
    }
    sleep($wait);
}
echo $body;
```

```javascript JavaScript theme={null}
const url = `https://app.readyforcommerce.com/api/v1/orgs/${process.env.RFC_ORG_ID}/core/org`;
let response;
for (let attempt = 0; attempt < 3; attempt++) {
  response = await fetch(url, { headers: { Authorization: `Bearer ${process.env.RFC_API_KEY}` } });
  if (response.status !== 429 && response.status !== 503) break;
  const seconds = Number(response.headers.get('Retry-After'));
  await new Promise((resolve) => setTimeout(resolve, seconds * 1000));
}
console.log(await response.json());
```

## Calls in flight

Keep at most 5 calls of your organization's keys and AI connections running at once. A call
beyond them may answer `429 core.rate_limited` before it does anything, with its own `Retry-After`. To read faster,
ask for bigger [pages](https://developers.readyforcommerce.com/pagination.md) rather than more calls at once.

## Unfinished jobs

Long work, such as an import or an export, runs as a job: its call answers `202 Accepted` with a `jobId`, which
[Get a job](https://developers.readyforcommerce.com/api-reference/core-get-job.md) follows until it ends. An organization holds at most 50 unfinished
jobs, whoever started them: waiting, paused or running. A key's or an AI's start beyond them answers `429` with the
code `core.job_limit_reached` and a `Retry-After` of 60 seconds, and starts nothing. A person's start in
the app is never refused.

A job's file, such as an export's, is read through a link from
[Get a job's file link](https://developers.readyforcommerce.com/api-reference/core-get-job-file.md), which works for 5 minutes: ask for a
new one when it expires.

## Retry a write safely

A write whose answer never arrived, because the connection dropped or timed out, may or may not have happened. Send
it with an `Idempotency-Key` header, a value of your own of at most 255 characters, such as a
UUID made for that one write, and send the same key on every retry of it:

```bash curl theme={null}
IDEMPOTENCY_KEY=$(uuidgen) # one per write; the same on every retry

curl --request POST \
  "https://app.readyforcommerce.com/api/v1/orgs/$RFC_ORG_ID/core/jobs/$JOB_ID/cancel" \
  --header "Authorization: Bearer $RFC_API_KEY" \
  --header "Idempotency-Key: $IDEMPOTENCY_KEY"
```

```python Python theme={null}
import os
import uuid

import requests

idempotency_key = str(uuid.uuid4())  # one per write; the same on every retry
response = requests.post(
    f"https://app.readyforcommerce.com/api/v1/orgs/{os.environ['RFC_ORG_ID']}/core/jobs/{os.environ['JOB_ID']}/cancel",
    headers={
        "Authorization": f"Bearer {os.environ['RFC_API_KEY']}",
        "Idempotency-Key": idempotency_key,
    },
)
print(response.json())
```

```php PHP theme={null}
<?php
$idempotencyKey = bin2hex(random_bytes(16)); // one per write; the same on every retry
$curl = curl_init(
    'https://app.readyforcommerce.com/api/v1/orgs/' . getenv('RFC_ORG_ID') . '/core/jobs/' . getenv('JOB_ID') . '/cancel'
);
curl_setopt_array($curl, [
    CURLOPT_POST => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('RFC_API_KEY'),
        'Idempotency-Key: ' . $idempotencyKey,
    ],
    CURLOPT_RETURNTRANSFER => true,
]);
echo curl_exec($curl);
```

```javascript JavaScript theme={null}
const idempotencyKey = crypto.randomUUID(); // one per write; the same on every retry
const response = await fetch(
  `https://app.readyforcommerce.com/api/v1/orgs/${process.env.RFC_ORG_ID}/core/jobs/${process.env.JOB_ID}/cancel`,
  {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${process.env.RFC_API_KEY}`,
      'Idempotency-Key': idempotencyKey,
    },
  },
);
console.log(await response.json());
```

* **The first call does the write;** a retry with the same key and the same request answers the first call's answer,
  without doing it again, for 24 hours.
* **While the first call still runs,** a retry answers `409 core.idempotency_in_progress`: wait, then retry.
* **The same key with another request,** another body or another path, answers `422 core.idempotency_mismatch`.
* **Each value is your credential's own:** another API key's call with the same value is a call of its own.
* **A value that is empty or too long** answers `400 core.bad_request`.
* A call that failed stores nothing, so its retry runs again.

These writes honor an `Idempotency-Key`; on any other, it changes nothing: [Cancel a job](https://developers.readyforcommerce.com/api-reference/core-cancel-job.md),
[Restore a version](https://developers.readyforcommerce.com/api-reference/core-restore-version.md), [Start an import](https://developers.readyforcommerce.com/api-reference/core-start-import.md),
[Inspect an import file](https://developers.readyforcommerce.com/api-reference/core-inspect-import.md), [Check an import](https://developers.readyforcommerce.com/api-reference/core-check-import.md),
[Fetch a file from a URL](https://developers.readyforcommerce.com/api-reference/core-fetch-import-file.md),
[Write an import's results](https://developers.readyforcommerce.com/api-reference/core-start-import-results.md),
[Start an export](https://developers.readyforcommerce.com/api-reference/core-start-export.md),
[Create an export template](https://developers.readyforcommerce.com/api-reference/core-create-export-template.md),
[Create a saved view](https://developers.readyforcommerce.com/api-reference/core-create-view.md),
[Connect a store](https://developers.readyforcommerce.com/api-reference/integrations-connect-store.md),
[Create a product](https://developers.readyforcommerce.com/api-reference/catalog-create-product.md) and
[Duplicate a product](https://developers.readyforcommerce.com/api-reference/catalog-duplicate-product.md).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.