diff --git a/content/docs/api-reference.mdx b/content/docs/api-reference.mdx
index 244029e..e1c671b 100644
--- a/content/docs/api-reference.mdx
+++ b/content/docs/api-reference.mdx
@@ -72,7 +72,7 @@ Core workspace CRUD and lifecycle operations.
| POST | `/workspaces` | AdminAuth | Create a new workspace. Accepts `name`, `runtime`, `template`, `parent_id`, `tier`, `workspace_dir`, and other fields. Runtime is auto-detected from template config if omitted (defaults to `langgraph`). |
| GET | `/workspaces` | AdminAuth | List all workspaces with status, runtime, agent card, position, and hierarchy info. |
| GET | `/workspaces/:id` | WorkspaceAuth | Get a single workspace by ID. |
-| PATCH | `/workspaces/:id` | WorkspaceAuth | Update workspace fields. A workspace bearer token is always required — unauthenticated calls return 401. Validates field constraints: `name` ≤ 255 chars, `role` ≤ 1,000 chars, `model` and `runtime` ≤ 100 chars each; `name` and `role` must not contain newlines (`\\n`, `\\r`) or YAML-special characters (`{}[]|>*&!`). Oversized or invalid field values return 400. `:id` must be a valid UUID. |
+| PATCH | `/workspaces/:id` | WorkspaceAuth | Update workspace fields. A workspace bearer token is always required — unauthenticated calls return 401. Validates field constraints: `name` ≤ 255 chars, `role` ≤ 1,000 chars, `model` and `runtime` ≤ 100 chars each; `name` and `role` must not contain newlines (`\\n`, `\\r`) or YAML-special characters (`{}[]|>*&!`). Oversized or invalid field values return 400. `:id` must be a valid UUID. Financial fields (`budget_limit`) are not accepted here — use `PATCH /workspaces/:id/budget` (AdminAuth). |
| DELETE | `/workspaces/:id` | AdminAuth | Delete a workspace. Stops the container, revokes all auth tokens, and removes all associated data. |
### Lifecycle
@@ -83,6 +83,56 @@ Core workspace CRUD and lifecycle operations.
| POST | `/workspaces/:id/pause` | WorkspaceAuth | Stop the container and set status to `paused`. Paused workspaces skip health sweep, liveness monitor, and auto-restart. |
| POST | `/workspaces/:id/resume` | WorkspaceAuth | Re-provision a paused workspace. Status transitions to `provisioning`. |
+### Budget
+
+| Method | Path | Auth | Description |
+|--------|------|------|-------------|
+| GET | `/workspaces/:id/budget` | AdminAuth | Read a workspace's current spend and ceiling. Returns `budget_limit`, `monthly_spend`, and `budget_remaining` (all in USD cents). |
+| PATCH | `/workspaces/:id/budget` | AdminAuth | Set or clear a workspace's monthly spend ceiling. Body: `{ "budget_limit": N }` (positive integer, USD cents) or `{ "budget_limit": null }` to remove the cap. Negative values → 400. Returns same shape as GET. |
+
+**Request / response shape:**
+
+```json
+// PATCH request body
+{ "budget_limit": 500 } // $5.00/month ceiling
+{ "budget_limit": null } // no ceiling
+
+// GET and PATCH success response (200)
+{
+ "budget_limit": 500, // null when no ceiling
+ "monthly_spend": 312, // accumulated spend this period, USD cents
+ "budget_remaining": 188 // null when no ceiling; max(0, limit-spend) — can be negative
+}
+```
+
+
+ **`budget_limit` and `monthly_spend` are absent from `GET /workspaces/:id`**
+
+ Financial fields are stripped unconditionally from the workspace detail
+ response — they do not appear for any caller, authenticated or not. Always
+ use `GET /workspaces/:id/budget` (AdminAuth) to read spend data.
+
+ `budget_limit` is also **not** accepted on the general `PATCH /workspaces/:id`
+ endpoint. Use the dedicated `/budget` route.
+
+
+
+ **Enforcement and fail-open behaviour**
+
+ When `monthly_spend >= budget_limit`, `POST /workspaces/:id/a2a` returns:
+ ```
+ HTTP 402 Payment Required
+ {"error": "workspace budget limit exceeded"}
+ ```
+ Channel sends (Slack, Telegram, Discord, Lark) are also budget-gated with
+ the same 402 response. The workspace itself is **not paused** — it keeps
+ running; only inbound A2A and channel traffic is blocked.
+
+ **Fail-open:** if the budget check encounters a DB error, traffic is allowed
+ through rather than blocked. The spend ceiling is a soft guardrail, not a
+ hard guarantee.
+
+
---
## Registry
diff --git a/content/docs/concepts.mdx b/content/docs/concepts.mdx
index 22579b0..a19ea2f 100644
--- a/content/docs/concepts.mdx
+++ b/content/docs/concepts.mdx
@@ -18,6 +18,7 @@ workspace has:
- An optional **parent** (forms the org tree)
- An optional **workspace_dir** (a host path bind-mounted into the
container — gives the agent direct access to your codebase)
+- An optional **budget_limit** (workspace-level spend cap — see [Workspace budgets](#workspace-budgets) below)
Workspaces talk to each other via **A2A** (agent-to-agent) messages, routed
by the platform. Communication rules: same workspace, siblings, and
@@ -52,6 +53,75 @@ description: "Senior backend engineer focused on correctness, security, and perf
The generator is non-fatal: a missing or unreadable `config.yaml` prints a
startup warning but does not prevent the workspace from booting.
+## Workspace budgets
+
+A **budget limit** is a per-workspace monthly spend ceiling, expressed in
+**USD cents** (e.g. `500` = $5.00/month). It is set by a tenant admin — workspace
+agents cannot read or clear their own financial ceiling.
+
+### What happens at the ceiling
+
+When `monthly_spend >= budget_limit`, the platform blocks new A2A proxy calls
+to that workspace:
+
+```
+HTTP 402 Payment Required
+{"error": "workspace budget limit exceeded"}
+```
+
+The workspace itself keeps running — only inbound A2A messages and channel
+sends are gated. Raising `budget_limit` or resetting spend immediately
+restores traffic.
+
+**Fail-open behaviour:** if the platform cannot reach the budget record
+(e.g. a transient DB error), traffic is **allowed through** rather than
+blocked. The spend ceiling is a soft guardrail, not a hard guarantee.
+
+### Setting a limit (admin only)
+
+```bash
+# Set ceiling to $5.00/month
+curl -X PATCH https://api.moleculesai.app/workspaces//budget \
+ -H "Authorization: Bearer " \
+ -H "Content-Type: application/json" \
+ -d '{"budget_limit": 500}'
+
+# Remove ceiling entirely
+curl -X PATCH https://api.moleculesai.app/workspaces//budget \
+ -H "Authorization: Bearer " \
+ -H "Content-Type: application/json" \
+ -d '{"budget_limit": null}'
+```
+
+Values must be a positive integer (USD cents) or `null`. Negative values are
+rejected with `400 Bad Request`.
+
+### Monitoring spend
+
+Use the dedicated budget endpoint — financial fields are **not** included in
+the standard `GET /workspaces/:id` response:
+
+```bash
+curl https://api.moleculesai.app/workspaces//budget \
+ -H "Authorization: Bearer "
+```
+
+```json
+{
+ "budget_limit": 500,
+ "monthly_spend": 312,
+ "budget_remaining": 188
+}
+```
+
+| Field | Description |
+|-------|-------------|
+| `budget_limit` | Ceiling in USD cents, or `null` if no limit is set |
+| `monthly_spend` | Accumulated spend this billing period in USD cents |
+| `budget_remaining` | `null` if no limit; otherwise `max(0, limit − spend)`. Can read negative if spend exceeded the ceiling before enforcement kicked in. |
+
+See the [API Reference](/docs/api-reference#budget) for the full endpoint specification.
+
## External agents
An **external agent** is a workspace with `runtime: external` — it runs on