Skip to content

Commit 68050ef

Browse files
authored
Merge pull request #1365 from layer5io/docs/cloud-rate-limits
docs(cloud): document API rate limits
2 parents 5dfccb5 + 3d26a6f commit 68050ef

3 files changed

Lines changed: 132 additions & 0 deletions

File tree

‎content/en/cloud/reference/_index.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ These pages state facts about Layer5 Cloud: which plan includes a capability, wh
1818
| [Sessions]({{< ref "cloud/concepts/identity-and-security/sessions.md" >}}) | Check session lifetimes, cookies, and how to sign out a session. |
1919
| [Tokens]({{< ref "cloud/concepts/identity-and-security/tokens.md" >}}) | Create, list, rotate, and revoke API tokens. |
2020
| [Cloud API]({{< ref "cloud/reference/api-reference/_index.md" >}}) | Authenticate, set the organization, page through results, and handle errors. |
21+
| [Rate limits]({{< ref "cloud/reference/api-reference/rate-limits.md" >}}) | Check which API routes are rate limited, the limit for each route family, and how to handle HTTP 429. |
2122

2223
## Security permission keys {#permission-keys}
2324

‎content/en/cloud/reference/api-reference/_index.md‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ To create integrations, retrieve data, and automate your cloud native infrastruc
1010

1111
Every endpoint lives under `/api` on the host of your Layer5 Cloud deployment. Hosted Layer5 Cloud uses `https://cloud.layer5.io/api`. A self-hosted deployment uses `https://<your-cloud-host>/api`.
1212

13+
Some route families are rate limited per caller. See [Rate limits]({{< ref "cloud/reference/api-reference/rate-limits.md" >}}) for the limits and how to handle HTTP 429.
14+
1315
## Authenticating with the API
1416

1517
In order to authenticate to Layer5 Cloud's REST API, you need to generate and use a [security token]({{< ref "cloud/concepts/identity-and-security/tokens.md" >}}). Visit your [user account's security tokens](https://cloud.layer5.io/security/tokens) and generate a long-lived token. Security tokens remain valid until you revoke them, and you can issue as many as you need.
@@ -302,6 +304,7 @@ An error response is identified by its HTTP status code. The body takes one of t
302304
| 400 | The request is malformed or a required parameter is missing. |
303305
| 401 | The credential is missing, invalid, or expired. |
304306
| 403 | You do not hold the permission the endpoint requires in the organization of the request. |
307+
| 429 | You called a rate-limited route family faster than its limit. Wait the number of seconds in the `Retry-After` header, then retry. See [Rate limits]({{< ref "cloud/reference/api-reference/rate-limits.md" >}}). |
305308
| 500 | The server could not complete the request. |
306309

307310
Branch on the status code first, then parse the body defensively. The generated [REST API Reference]({{< ref "cloud/reference/api-reference/rest.md" >}}) documents 401 responses as plain text, but the authentication layer answers with JSON.
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
---
2+
title: Rate limits
3+
description: >
4+
Which Layer5 Cloud API routes are rate limited, the limit for each route family, and how to handle HTTP 429.
5+
weight: 10
6+
categories: [reference]
7+
tags: [extensibility]
8+
---
9+
10+
Layer5 Cloud limits how fast a caller can call some API routes. A request over the limit is refused with HTTP 429. The limits first shipped in Layer5 Cloud v1.0.334 and are unchanged through v1.0.342.
11+
12+
Normal desktop use never reaches these limits. You see a 429 only when a client calls far faster than normal use, for example in a retry loop or when it replays a large backlog at once.
13+
14+
## What is limited
15+
16+
Limits apply per caller and per route family. There is no global limit.
17+
18+
- Each route family has its own bucket for each caller. Using up one family never refuses a request in another family.
19+
- Routes outside the families in the [limits table](#limits-by-route-family) are not rate limited.
20+
- The families cover the routes the Blowhorn desktop app calls: claims, schedules, ledgers, content items, events, profiles, desktop connections, capabilities, and Blowhorn health.
21+
22+
### Who counts as the caller
23+
24+
The caller is the authenticated user. All of your installs and all of your [tokens]({{< ref "cloud/concepts/identity-and-security/tokens.md" >}}) share your buckets, because they all belong to the same user.
25+
26+
Two kinds of request are counted by client IP address instead:
27+
28+
- A request with no user, such as the capabilities handshake.
29+
- A request authenticated with the shared anonymous Meshery results token.
30+
31+
Layer5 Cloud checks the `CF-Connecting-IP` header, then `X-Real-IP`, and takes the first valid IPv4 address found across them in that order. If neither header holds a valid IPv4 address, it takes the first valid IPv6 address found. If neither header holds a valid address, it uses the connection's remote address.
32+
33+
## How the limit works
34+
35+
Each bucket is a token bucket with two numbers:
36+
37+
- **Burst** is the size of the bucket: how many requests you can make back to back, starting from a full bucket.
38+
- **Rate** is how many tokens per second refill the bucket. Each request spends one token. Once the burst is spent, traffic sustained above the rate is refused.
39+
40+
A bucket that is empty refills completely in burst ÷ rate seconds. Over any one minute, a caller can make at most burst + (60 × rate) requests in one family.
41+
42+
A bucket that is not used for 10 minutes is dropped. By then it has refilled completely, so dropping it changes no answer.
43+
44+
## Limits by route family
45+
46+
In the route column, `*` means the path and every path under it.
47+
48+
| Family | Routes | Rate | Burst |
49+
|---|---|---|---|
50+
| `claims` | `/api/claims*` | 10 per second | 150 |
51+
| `schedules` | `/api/schedules*`, `/api/workspaces/{workspaceId}/schedules*` | 20 per second | 200 |
52+
| `ledgers` | `/api/ledgers*` | 20 per second | 300 |
53+
| `content_items` | `/api/content-items*` | 20 per second | 400 |
54+
| `events_write` | `POST /api/events` (run records) | 10 per second | 300 |
55+
| `events_read` | `GET /api/events*`, `GET /api/workspaces/{workspaceId}/events` | 20 per second | 500 |
56+
| `events_manage` | `PUT` and `DELETE` on `/api/events*`: `DELETE /api/events`, `DELETE /api/events/{id}`, `PUT /api/events/status`, `PUT /api/events/{id}/status` | 5 per second | 100 |
57+
| `profiles` | `/api/profiles*`, `/api/workspaces/{workspaceId}/profiles*` | 5 per second | 100 |
58+
| `desktop_connections` | `POST /api/integrations/connections`, `POST /api/integrations/connections/{connectionId}/heartbeat`, `DELETE /api/integrations/connections/desktop/{installationId}` | 2 per second | 200 |
59+
| `capabilities` (counted by IP address) | `GET /capabilities` (deprecated), `GET /{meshery-version}/capabilities` | 10 per second | 200 |
60+
| `blowhorn_health` | `GET /api/blowhorn/health` | 1 per second | 30 |
61+
62+
Registering a connection uses the generic `POST /api/integrations/connections` route. Meshery Server connection registration therefore shares the `desktop_connections` bucket with the desktop app.
63+
64+
### Effective window
65+
66+
| Family | Refill from empty | Most requests in one minute |
67+
|---|---|---|
68+
| `claims` | 15 seconds | 750 |
69+
| `schedules` | 10 seconds | 1,400 |
70+
| `ledgers` | 15 seconds | 1,500 |
71+
| `content_items` | 20 seconds | 1,600 |
72+
| `events_write` | 30 seconds | 900 |
73+
| `events_read` | 25 seconds | 1,700 |
74+
| `events_manage` | 20 seconds | 400 |
75+
| `profiles` | 20 seconds | 400 |
76+
| `desktop_connections` | 100 seconds | 320 |
77+
| `capabilities` | 20 seconds | 800 |
78+
| `blowhorn_health` | 30 seconds | 90 |
79+
80+
### How the limits are sized
81+
82+
The limits are sized for one heavy user running the desktop app on 5 installs at once. Each family has at least 10 times the headroom over that user's normal peak. A script that sends several hundred requests in a few seconds still empties the bucket and is refused.
83+
84+
## When a request is refused
85+
86+
A refused request receives:
87+
88+
- Status `429 Too Many Requests`.
89+
- A `Retry-After` header: the whole number of seconds until your bucket holds a token again, at least 1. Every family refills at 1 token per second or faster, so with the current limits the value is `1`.
90+
- A JSON body in the MeshKit error format, with code `meshery_cloud-3629`.
91+
92+
```json
93+
{
94+
"code": "meshery_cloud-3629",
95+
"error": "Too many requests",
96+
"severity": "alert",
97+
"probableCause": [
98+
"A client is calling this route family faster than normal use, for example a retry loop or a burst of replayed requests."
99+
],
100+
"suggestedRemediation": [
101+
"Wait for the number of seconds in the Retry-After header, then retry. A client that is refused in normal use should pace its requests."
102+
]
103+
}
104+
```
105+
106+
The body does not name the route family. Identify a refusal by the code `meshery_cloud-3629` and the `Retry-After` header, and do not parse the message text.
107+
108+
### Handling a 429
109+
110+
- Treat 429 as retryable. Wait the number of seconds in `Retry-After`, then retry the request.
111+
- Retrying a Blowhorn write is safe, because the writes are idempotent: a claim replays, a ledger record is written only if absent, and a run record carries an idempotency key.
112+
- If your client is refused during normal use, pace its requests so that it stays under the rate of the family it calls.
113+
114+
### Order of checks
115+
116+
The limit is checked after authentication and before the organization of the request is resolved.
117+
118+
- A request with a missing or invalid credential receives HTTP 401 before any rate check, and does not spend a token.
119+
- A refused request does no work: no organization is resolved and the endpoint does not run.
120+
121+
## Where the counts are kept
122+
123+
- Counts are kept in memory in the Layer5 Cloud server process, separately on each pod. They are not shared between pods and are not saved.
124+
- Counts reset when the server restarts or a new version is deployed.
125+
- Hosted Layer5 Cloud runs one replica, so the per-pod count is the real limit.
126+
- The limits are fixed in the server code. They cannot be configured per organization or per deployment, and a [self-hosted deployment]({{< ref "cloud/guides/self-hosted/_index.md" >}}) has the same values.
127+
128+
For authentication, organization context, and the other error responses, see the [Cloud API]({{< ref "cloud/reference/api-reference/_index.md" >}}).

0 commit comments

Comments
 (0)