---
title: "ADR-231: A monthly billing report with a markup"
manual: "LLM Foundation"
version: "0.42"
permalink: "https://docs.typo3.org/permalink/netresearch/nr-llm:adr-231@0.42"
source: "Adr/Adr231AMonthlyBillingReport.rst"
rendered: "2026-11-01T11:38:21+00:00"
---

# ADR-231: A monthly billing report with a markup {#adr-231}

-   *Status:* Accepted
-   *Date:* 2026-10-28
-   *Authors:* Netresearch DTT GmbH

## Context {#adr-231-context}

An agency that runs the AI features for a customer bills them monthly. The
Analytics module ([Usage analytics](https://docs.typo3.org/permalink/netresearch/nr-llm:administration-analytics@0.42)) shows usage over a
rolling range, not a calendar month, and has no export. The billing needs
four figures per month: the estimated cost with the agency's markup, the
editors who used the features, the editors who were allowed to, and the
results split by kind. A split per site or tenant is also wanted; usage rows
carry no site today, and recording one needs every calling extension to pass
page context. That is a separate decision.

## Decision {#adr-231-decision}

A second page of the Analytics module, `billing`, shows one calendar
month; `exportBilling` downloads the same month as CSV. Both reuse the
queries of the Analytics page through `UsageAnalyticsServiceInterface`
and refuse a non-administrator themselves, on top of the module's
`access => admin`.

-   **The month** is the calendar month in the server timezone, from the
    first second to the last second of its last day. `request_date` is the
    local midnight of the day a request ran, and the queries compare
    inclusively, so the first second of the next month would pull that day in.
    The default is the previous complete month; the running month and the
    twelve before it are offered.
-   **The markup** is the extension setting `billing.markupPercent`, a whole
    percentage from 0 to 1000, default 0. It is added once to the month total,
    in micro-dollars, rounded half up; stored costs never change. A value
    outside the range is clamped and a value that is not a whole number reads
    0, and the page says so.
-   **Active editors** are the backend users with at least one request in
    the month, whatever their account state is today; the state is shown.
    Requests without a backend user (command line, scheduler) are a separate
    row, not an editor; so is the usage of core's command-line user
    `_cli_`, under which console commands record usage since TYPO3 14.3.8.
    The page shows cost and total rounded to cents and the markup as their
    difference, so the displayed lines add up; the CSV keeps 6 decimals.
-   **Entitled editors** are the users who can log in now, hold the
    `tasks_use` grant ([ADR-130](https://docs.typo3.org/permalink/netresearch/nr-llm:adr-130@0.42)) through a group or a
    subgroup, and have the `nrllm_aitasks` module ([ADR-131](https://docs.typo3.org/permalink/netresearch/nr-llm:adr-131@0.42))
    through the user record or a group. Groups are resolved by core's
    `GroupResolver::findAllUsersInGroups()` for both the grant and the
    module, with its restrictions. Administrators are not counted,
    their number is stated. Users and groups keep no history, so this is
    always today's state, labelled with today's date.
-   **Results by kind** group requests, tokens and cost by service type,
    calling extension ([ADR-178](https://docs.typo3.org/permalink/netresearch/nr-llm:adr-178@0.42)) and task.
-   **The CSV** is one table with a `section` column, so a spreadsheet
    imports it in one step: RFC 4180, comma, CRLF, UTF-8 without a byte order
    mark, dot decimals, amounts with 6 decimals in USD named in the column
    header. Text cells that would start a formula get a leading `'`.

## Consequences {#adr-231-consequences}

-   A site dimension is one more column in the "by kind" grouping and one
    more CSV column, once usage rows carry a site.
-   "Entitled" covers nr-llm's own permission only. An extension that calls
    the AI services with its own checks is not covered, so an active editor
    need not be entitled.
-   The figures are the estimates of [ADR-029](https://docs.typo3.org/permalink/netresearch/nr-llm:adr-029@0.42), not provider
    invoices.

## Alternatives considered {#adr-231-alternatives}

-   **Markup per row in the export.** Rounding per row makes the rows not add
    up to the total; the markup is a property of the invoice, so it sits on
    the total row only.
-   **Separate CSV files per section.** A billing spreadsheet would need
    several imports; one table with a section column needs one.
-   **Entitlement by the grant alone.** A user with the grant but without the
    module has no page to run a task from, so counting them would overstate
    what the customer pays for.
