> ## Documentation Index
> Fetch the complete documentation index at: https://docs.nextmoca.com/llms.txt
> Use this file to discover all available pages before exploring further.

# How to recompute your own bill

> You hold the payload, we publish the function, the receipt names the version. Here is the arithmetic, in full, with runnable code.

<Note>
  Every selection response carries the `usage` receipt; the arithmetic below is
  the published definition of the number on it.

  One nuance if you are also checking payloads against the
  [per-request ceiling](/limits#per-request-ceilings): the ceiling counts your
  strings as sent, without NFC normalisation, while the meter normalises to NFC.
  For text already in NFC, which is nearly all text, the two agree.
</Note>

## Why this page exists

If you dispute a charge, we cannot show you your records. We never had them.

So the audit story has to work without them, and it does — by making the meter a
**pure function of your request body**:

<Steps>
  <Step title="You hold the payload">You sent it. We did not keep a copy.</Step>
  <Step title="We publish the function">In full, below, with a reference implementation.</Step>
  <Step title="The receipt names the version">`meter_version` pins which definition applied.</Step>
</Steps>

Which means you can arrive at the number yourself, without trusting a log you
cannot see. Exactly, if you run the Python reference implementation below — it
*is* the definition. A port to another language is exact for real text and has
one stated residual on exotic characters, which is
[written down](#if-your-number-disagrees) rather than left for a support
ticket.

## The definition

```
input_tokens = Σ estimate_tokens(NFC(record.text))
             + Σ estimate_tokens(NFC(record.title))
             +   estimate_tokens(NFC(task.prompt))
             + Σ estimate_tokens(NFC(task.recent_prompts[i]))
```

Those four field groups, and nothing else.

("Every field the engine reads" would be a wider set — `keywords`, `tags` and
`attributes` are read as indexing hints and are deliberately outside the meter,
because a number you have to be able to reproduce should be short enough to
reproduce by hand.)

**Not counted:** `keywords`, `tags`, `attributes` (structurally bounded; a size
cap on them applies), record `id`, `source`,
`step_id`, `kind`, anything in the response, and any JSON syntax — braces,
quotes and commas in the envelope are not your text.

<Note>
  **`tokens_before` on the response is a different number and is not the meter.**

  On this deployment it works out to `Σ estimate_tokens(record.text)` — the record
  text only. The meter adds `record.title`, `task.prompt` and
  `task.recent_prompts`, so your `input_tokens` will be **larger** than
  `tokens_before`, and the gap is exactly those three field groups. That is
  expected, not a discrepancy.

  Do not reconcile an invoice against `tokens_before`: it is an engine-internal
  budgeting quantity and is free to move when engine internals do, while the meter
  is pinned by `meter_version`.
</Note>

## The counting rules

These are **part of `meter_version`**, not implementation detail. They are what
make "you can recompute it" a true statement rather than an approximate one.

<AccordionGroup>
  <Accordion title="Normalise to NFC before counting">
    Unicode gives the same visible text more than one encoding. `á` can be one
    code point or two — `a` followed by a combining acute — and the counter sees
    them differently:

    ```python theme={null}
    estimate_tokens("a\u0301 clause")   # decomposed: "a" + combining acute -> 3
    estimate_tokens("\u00e1 clause")    # NFC: one code point               -> 2
    ```

    Both render as `á clause`. Only the second is what the meter counts.

    So the meter normalises first, and so should you. For text that is already in
    NFC — which is nearly all text — this is a no-op.
  </Accordion>

  <Accordion title="Count over Unicode scalar values, not UTF-16 code units">
    The character-count fallback divides by the number of **code points**. In
    JavaScript, `"𝔘".length` is `2` because the string is measured in UTF-16 code
    units, while the same character is one scalar value.

    A naïve port using `.length` therefore diverges on every astral-plane
    character — emoji, many CJK extensions, mathematical alphanumerics — and the
    same document would bill differently depending on which language you checked
    with. The TypeScript reference below uses `[...text].length` for this reason.
  </Accordion>

  <Accordion title="Round once, at invoice aggregation">
    Rounding happens once, over the summed exact amount for the whole invoice
    period — never per request. Per-request rounding of sub-cent amounts is how a
    million small requests silently stop summing to their own receipts.

    So: sum `input_tokens` across your receipts first, then multiply, then round.
    Not the other way round.
  </Accordion>
</AccordionGroup>

## The reference implementation

This is the published function, verbatim. It is a deterministic heuristic, not a
model tokenizer — deliberately, so the number is reproducible with no model
dependency and no per-provider drift.

<CodeGroup>
  ```python Python (reference) theme={null}
  import re
  import unicodedata

  _WORD_RE = re.compile(r"\w+|[^\w\s]", re.UNICODE)


  def estimate_tokens(text: object) -> int:
      """Deterministic token estimate used for budgeting without model deps."""
      if text is None:
          return 0
      raw = str(text)
      if not raw:
          return 0
      # Word/punctuation estimate plus a char fallback for long compact strings.
      lexical = len(_WORD_RE.findall(raw))
      char_based = max(1, len(raw) // 4)
      return max(lexical, char_based)


  def nfc(text: object) -> str:
      return unicodedata.normalize("NFC", "" if text is None else str(text))


  def input_tokens(request: dict) -> int:
      """The metered quantity, from the request body alone."""
      task = request.get("task", {})
      total = estimate_tokens(nfc(task.get("prompt")))
      for prompt in task.get("recent_prompts", []):
          total += estimate_tokens(nfc(prompt))
      for record in request.get("records", []):
          total += estimate_tokens(nfc(record.get("text")))
          total += estimate_tokens(nfc(record.get("title")))
      return total
  ```

  ```typescript TypeScript (port) theme={null}
  const WORD = "\\p{L}\\p{N}_";
  // Python's `\s` also covers U+001C–U+001F and U+0085; JavaScript's does not.
  const SPACE = "\\s\\u001C-\\u001F\\u0085";
  // U+FEFF is whitespace to JavaScript and not to Python, so it must still be
  // counted. It leads the alternation so the negated class cannot swallow it.
  const TOKEN_RE = new RegExp(`[${WORD}]+|\\uFEFF|[^${WORD}${SPACE}]`, "gu");

  export function estimateTokens(text: unknown): number {
    if (text === null || text === undefined) return 0;
    const raw = String(text);
    if (raw === "") return 0;
    const lexical = (raw.match(TOKEN_RE) ?? []).length;
    // Code points, NOT `.length` — see "Count over Unicode scalar values".
    const charBased = Math.max(1, Math.floor([...raw].length / 4));
    return Math.max(lexical, charBased);
  }

  const nfc = (text: unknown): string =>
    text === null || text === undefined ? "" : String(text).normalize("NFC");

  export function inputTokens(request: {
    records?: { text?: string; title?: string }[];
    task?: { prompt?: string; recent_prompts?: string[] };
  }): number {
    const task = request.task ?? {};
    let total = estimateTokens(nfc(task.prompt));
    for (const prompt of task.recent_prompts ?? []) total += estimateTokens(nfc(prompt));
    for (const record of request.records ?? []) {
      total += estimateTokens(nfc(record.text));
      total += estimateTokens(nfc(record.title));
    }
    return total;
  }
  ```
</CodeGroup>

### How the count works

Two estimates, and the larger wins:

* **Lexical** — the number of word runs and standalone punctuation marks.
* **Character fallback** — code points ÷ 4, at least 1. This catches long
  compact strings with no word boundaries, such as a base64 blob or minified
  JSON, which the lexical pass would badly under-count.

`max(lexical, char_based)` means a normal sentence is counted lexically and a
dense blob is counted by length.

## Worked example

The [quickstart](/quickstart) payload, field by field:

| Tokens | Field                    | Value                                                                        |
| -----: | ------------------------ | ---------------------------------------------------------------------------- |
|      8 | `task.prompt`            | `Can this invoice still be refunded?`                                        |
|      5 | `task.recent_prompts[0]` | `Summarise the account.`                                                     |
|      6 | `task.recent_prompts[1]` | `Pull the latest invoice.`                                                   |
|     21 | `records[r-812].text`    | `{"invoice_id": 4471, "total_cents": 128400, "status": "open"}`              |
|      4 | `records[r-812].title`   | `get_invoice(4471)`                                                          |
|     18 | `records[r-813].text`    | `Refunds are issued to the original payment method within 10 business days.` |
|      4 | `records[r-813].title`   | `Refund policy v4`                                                           |
| **66** |                          | **`input_tokens`**                                                           |

Note that `task.keywords` (`["refund", "invoice"]`) contributes nothing — it is
capped, not counted.

Then, from the receipt:

```
charge = max( minimum_request_fee,
              rate_per_million × charge_multiplier × input_tokens )
```

with `charge_multiplier` read off the receipt for that request, and
`rate_per_million` from the rate card named by `price_version`.

## Checking a whole invoice

<Steps>
  <Step title="Export your receipts for the period">
    One per request: `input_tokens`, `outcome`, `charge_multiplier`,
    `meter_version`, `price_version`.
  </Step>

  <Step title="Group by (price_version, charge_multiplier)">
    A rate change or a different outcome mix inside one period means more than one
    group. Do not average the multipliers.
  </Step>

  <Step title="Sum input_tokens within each group, then price the group">
    Exact arithmetic, not floats, if you can — a rational or decimal type. This is
    where the "round once" rule applies.
  </Step>

  <Step title="Round the total, once">
    Sum the groups, then round. The invoice does the same, in the same order.
  </Step>
</Steps>

Spot-check by re-deriving `input_tokens` for a sample of requests from your own
stored payloads. If your recomputation matches the receipts and your arithmetic
matches the invoice, the bill is verified end to end without us being involved.

## If your number disagrees

<AccordionGroup>
  <Accordion title="You are off by a lot on one request">
    Check that you are counting `title` as well as `text`, and `recent_prompts` as
    well as `prompt`. Those four are the whole surface, and omitting `title` or
    `recent_prompts` is the usual cause.

    Also check you are not counting `keywords`, `tags` or `attributes`, which are
    not metered, and not counting the JSON envelope.
  </Accordion>

  <Accordion title="You are off by a little, on text with unusual characters">
    Two likely causes:

    * **You did not normalise to NFC.** Decomposed accents count differently.
    * **You ported the function to a language that measures strings in UTF-16 code
      units.** Use code points.
  </Accordion>

  <Accordion title="You are off by a little and none of that applies">
    There is one residual we know about and would rather name.

    The counting function classifies characters as word / non-word / whitespace,
    and every runtime carries those classification tables at whatever **Unicode
    version** it shipped with. Two runtimes at different Unicode versions can
    disagree about a recently-assigned character.

    In practice this is invisible, and we measured rather than assumed it. Over a
    **4.2 MB corpus of prose, Markdown and source code (7,106 samples)** the two
    implementations agree on **every** sample, as they do across an adversarial
    set of whitespace and combining-character cases — including the three
    classifications a naive port gets wrong (U+001C–U+001F and U+0085, which
    Python treats as whitespace and JavaScript does not; and U+FEFF, the reverse).

    Where they *can* diverge is on code points assigned in a Unicode version one
    runtime has and the other does not. Against 3,027 strings of uniformly random
    code points — overwhelmingly unassigned and private-use, which is the worst
    case rather than a realistic one — they disagree on **116**, always by one or
    two tokens, and always because one runtime classifies a
    recently-assigned character as a letter and the other does not.

    If you need a number with no residual at all, run the **Python reference
    implementation**. It is the one the meter is defined by.

    You do not have to take the measurement on trust either — it is a script, and
    re-running it is how we keep the claim true as runtimes move.
  </Accordion>

  <Accordion title="It still disagrees">
    Tell us, with the `request_id`, the `meter_version` from the receipt, and your
    computed number. A disagreement between the published function and the meter
    is a defect on our side, and it is one we want to hear about — the whole point
    of publishing the function is that you can find it.
  </Accordion>
</AccordionGroup>

## Checking a payload before you send it

The same function answers "will this `413`?". Your per-request ceiling is
denominated in exactly this quantity:

```python theme={null}
# NOTE: the live ceiling counts strings as sent. Drop the nfc() wrapper here to
# match it exactly; keep it when you are reproducing a meter receipt.
tokens = input_tokens(request_body)
if tokens > 100_000:          # your plan's ceiling — see /limits
    ...                       # split it, or drop the least plausible records
```

Cheaper than a round trip, and it lets you split deliberately rather than by
bisection.
