+
+
+
+
+
+
+
+
+ Thresholds per bucket ({tier} / {env})
+ {searchQuery && (
+
+ (filtered by search)
+
+ )}
+
+
+
+
+
+ |
+ Endpoint(s)
+ |
+
+ Bucket
+ |
+
+ Sustained (rpm)
+ |
+
+ Burst (rps)
+ |
+
+
+
+ {displayedThresholds.map((t) => {
+ const endpoints = bucketToEndpoints.get(t.bucket) ?? []
+ const maxEndpoints = searchQuery ? endpoints.length : 20
+ const endpointsToShow = endpoints.slice(0, maxEndpoints)
+ const endpointDisplay = endpointsToShow.length
+ ? endpointsToShow.map((e) => {
+ const isMatch = endpointMatchesSearch(e)
+ return (
+
+ {e.path}{" "}
+
+ ({e.method})
+
+
+ )
+ })
+ : null
+ const overflow =
+ endpoints.length > maxEndpoints
+ ? endpoints.length - maxEndpoints
+ : 0
+ return (
+
+ |
+ {endpointDisplay}
+ {overflow > 0 && (
+
+ +{overflow} more
+
+ )}
+ |
+
+ {t.bucket}
+ |
+
+ {t.rpm}
+ |
+
+ {t.rps}
+ |
+
+ )
+ })}
+
+
+
+ {searchQuery && displayedThresholds.length === 0 && (
+
+ No buckets match the search "{pathSearchDebounced.trim()}
+ ".
+
+ )}
+
+
+ )
+}
diff --git a/src/components/RateLimitsTable/types.ts b/src/components/RateLimitsTable/types.ts
new file mode 100644
index 0000000000..56236727b1
--- /dev/null
+++ b/src/components/RateLimitsTable/types.ts
@@ -0,0 +1,24 @@
+// Copyright © 2022 Ory Corp
+// SPDX-License-Identifier: Apache-2.0
+
+export interface EndpointRow {
+ method: string
+ path: string
+ bucket: string
+}
+
+export interface ThresholdRow {
+ bucket: string
+ tier: string
+ env: string
+ rpm: number
+ rps: number
+}
+
+export interface RateLimitsData {
+ endpoints: EndpointRow[]
+ thresholds: ThresholdRow[]
+}
+
+export type Tier = "Developer" | "Production" | "Growth" | "Enterprise"
+export type Env = "Development" | "Staging" | "Production"
diff --git a/src/lib/rate-limits/csv-provider.ts b/src/lib/rate-limits/csv-provider.ts
new file mode 100644
index 0000000000..5d951cf64f
--- /dev/null
+++ b/src/lib/rate-limits/csv-provider.ts
@@ -0,0 +1,117 @@
+// Copyright © 2022 Ory Corp
+// SPDX-License-Identifier: Apache-2.0
+
+import * as fs from "fs"
+import * as path from "path"
+import type {
+ EndpointRow,
+ Env,
+ GetThresholdsOptions,
+ RateLimitsProvider,
+ ThresholdRow,
+ Tier,
+} from "./types"
+import { ENV_FROM_CSV, TIER_FROM_CSV } from "./types"
+
+const ENDPOINTS_CSV = "bucket-to-endpoints-20260204-1941.csv"
+const THRESHOLDS_CSV = "bucket-to-threshold-20260204-1941.csv"
+
+/**
+ * Resolve data directory. When running from Docusaurus plugin, pass siteDir so paths resolve correctly.
+ */
+function getDataDir(siteDir?: string): string {
+ if (siteDir) {
+ return path.join(siteDir, "src", "lib", "rate-limits", "data")
+ }
+ return path.join(__dirname, "data")
+}
+
+function parseCsv(content: string): string[][] {
+ const lines = content.trim().split(/\r?\n/)
+ return lines.map((line) => line.split(",").map((cell) => cell.trim()))
+}
+
+/**
+ * For duplicate (bucket, tier, env) we keep the first row (CSV order).
+ */
+function dedupeThresholds(rows: ThresholdRow[]): ThresholdRow[] {
+ const key = (r: ThresholdRow) => `${r.bucket}|${r.tier}|${r.env}`
+ const seen = new Set