---
title: "Understand quota charging and refusals"
description: "Identify who pays for a job and what happens when its quota is reached."
canonical_url: "https://docs.oz0.ai/spend/budgets-and-quotas/charging"
---
# Understand quota charging and refusals

> Identify who pays for a job and what happens when its quota is reached.

For: tenant administrators and cost owners.

Identify who pays for a job and what happens when its quota is reached.

## Who a job is charged to

A job is charged to whoever started it and to one of their teams: the one they name, or else their team, the first by name when they have several.

```bash [Terminal]
oz0 run acme-reviewer --team ml "Review PR 42 in acme/api"
```

The runtime API takes the same as `team` in `StartJob`. A person can only charge a team they are in; the tenant's admins can charge any team, by their own session (an operator's certificate can too). A person acting through an app holds what both they and the app hold, and charges only the teams they are in, whatever their role; an API key never charges a team it is not in.

A start again under a job ID with the same request is a retry, while the job runs and after it ended: it answers with that job, is charged to nothing and counts nothing. Another start under the ID is refused before it is counted (see [job IDs and retries](https://docs.oz0.ai/build/apps/runtime-api#job-ids-and-retries)).

A flow's [webhook](https://docs.oz0.ai/build/parts/flows#start-one-from-a-webhook) starts its jobs as `service:webhook/<flow>`. Give it a quota to limit what callers from outside may start, and add it to a team to charge the team. A delivery over its quota gets `429 Too Many Requests`; a retried delivery of a job that ran is answered as before.

## When a quota is reached

A job that would pass a quota of jobs at once or a day does not start. The runtime API answers `resource_exhausted`, and says why and how to ask for more; the refusal is written to the [audit trail](https://docs.oz0.ai/govern/audit-trail) as `run.refused`, as is one at a webhook or a routine's fire:

```text
team ml runs 5 of the 5 jobs at once its quota allows; wait for one to end, or ask for more on the Approvals
page of the web UI, or with: oz0 quota request team:ml --running 1 --reason '...'
```

Every such refusal also carries its numbers for code to read: the quota's scope, window, when it resets, its limit and use, and who can raise it, in an `oz0` object beside the message (a detail of the `resource_exhausted` error, the webhook's JSON body, the gateway's 402); a quota that resets within a day adds `Retry-After`. A caller who may not read the quota (another person's, or a team's that [works-council mode](https://docs.oz0.ai/govern/works-council-mode) keeps to itself) gets neither its name nor its figures, in the object or in the message. The fields are listed in [Spend refusals](https://docs.oz0.ai/reference/spend-refusals).

Starts at the same time cannot both take a quota's last place, on one edge or on several: each start is counted in the same database step that checks the count, so a quota of 20 jobs a day admits 20 however the starts are spread over the edges. A job that ended in the last moments before a start may still count as running for it, which refuses that start a little early rather than let one through.

Each call is charged to its owner's and team's quota once, by the call's ID, whichever edge or try writes the charge: a write whose answer was lost and is tried again on another edge, a write an edge kept on its disk and replays after a restart, and a call the edge's clean-up pass settles are charged once and not again.

Money is checked when a job starts and again before every model call. A job that is refused at its start is not started, and does not wait: ask for more and start it again. Once the month's money is spent while a job runs, the gateway refuses its next model call with `402 oz0_quota_exceeded`, and the job waits for a budget decision instead of failing:

1. Within a few seconds the edge opens a budget request for each spent quota, the owner's or the team's, asked for by the job's owner (so the owner cannot decide it), or links one that is already open. It asks for what was spent beyond the quota and half the quota again, in whole cents, until the end of the month. The audit log records it as `budget_request.create` by `system:budget-waits`.
2. The job shows the wait: `job get`, `job why` and the job's page in the console name the request, and the Approvals page and the sidebar's count show it to those who may decide it.
3. Once a request is approved, or the quota has room again for any reason (raised with `quota set`, or a new month), the job sends the refused call again and goes on where it was. Every job of the subject that waits goes on with the same approval.
4. If the request is denied or cancelled, or nothing is decided within 24 hours (or before the agent's `limits.timeout` runs out, when that is sooner; the job's page shows the deadline), the job fails with `QuotaExceeded`:

```text
$ oz0 job get job-c
Status:  running
Waiting: a budget decision, since 2026-10-06 11:08: user:lee has spent $0.0004 of its $0.0001 quota for October 2026; ...
         budget request br-01a11079-0607-... (oz0 quota approve br-01a11079-0607-...)

QuotaExceeded: operator:admin denied the budget request: not this month (br-01a11079-1f7a-...); user:kai has spent
$0.0004 of its $0.0001 quota for October 2026; ask for more on the Approvals page of the web UI, or with: ...
```

A request the edge opened for a job that ended before anyone decided is cancelled (`budget_request.cancel` by `system:budget-waits`), unless another waiting job waits for it, so that nobody approves budget for a job that is gone. The edge opens at most one pending request per quota and job; people may still ask more than once.

An agent's child jobs wait the same way at their own model calls, and a flow waits while its agent steps do. Before a job starts a child job (an agent's `oz.delegate`, a flow's agent step), it also checks the quotas the child would be charged to, its owner's and team's: when one is spent, the job waits for a budget decision in the same way, before the child exists, and starts it once the quota has room; several children started at once wait one after another, each asking again when the wait before it ends.

Child jobs do not count toward the quotas on jobs at once or a day: those count the jobs people start.

A [harness agent's](https://docs.oz0.ai/build/parts/harnesses) session waits too. The node's LLM proxy sees the quota refuse one of the session's model calls; the session's turn ends (an ACP agent that does not end it within 10 seconds is cancelled), the job waits for the budget decision as above, and then the session goes on: an ACP agent gets the next prompt in the same session, and a JSON-lines harness such as the Claude Agent SDK is resumed from its session ID, in the same working directory.

The session's process stays on its node while it waits, so the wait ends with the session's own time limit (the agent's `limits.timeout`, or two hours). A harness that gives no session ID with its failed result cannot be resumed, and fails as before.

A spent tenant [budget](https://docs.oz0.ai/build/parts/contracts-and-limits/budgets#budgets) still fails the job with `BudgetExceeded`. A spent job tree's budget does too, unless the agent's `on_budget` makes the job wait: then the edge opens a request about the tree (kind `job`, subject `job:<root>`) that the tree's owner or the tenant's admins decide, and before the wall a forecast may ask too (kind `forecast`); see [Budgets that wait](https://docs.oz0.ai/build/parts/contracts-and-limits/budgets#budgets-that-wait). `quota requests` shows each request's kind.

Each model call reserves its worst case against the quotas of its job's owner and team before it is sent, as it does against [budgets](https://docs.oz0.ai/build/parts/contracts-and-limits/budgets#each-call-reserves-its-worst-case), in one step on counters every edge sees: parallel calls, on one edge or several, cannot all pass a quota that only one of them fits, and a quota passes its limit by at most one call's reservation (the last call, which starts only when nothing else of the quota runs).

A call whose outcome is unknown (a timeout, a cut stream, a lost node) is charged at least its reservation, to the quota too. A changed quota or an approved request bites within five seconds on every edge.

### Budgets and tool calls

No budget or quota refuses a single tool call: they meter model calls. At a job tree's soft wall, or once a person denied a forecast's early request, every tool of the agent is withdrawn and it can only answer with what it has. A flow tool's call starts a child job, whose start meets the starter's quota and the tree's budget at the edge, as a delegation's does. A tool's own model calls (MCP sampling, or a call through the node's gateway path) are budgeted at the gateway as the job's. See [how a tool call is decided](https://docs.oz0.ai/concepts/how-a-tool-call-is-decided#budgets-and-tool-calls).

## Repeated reports on separate edges

A repeated plugin usage report or local-model report can temporarily inflate a budget counter when management and edge run separately. This affects budgets on which the call reserved nothing. A busy budget can refuse later calls early until no call of that budget is open and the counter is reset to the sum of recorded usage.

Usage rows and quota charges are deduplicated by call ID. A combined server also deduplicates this addition to budget counters. The separate-edge counter limitation remains open; use recorded usage when investigating an early refusal.

After an unclean stop, also check the [quota accounting limitation](https://docs.oz0.ai/concepts/security/assurance#quota-accounting-after-a-crash).


## Sitemap

See the full [sitemap](https://docs.oz0.ai/sitemap.md) for all pages.
