diff --git a/.claude/rules/app-classes.md b/.claude/rules/app-classes.md
index 803bdcfc..b9375bea 100644
--- a/.claude/rules/app-classes.md
+++ b/.claude/rules/app-classes.md
@@ -103,6 +103,7 @@ Env-scoped read-path controllers (`AlertController`, `AlertRuleController`, `Ale
- `SensitiveKeysAdminController` — GET/PUT `/api/v1/admin/sensitive-keys`. GET returns 200 or 204 if not configured. PUT accepts `{ keys: [...] }` with optional `?pushToAgents=true`. Fan-out iterates every distinct `(application, environment)` slice — intentional global baseline + per-env overrides.
- `ClaimMappingAdminController` — CRUD `/api/v1/admin/claim-mappings`, POST `/test`.
- `LicenseAdminController` — GET/POST `/api/v1/admin/license`.
+- `LicenseUsageController` — GET `/api/v1/admin/license/usage`. Returns license `state`, `expiresAt`/`daysRemaining`/`gracePeriodDays`/`tenantId`/`label`/`lastValidatedAt`, the `LicenseMessageRenderer.forState(...)` message, and a `limits[]` array (`{key, current, cap, source}`) covering every effective-limits key. `source` is `"license"` when the cap came from the license override map, `"default"` otherwise. `max_agents` reads from `AgentRegistryService.liveCount()`; all other counts come from `LicenseUsageReader.snapshot()`.
- `ThresholdAdminController` — CRUD `/api/v1/admin/thresholds`.
- `AuditLogController` — GET `/api/v1/admin/audit`.
- `RbacStatsController` — GET `/api/v1/admin/rbac/stats`.
diff --git a/cameleer-server-app/src/main/java/com/cameleer/server/app/controller/LicenseUsageController.java b/cameleer-server-app/src/main/java/com/cameleer/server/app/controller/LicenseUsageController.java
new file mode 100644
index 00000000..79483aee
--- /dev/null
+++ b/cameleer-server-app/src/main/java/com/cameleer/server/app/controller/LicenseUsageController.java
@@ -0,0 +1,97 @@
+package com.cameleer.server.app.controller;
+
+import com.cameleer.server.app.license.LicenseMessageRenderer;
+import com.cameleer.server.app.license.LicenseRepository;
+import com.cameleer.server.app.license.LicenseService;
+import com.cameleer.server.app.license.LicenseUsageReader;
+import com.cameleer.server.core.agent.AgentRegistryService;
+import com.cameleer.server.core.license.LicenseGate;
+import org.springframework.http.ResponseEntity;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.time.Duration;
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Read-only operator surface returning current license state, key timestamps, the
+ * human-readable message produced by {@link LicenseMessageRenderer}, and a per-limit
+ * usage/cap/source table covering every key exposed by the effective limits map.
+ *
+ *
Each limit row carries:
+ *
+ * - {@code key} — the limit key (e.g. {@code max_apps})
+ * - {@code current} — current usage (0 when not measured server-side)
+ * - {@code cap} — effective cap (license override or default-tier value)
+ * - {@code source} — {@code "license"} when the cap came from the license override map,
+ * {@code "default"} otherwise
+ *
+ *
+ * {@code max_agents} is sourced from the in-memory {@link AgentRegistryService} since the
+ * registry is not persisted; all other counts come from PostgreSQL via
+ * {@link LicenseUsageReader#snapshot()}.
+ */
+@RestController
+@RequestMapping("/api/v1/admin/license/usage")
+@PreAuthorize("hasRole('ADMIN')")
+public class LicenseUsageController {
+
+ private final LicenseGate gate;
+ private final LicenseUsageReader reader;
+ private final AgentRegistryService agents;
+ private final LicenseService svc;
+ private final LicenseRepository repo;
+
+ public LicenseUsageController(LicenseGate gate,
+ LicenseUsageReader reader,
+ AgentRegistryService agents,
+ LicenseService svc,
+ LicenseRepository repo) {
+ this.gate = gate;
+ this.reader = reader;
+ this.agents = agents;
+ this.svc = svc;
+ this.repo = repo;
+ }
+
+ @GetMapping
+ public ResponseEntity