ModelRig Docs Leaderboard Console ↗ GitHub Sign up

What ModelRig does not capture unless you record it

The question this page answers: which of my model calls does ModelRig not see, how can I tell from the console, and how do I record a call my code makes itself?

ModelRig records a call when your code makes it through ModelRig: the SDK (rig.run, rig.runBundle, rig.runRaw, rig.runRawStream) or the OpenAI-compatible gateway. It also records a call your code makes itself when you report it with rig.recordCall (see Calls your code makes itself: record them). It has no other way to see your traffic. It doesn't read your provider account, and it doesn't patch provider client libraries. So the honest boundary is simple: if a call didn't go through ModelRig and you didn't record it, ModelRig didn't see it.

What ModelRig does not see unless you record it. ModelRig records the calls your code makes through the SDK (rig.run, rig.runBundle, rig.runRaw, rig.runRawStream) or the gateway, plus any call your code makes itself and reports with rig.recordCall. Nothing else is seen. Five things stay invisible or unattached unless you record them: (1) direct provider SDK calls, with or without a JSON schema; (2) streams you read from a provider client yourself; (3) a fallback your code makes after the raw lane refuses a responseSchema it cannot enforce (deepseek, deepinfra, fireworks and zai fail closed with invariant_violation); (4) calls made outside run.scope / run.start, which land on /inferences but on no run; (5) embeddings. A recorded call shows as "Reported by your code": ModelRig neither dispatched nor validated it, and its tokens are the numbers you reported. Details and the console tells: https://modelrig.dev/not-captured.html.

Each case, and what to do about it

CaseWhat happensWhat to do
Direct provider SDK calls (an OpenAI, Gemini or Anthropic client your code calls itself), including calls that pass a JSON schemaNothing is recorded: no row on /inferences, no step on /runs, no cost.Record it with rig.recordCall right after the call returns (below), or put it behind rig.runRaw with the same provider, model and parameters, or extract it to a route. A schema call on the raw lane passes responseSchema; a route declares schema:.
Streams you read from a provider clientSame as a direct call: invisible.Record the finished stream's usage with rig.recordCall, or use rig.runRawStream, which records the stream when it finishes.
A responseSchema the raw lane can't enforce on that provider (deepseek, deepinfra, fireworks and zai; gemini, openai, grok and anthropic do enforce it)ModelRig refuses the call before dispatch, rather than sending it without the schema: your code gets a RigFailureError with class invariant_violation, and no provider call is made. If your code then falls back to calling the provider directly, the fallback is invisible unless you record it.Check the provider × knob matrix in Route bundles before you add responseSchema. On those four providers, use responseFormat: "json_object" where the matrix shows it and validate in your code, send the step to a provider that enforces schemas, or record the direct fallback with rig.recordCall.
Calls outside a runThe call is recorded on /inferences with its cost, but it belongs to no run, so /runs doesn't show it.Wrap the pipeline execution in run.scope / run.start (Observe a pipeline).
EmbeddingsNot recorded on any lane.Record them with rig.recordCall (tokensOut: 0) if you want them in your ModelRig costs; otherwise account for them from your provider bill.

A call your SDK version doesn't support yet behaves like a direct call if your code works around it. If a page describes a knob your installed version rejects, compare npm ls modelrig with the version stamp at the top of llms.txt.

Calls your code makes itself: record them

rig.recordCall needs modelrig 0.11.0 or newer.

When a call has to stay a direct provider call (a client library ModelRig has no lane for, a stream your code reads itself, a fallback), report it to ModelRig right after it returns. rig.recordCall writes one row for it, joins it to the active run and step, and returns an inferenceId you can grade.

Record a call your code makes itself. A provider call your code makes directly (a client ModelRig has no lane for, a stream you read yourself, an embedding, a fallback) is invisible until you report it. Call rig.recordCall right after it returns, inside the run, with the tokens the provider reported: the call shows on /inferences as "Reported by your code", joins its run and step, and counts toward cost. It is synchronous and never throws; invalid input warns once and records nothing (inferenceId: null). Spread optional fields only when the provider reported them, so the call type-checks under exactOptionalPropertyTypes.


const started = Date.now();
const resp = await genai.models.generateContent({ model: "gemini-3.1-pro-preview", contents });
const u = resp.usageMetadata;
const finishReason = resp.candidates?.[0]?.finishReason;

const rec = rig.recordCall({
  provider: "gemini",
  model: "gemini-3.1-pro-preview",
  usage: {
    tokensIn: u?.promptTokenCount ?? 0, // total input, cached included
    tokensOut: (u?.candidatesTokenCount ?? 0) + (u?.thoughtsTokenCount ?? 0),
    tokensCached: u?.cachedContentTokenCount ?? 0,
    // optional fields: spread only when the provider reported them
    ...(u?.thoughtsTokenCount !== undefined ? { reasoningTokens: u.thoughtsTokenCount } : {}),
  },
  servedTier: "standard", // the tier that SERVED the call: pass what the provider reports (flex, priority); it is priced at that tier
  latencyMs: Date.now() - started,
  stepKey: "summarize", // the step on /runs (inside run.scope / run.start)
  ...(finishReason !== undefined ? { finishReason } : {}),
});
if (rec.inferenceId !== null) rig.grade({ kind: "inference", id: rec.inferenceId }, { score: 1, kind: "deterministic" });

What is verified and what isn't. Nothing about the call itself. ModelRig did not send the request, so it has no prompt or output to hash, no schema to check, and no measurement of its own: the tokens, tier, latency and failure on the row are the numbers you reported (the row's meta.provenance is caller-reported). What ModelRig does own is the arithmetic: the cost is the provider's list price at the served tier applied to your token counts, and the row joins your run, step and grades exactly as a raw-lane call does.

How it shows in the console. On /inferences, in the call detail and in the step panel the call carries a Reported by your code badge (recorded · unverified with Developer details on): "Your code made this call and reported its tokens and cost." ModelRig did not dispatch or validate it. It counts toward run, step, tag and project costs, and its quality signal is your grades. It never enters a route's ladder, fallback or conformance numbers, and lane splits count it separately as recorded. Reading rows yourself (an export, the analytics API)? Recorded rows carry tags.lane = "external"; isRoutedLane(tags.lane) from modelrig is false for them and for raw-lane rows, the same test the console uses.

Cost and reconciliation. A recorded call is spend on your own provider key: ModelRig never charges it to a prepaid balance as provider cost, and it is billed like a raw-lane call on your own key. Its cost estimate is only as accurate as the tokens you report, so reconcile against your provider bill the same way as for any other lane: per day and per model. A recorded call with pricingMissing adds $0 to ModelRig's totals and is the first place to look for a gap. Context-length price bands (for example Gemini above 200k tokens) are not applied, as on every lane.

How to tell from the console

Three tells point at a call ModelRig didn't see:

1. A step shows — for its cost (shown as cost n/a with Developer details on) on /runs/<id>. The step has artifacts (you saved them with that stepKey) but no recorded call. The model call that produced them went around ModelRig and wasn't reported with rig.recordCall. 2. Your code has no inferenceId for a call. Every recorded call returns result.meta.inferenceId. If your logging or grading code finds none for a step, that step's call wasn't recorded, and a grade for it has nothing to attach to. 3. The run's total cost is lower than your provider bill for the same period. Some spend is happening outside ModelRig. Compare per day and per model: the gap is usually one call site.

To find the call site, search your code for direct provider client calls (new OpenAI(, GoogleGenAI, generateContent, messages.create, chat.completions.create) that aren't inside the rig.runRaw seam and aren't followed by a rig.recordCall.

What is recorded for a call ModelRig does see

For each attempt: provider, model, requested and served tier, tokens in, out and cached, cost, latency, failure class, your tags, and the step it belongs to, plus hashes and sizes of the request and the output. The prompt and output text is not kept unless you save it as an artifact or turn on call capture (captureCalls, needs modelrig 0.14.0 or newer); what is then stored follows your content custody setting.

Keep prompts and outputs: turn on call capture. By default ModelRig records every call's metadata and cost (model, tokens, tier, latency, failure, and hashes and sizes of the request and output), not the prompt or output text. You keep text two ways: save it yourself with artifact.save, or turn on call capture in code with artifacts.captureCalls: true. It is off by default and is not an env var, and it needs api mode (MODELRIG_API_KEY): direct and local-only rigs never capture. With it on, each attempt ModelRig sends to a provider through rig.run, rig.runBundle, rig.runStream, rig.runRaw and rig.runRawStream saves the request as rendered before provider-side additions as a prompt artifact and the response text as a raw_response artifact; retried attempts each get their own pair, a failed attempt keeps its prompt plus the model's response when the provider returned one (for example a content_invalid or quality-rejected attempt), and the call page labels them captured (opt-in). It applies only when your organization's content posture is managed, and never under metadata or off, while the SDK has not read (or cannot re-read) the posture, or on a zero-retention route or run. Routes or calls with a mask policy are never captured: capture skips them. Captured text takes the same path as any artifact you save: your scrub setting runs first (a pii or phi route is scrubbed at redact or stronger), the upload size limit applies, and stored content follows your organization's retention setting on /account. rig.recordCall never captures text; save artifacts for those calls yourself. Details: https://modelrig.dev/not-captured.html.


import { createRig, loadConfigFromEnv } from "modelrig";

// Opt in, in code (not an env var): keep each call's prompt and response text.
const rig = createRig(loadConfigFromEnv({ artifacts: { captureCalls: true } }));

Related: Choosing a lane · Troubleshooting an empty console · Routing & reliability