Jamf Enforces Per-User Bedrock Spend Limits in Under 15 Minutes

September 1, 2026news
Amazon Bedrock

Generative AI spend breaks every assumption traditional FinOps tooling was built on. Provisioned compute scales predictably; token consumption scales with developer behaviour. A single engineer running an agentic coding loop against a premium model can exhaust more budget in a few hours than an entire team consumes in a week. Jamf — trusted by more than 76,000 organizations to manage and secure Apple devices — hit this wall after giving its engineering organization broad access to Amazon Bedrock. Productivity climbed, but so did the need for per-user cost accountability. Their response was a production system that enforces tiered spend limits in near-real-time, without interrupting active sessions or requiring re-authentication.

The architecture demonstrates that governance is a prerequisite for scaling agentic workflows, not an obstacle to them — a pattern that transfers directly to any organization running foundation models through managed APIs.

Three-Layer Architecture: Measure, Decide, Enforce

Jamf decomposed the problem into three discrete concerns, each handled by a purpose-built serverless component.

Measure. When an engineer calls bedrock:InvokeModel through an AWS IAM Identity Center SSO session, Amazon Bedrock logs the invocation — model ID, input token count, output token count, and user identity — to a configured Amazon S3 bucket. No custom instrumentation is required at the SDK layer.

Decide and notify. An Amazon Athena view named bedrock_cost_today reads those S3 logs and computes per-user daily spend by multiplying token counts against published per-model rates, grouped by user identity and the current date. An AWS Lambda enforcement handler, triggered every 15 minutes by an Amazon EventBridge schedule, queries that view and cross-references a DynamoDB exceptions table holding any custom, time-boxed limits. When a user crosses a new spending threshold, the handler emits a one-time Slack direct message for that tier, reading the user's previous state from a DynamoDB state table to avoid repeated notifications.

Enforce. For each engineer over a threshold, Lambda publishes a new version of the appropriate Customer Managed Policy (CMP) via iam:CreatePolicyVersion. The policy targets specific users through a saml:sub condition key. Because the CMPs are attached directly to the IAM permission set, the updated policy takes effect on the engineer's next Bedrock call — no re-provisioning, no session teardown.

Tiered Thresholds and the Always-Available Model

The threshold logic encodes a deliberate product decision. At 80% of the daily budget, access to Anthropic Claude Opus is denied. At 100%, Anthropic Claude Sonnet is also denied. Anthropic Claude Haiku is never restricted — an engineer who has exhausted their budget retains a low-cost model and can continue working. Enforcement is not a hard stop; it is a cost-weighted capability tier.

Engineers who need more capacity for a large migration, a customer escalation, or model evaluation can request an elevated limit via a Slack slash command (/bedrock-limit). Admins grant time-boxed higher limits that write to the DynamoDB exceptions table with an expiry timestamp and a full audit trail. A DynamoDB Time to Live (TTL) attribute on the expiry timestamp means exceptions clean themselves up without a separate code path.

Spend Threshold Denied Model Still Available Enforcement Mechanism
80% of daily budget Anthropic Claude Opus Claude Sonnet, Claude Haiku CMP version published via iam:CreatePolicyVersion
100% of daily budget Anthropic Claude Sonnet Claude Haiku CMP version published via iam:CreatePolicyVersion
Exception granted None (elevated limit) All models DynamoDB exceptions table with TTL expiry

Operational Specifics and Control Plane Cost

The enforcement Lambda is idempotent by construction. Each run recomputes the full restricted-user list from cumulative spend for the current day rather than applying incremental changes. A missed run or duplicate execution produces the same result once it catches up. The daily reset is implicit: the Athena view scopes spend to a rolling window anchored at 00:00 in the configured reference time zone. When the window rolls over, the next run drops users who are no longer over threshold, and the CMP restriction lifts automatically on the following iam:CreatePolicyVersion call.

Running this system across hundreds of engineers, the combined cost of Lambda, DynamoDB, and S3 stayed well under $10 per month. Athena is the one line item requiring active management. JSON logs force Athena to deserialize every row before applying any filter — column pruning and predicate pushdown provide no relief. Jamf observed that four differently filtered queries against the same view each scanned approximately 11 GB. The fix is to consolidate derived queries into a single SELECT ... GROUP BY and split results in application code, or convert logs to a columnar format such as Parquet.

Two hard constraints require attention. First, a managed policy retains a maximum of five versions; the enforcement Lambda must delete the oldest non-default version before calling iam:CreatePolicyVersion, or the call fails. Second, Athena's asynchronous query model means Lambda must submit a query, poll for completion, and then read results — the function timeout must be set accordingly. An unrecognized model in the pricing map is deliberately priced at the highest tier as a fail-safe, not at zero, so a newly enabled model cannot silently bypass enforcement.

The code is available at https://github.com/aws-samples/sample-bedrock-spend-enforcement.

The counterintuitive outcome Jamf documented: installing hard per-user caps made leadership comfortable expanding AI access rather than contracting it. Spend became observable, which made the ROI case tractable. Organizations that can answer the tokenomics question at the individual contributor level are the ones that will turn broad model access into durable productivity gains.

Related Reading