LM-Kit OneDocs2026.10.8lm-kit.comEULA
Integration

Usage Metering and Billing

Every API response reports what the request fed to each model family and what it produced. A platform that resells AI work (credits per tenant, a price per page) reads this report and applies its own prices. The server measures, and your application bills.

Two principles drive the numbers:

  • Only what reached a model counts. A 100-page PDF sent to classification that reads three pages reports three. A page read from its text layer without a model reports nothing.
  • A page counts once per model family. The document parser can read a page twice (a second reading at a higher resolution, a crop of a table or chart). Each family still counts that page once, so internal quality passes never inflate a bill.

1Where the report is#

Response Carrier
JSON body of a /lmkit/v1 endpoint a usage field at the root of the body
Any metered response (binary files, OpenAI-, Anthropic- and Ollama-compatible endpoints) the X-LMKit-Usage header, the same object as compact JSON
Streamed chat, chat with documents, grounded answers the terminal event (done event or the frame with "done": true)
OpenAI-compatible chat stream with stream_options.include_usage lmkit_usage on the usage-bearing final chunk
Background job usage at the root of GET /lmkit/v1/jobs/{job_id} once the job has ended, and the X-LMKit-Usage header on that poll

A stream starts before its work is done, so it never carries the header: read its final event. A 202 Accepted carries no usage: the work continues in the job, and the job reports it. A failed or cancelled request reports the work it did before it stopped, with partial: true, in the header (and on the job). Job polling and the admin API are not metered.

The header is listed in Access-Control-Expose-Headers, so a browser client can read it.


2The object#

"usage": {
  "operation": "document-parsing",
  "input": {
    "llm":       { "characters": 0, "tokens": 0 },
    "vlm":       { "images": 2, "tokens": 834 },
    "ocr":       { "images": 0 },
    "layout":    { "images": 2 },
    "image":     { "images": 0 },
    "embedding": { "characters": 0, "images": 0, "items": 0 },
    "speech":    { "audio_seconds_analyzed": 0, "audio_seconds_transcribed": 0 },
    "training":  { "tokens": 0, "steps": 0, "duration_ms": 0 },
    "tools":     { "calls": {} }
  },
  "output": { "tokens": 92, "characters": 180, "items": 0 },
  "partial": false,
  "duration_ms": 12893,
  "models": [
    { "model": "lmkit-doc-parser:1", "kind": "vlm", "calls": 2, "input_tokens": 834, "output_tokens": 92, "duration_ms": 755 },
    { "model": "docling-layout-heron", "kind": "layout", "calls": 2, "input_tokens": 0, "output_tokens": 0, "duration_ms": 210 }
  ]
}

Every block is always present, with zeros when a family did no work, so one parser reads every endpoint.

Billable units

Field Counts
input.llm.characters Content placed into prompts: the new messages of the request, the input text of a task endpoint (translation, extraction, classification and the rest), and the text of document pages read as text, each page once. The instructions the server adds are not counted. A chat client that sends its whole history again is counted for its new messages only. A task that reads the same input in several passes counts it once.
input.vlm.images Distinct pages and images read by a vision-language model.
input.ocr.images Distinct pages and images read by an OCR engine.
input.layout.images Distinct pages analyzed by the layout detection model.
input.image.images Images processed by a non-generative image model (background removal).
input.embedding.characters / images / items Text embedded or reranked, images embedded, and vectors computed or documents scored. Cached vectors count too, so a figure never depends on what earlier requests left in a cache.
input.speech.audio_seconds_analyzed Audio scanned by voice activity detection.
input.speech.audio_seconds_transcribed Speech transcribed; silence the detector skipped is not.
input.training.tokens / steps / duration_ms Dataset tokens trained on times the epochs run, optimizer steps, and wall time. A run stopped early counts the share it trained.
input.tools.calls Tool calls by tool name, from chat and agents.
output.characters Generated text, transcripts included.
output.items Discrete results returned: entities, PII entities, keywords, extracted fields, segments, categories.

One page can count in several families: a scan read by the layout model, then OCR, then the reader counts once in layout, once in ocr and once in vlm. Each family has its own price, which can be zero.

Cost figures

tokens in input.llm and input.vlm, output.tokens and the models list describe what the work cost you: instructions, repeated passes and internal re-reads included. Use them for margins and capacity planning, not for a tenant's bill. models[].calls shows the re-reads that the billable counts leave out.


3Reserving credit before the work runs#

POST /lmkit/v1/file-info returns usage_estimate: the most a file can feed to models, in the same units.

"usage_estimate": { "images": 5, "characters": 0, "audio_seconds": 0 }
File Estimate
PDF, image (each frame of a TIFF) images: pages or frames
Word, HTML, email, text and other reflowable documents characters: the document's text
Audio and video audio_seconds: the duration of the track

A typical flow: estimate, convert the estimate to your own unit, reserve that much of the tenant's credit, run the request, then settle on the usage it returns. A request that reads only part of the file (a page range, a classification that reads the first pages) settles below its reservation.


4Turning usage into a price#

The server never applies a price. One way to bill per page, with a page at a fixed price and reflowable content converted to page equivalents:

pages = input.vlm.images
      + input.ocr.images          (if OCR is billed on its own)
      + ceil(input.llm.characters / 3000)
      + ceil(input.speech.audio_seconds_transcribed / 60)

Store the whole usage object with each billed request: operation tells you which endpoint did the work, and models explains any figure a tenant questions.


5Using it from the SDK#

The server reads these figures from LM-Kit.NET's UsageScope. An application that embeds the SDK directly meters the same way:

using LMKit.Telemetry;

using (var usage = UsageScope.Begin())
{
    var parsed = await parser.ParseAsync("contract.pdf");
    UsageReport report = usage.GetReport();
    Console.WriteLine($"{report.Vlm.Images} pages read by the reader");
}

On a farm. Usage is measured on the node that runs the work and travels with the response or the job record, so any node answers a job poll with the job's usage.

See also Errors, Retries, and Jobs for the job contract, Building a Multi-Tenant SaaS for per-tenant keys, and The Cost Math for what the work costs to run.