---
title: "Create a new budget"
method: POST
path: "/v2/budgets"
tags: ["Budgets"]
---

# Create a new budget

`POST /v2/budgets`

Creates a new budget in the workspace. Exactly one scope variant must be set (workspace / project / identity / api_key / provider / model). At least one of `limits.amount`, `limits.token_limit`, or `rate_limit.requests_per_minute` MUST be provided. Uniqueness is enforced across (workspace_id, scope_kind, scope_target_id).

## Request body

- CreateBudgetRequest
  - `scope` BudgetScope — BudgetScope is a closed oneof. Exactly one variant must be set. The six variants are ordered by enforcement precedence (most specific to most general) and mirror the BudgetScopeKind filter enum.
    - `workspace` WorkspaceBudgetScope — Workspace-wide ceiling. The implicit target is the caller's workspace.
    - `project` ProjectBudgetScope — Per-project cap.
      - `project_id` string
    - `identity` IdentityBudgetScope — Per-identity cap. Keyed by the contact's external_id (not the internal Mongo `_id`) so the scope is stable across imports.
      - `identity_external_id` string
    - `api_key` ApiKeyBudgetScope — Per-api-key cap. Replaces the legacy embedded `constraints.budget` on auth.apiKeys.
      - `api_key_id` string
    - `provider` ProviderBudgetScope — Per-provider cap. The value is the provider enum string (e.g. "openai", "anthropic") drawn from ModelIntegrationIdentifier.
      - `provider` string
    - `model` ModelBudgetScope — Per-model cap. The value is the FULL model reference as callers send it ("openai/gpt-4o", or "workspaceKey@openai/gpt-4o" for private models) — NOT the Mongo `_id` of the model master-data document.
      - `model_id` string, required
  - `match` BudgetMatch — BudgetMatch carries the CEL expression that decides whether a budget applies to a request. Available variables: `model`, `provider`, `model_id`, `api_key`, `identity`, `project`, `metadata` (map), `headers` (map, lowercase keys). An empty expression always matches. Expressions are syntax-validated at write time.
    - `cel` string
  - `limits` BudgetLimits — BudgetLimits is the per-period spend and token ceiling. At least one of `amount`, `token_limit`, or RateLimit.requests_per_minute MUST be set on a Budget; that invariant is enforced by the handler.
    - `period` 'BUDGET_PERIOD_UNSPECIFIED' | 'BUDGET_PERIOD_DAILY' | 'BUDGET_PERIOD_WEEKLY' | 'BUDGET_PERIOD_MONTHLY' | 'BUDGET_PERIOD_YEARLY' | 'BUDGET_PERIOD_ONE_TIME'
    - `amount` number, double
    - `token_limit` number, double — Token ceiling. Carried as a double so it serializes as a JSON number (proto int64 would serialize as a quoted string); token counts are whole and well within double's exact-integer range (2^53). Stored as an integer server-side.
  - `rate_limit` RateLimit — RateLimit is the per-minute request ceiling. Enforced via atomic increment-first semantics in the enforcement middleware.
    - `requests_per_minute` integer
  - `is_active` boolean — Whether the budget should be active immediately. Defaults to true when omitted (handler enforces).
  - `expires_at` string, date-time — Optional expiration. When set in combination with is_active=true, the value MUST be in the future; the handler rejects past values.
  - `alerts` BudgetAlert[] — Optional threshold notifications. Ids are assigned by the server, so `alerts[].id` must be omitted here; supplying one is rejected.
    - `id` string — Assigned by ORQ. Supply an existing id to edit that alert in place.
    - `threshold_percent` integer, required — Percentage of the dimension's limit at which the alert fires, 1–100.
    - `notifier_ids` string[], required — Must be workspace-scoped; project-scoped notifiers are rejected.
    - `dimension` 'BUDGET_ALERT_DIMENSION_UNSPECIFIED' | 'BUDGET_ALERT_DIMENSION_COST' | 'BUDGET_ALERT_DIMENSION_TOKENS'

## Response `200`

OK

- CreateBudgetResponse
  - `budget` BudgetRestResponse, required — Budget is the canonical record stored in MongoDB `budgets.entities`. It replaces the embedded `constraints.budget` on api-keys and the legacy CONTACT-only `budgets.configs` collection (see ADR 0007).
    - `budgetId` string, required
    - `scope` BudgetScopeRestResponse — BudgetScope is a closed oneof. Exactly one variant must be set. The six variants are ordered by enforcement precedence (most specific to most general) and mirror the BudgetScopeKind filter enum.
      - `workspace` WorkspaceBudgetScope — Workspace-wide ceiling. The implicit target is the caller's workspace.
      - `project` ProjectBudgetScopeRestResponse — Per-project cap.
        - `projectId` string
      - `identity` IdentityBudgetScopeRestResponse — Per-identity cap. Keyed by the contact's external_id (not the internal Mongo `_id`) so the scope is stable across imports.
        - `identityExternalId` string
      - `apiKey` ApiKeyBudgetScopeRestResponse — Per-api-key cap. Replaces the legacy embedded `constraints.budget` on auth.apiKeys.
        - `apiKeyId` string
      - `provider` ProviderBudgetScope — Per-provider cap. The value is the provider enum string (e.g. "openai", "anthropic") drawn from ModelIntegrationIdentifier.
        - `provider` string
      - `model` ModelBudgetScopeRestResponse — Per-model cap. The value is the FULL model reference as callers send it ("openai/gpt-4o", or "workspaceKey@openai/gpt-4o" for private models) — NOT the Mongo `_id` of the model master-data document.
        - `modelId` string, required
    - `match` BudgetMatch — BudgetMatch carries the CEL expression that decides whether a budget applies to a request. Available variables: `model`, `provider`, `model_id`, `api_key`, `identity`, `project`, `metadata` (map), `headers` (map, lowercase keys). An empty expression always matches. Expressions are syntax-validated at write time.
      - `cel` string
    - `limits` BudgetLimitsRestResponse, required — BudgetLimits is the per-period spend and token ceiling. At least one of `amount`, `token_limit`, or RateLimit.requests_per_minute MUST be set on a Budget; that invariant is enforced by the handler.
      - `period` 'BUDGET_PERIOD_UNSPECIFIED' | 'BUDGET_PERIOD_DAILY' | 'BUDGET_PERIOD_WEEKLY' | 'BUDGET_PERIOD_MONTHLY' | 'BUDGET_PERIOD_YEARLY' | 'BUDGET_PERIOD_ONE_TIME'
      - `amount` number, double
      - `tokenLimit` number, double — Token ceiling. Carried as a double so it serializes as a JSON number (proto int64 would serialize as a quoted string); token counts are whole and well within double's exact-integer range (2^53). Stored as an integer server-side.
    - `rateLimit` RateLimitRestResponse — RateLimit is the per-minute request ceiling. Enforced via atomic increment-first semantics in the enforcement middleware.
      - `requestsPerMinute` integer
    - `isActive` boolean
    - `expiresAt` string, date-time
    - `createdAt` string, date-time, required
    - `updatedAt` string, date-time, required
    - `usage` BudgetUsage — BudgetUsage is the current-period consumption of a budget, sourced from the live Redis counters (not the exact ledger). Each dimension is the consumed side of the matching limit dimension: `amount` is the accumulated cost in USD (vs limits.amount), `tokens` is the accumulated token count (vs limits.token_limit), and `requests` is the count in the rolling 60-second window (vs rate_limit.requests_per_minute). All three are explicit-presence so the triple is always emitted in full, zeros included — a never-spent budget serializes {amount:0, tokens:0, requests:0} rather than dropping its zero dimensions.
      - `amount` number, double
      - `tokens` number, double — Carried as a double (not int64) so it serializes as a JSON number rather than a quoted string, matching limits.token_limit.
      - `requests` integer
    - `alerts` BudgetAlertRestResponse[] — Threshold notifications. Absent when the budget has none.
      - `id` string — Assigned by ORQ. Supply an existing id to edit that alert in place.
      - `thresholdPercent` integer, required — Percentage of the dimension's limit at which the alert fires, 1–100.
      - `notifierIds` string[], required — Must be workspace-scoped; project-scoped notifiers are rejected.
      - `dimension` 'BUDGET_ALERT_DIMENSION_UNSPECIFIED' | 'BUDGET_ALERT_DIMENSION_COST' | 'BUDGET_ALERT_DIMENSION_TOKENS'

---

[API](https://skmtc.net/orq-ai/apis/orq-ai-api.md) · [All operations](https://skmtc.net/orq-ai/apis/orq-ai-api/llms.txt) · [OpenAPI document](https://skmtc-service-staging.skmtc.workers.dev/v1/apis/orq-ai/orq-ai-api/versions/1072e5ba28ab/schema)
