---
title: "An editor's budget status"
manual: "LLM Foundation"
version: "0.42"
permalink: "https://docs.typo3.org/permalink/netresearch/nr-llm:api-budget-status@0.42"
source: "Api/BudgetStatus.rst"
rendered: "2026-11-01T11:38:21+00:00"
---

# An editor's budget status {#api-budget-status}

`UserBudgetStatusService` reads one backend user's own usage today and
this month against the limits of their budget record, the numbers the AI
Tasks module shows ([ADR-232](https://docs.typo3.org/permalink/netresearch/nr-llm:adr-232@0.42)). It reads the same windows and
the same record that `BudgetServiceInterface::check()` compares: today
from 00:00, this month from the first at 00:00, both up to now.

```php
use Netresearch\NrLlm\Service\Budget\UserBudgetStatusService;
use Netresearch\NrLlm\Service\Formatting\LocalizedNumberFormatter;

final readonly class MyBudgetStatusReader
{
    public function __construct(
        private UserBudgetStatusService $budgetStatus,
    ) {}

    /**
     * @param int $beUserUid the logged-in backend user, never a request value
     */
    public function read(int $beUserUid): array
    {
        return $this->budgetStatus
            ->forUser($beUserUid, LocalizedNumberFormatter::forBackendUser())
            ->toArray();
    }
}
```

Obtain the service from the DI container; its constructor is not part of the
promise. Pass the uid of the logged-in backend user. A uid of 0 or below
reads no usage, because `be_user = 0` holds the usage of every caller
without a backend user (command line, scheduler).

-   **`UserBudgetStatus`**

    `$hasPersonalLimit` is true only when enforcement applies the
    record: it exists, is active and sets at least one limit above 0.
    Otherwise every limit reads 0 and the usage is still filled in.
    `$today` and `$month` are `BudgetWindowStatus` objects.

-   **`BudgetWindowStatus`**

    `$requests`, `$tokens` and `$cost`, one
    `BudgetMeter` each.

-   **`BudgetMeter`**

    `$used` and `$limit` as numbers; a limit of 0 means no limit on
    this axis (`isLimited()` is false). `getPercent()` is the share
    of the limit used, 0 to 100, rounded down. `getUsedFormatted()` and
    `getLimitFormatted()` format with the formatter passed to
    `forUser()`: cost as USD with two decimals, counts without.

`toArray()` on each of them returns the formatted values, for a JSON
answer: `hasPersonalLimit`, then `today` and `month`, each with
`requests`, `tokens` and `cost` as
`{used: string, limit: string, limited: bool, percent: int}`.

`LocalizedNumberFormatter` formats numbers and USD amounts in a locale
through ICU. `forBackendUser()` takes the locale of the current backend
user and falls back to `en-US` without one; \``new
LocalizedNumberFormatter('de-DE')`\` fixes one. Amounts stay in USD, the
currency budgets and prices are stored in.
