Your LLM dashboard looks great on day one. Token counts tick up in real time, cost curves slope gently upward, the Grafana panel turns green. You show it to the team and everyone nods.
Thirty days later, finance wants to know the per-ticket cost of your support AI. The dashboard goes blank. It’s not a loading error. There’s just no way to get there from the data you have.
This happens on two paths.
The DIY path: your team scrapes provider pricing pages into a spreadsheet, writes a Postgres table storing model name, token counts, and a timestamp, maybe wires a Grafana panel to it. It works fine initially. On day 30, answering finance's question requires adding columns, backfilling history, and a week of string-matching against freeform metadata that was never meant to be aggregated.
The vendor path: you wire up a metering SDK, watch token counts roll in, and call it done. Thirty days later, the same question arrives. The dashboard aggregates by model and day, but not by the thing the business actually cares about.
The fix isn't more data, and it isn't a different tool. It's a taxonomy: four fields on every LLM transaction, plus a clear definition of what a "unit of business work" means in your system.
Get those right, and the questions your dashboard can't answer today become answerable without touching your data pipeline again.
Three nested concepts, plus one cross-cutting one
Most LLM dashboards present a flat stream of calls. That works until you need to roll up. The Revenium data model gives you a hierarchy, and understanding it once makes every field below obvious.
Transaction is one LLM call. The Revenium SDK fills most of this in automatically: model, tokens, cost, latency, and provider. Transactions can form parent-child relationships within a trace via parentTransactionId, which is useful when the output of one LLM’s call becomes the input to the next.
Trace is a complete workflow execution, from initial request through every downstream call to final response. It's the highest technical grouping unit. Revenium aggregates total cost, duration, token counts, and success/error tallies across every transaction under one traceId. You set this.
Job is a complete unit of business work. Not a session, not a request, not an agent run. A support ticket from creation to resolution. A sales lead from capture to qualification. A generated report from prompt to sign-off. A Job can span one trace or many, multiple transactions, extended time periods, and even human handoffs. Where Trace is the technical grouping unit, Job is the business grouping above it. You set this too.
Subscriber and Organization represent who did the work and who they belong to. These cross-cut every span, independent of the hierarchy above.
One distinction worth holding onto: a Job can succeed technically but fail commercially. Every LLM call returns cleanly, every trace finishes, but the ticket doesn't resolve, the lead doesn't convert, the review doesn't land. Jobs are the span where that gap becomes visible. (More on Job Outcomes in Post 5.)
The four fields that do 90% of the work
Almost all the reporting value in a mature LLM system comes from four fields. Most teams get distracted by the full s. Three of these four you set yourself.
1. agenticJobId + agenticJobType + agenticJobVersion
These three fields bind transactions to one unit of business work.
agenticJobId is a unique identifier per Job instance. One support ticket, one Job ID. agenticJobType is the category of work: support-resolution, lead-qualification, code-review. Pick stable, low-cardinality values.
agenticJobVersion is the field that makes A/B cost comparisons provable. When you ship a new prompt, model, or workflow version, increment this. Without it, you're averaging across versions you can't distinguish, which means "the new prompt is cheaper" is an assertion you can't actually support.
Leave any of these null, and you lose the ability to ask what it costs to do a specific type of work. Leave agenticJobVersion null, and you lose the ability to demonstrate that your optimizations worked.
2. traceId + traceType + traceName
These fields bind transactions to one workflow execution, the technical span below the Job.
traceId is a unique identifier per workflow execution, either auto-generated or set by you at the start of each run. traceType is a workflow category you control. Canonical examples include chat-completion, document-analysis, and batch-summarization. Use kebab-case and reuse values aggressively so the dashboard can roll up cleanly by workflow class. traceName is a human-readable label for this specific execution, useful for debugging but less important for aggregation.
3. taskType
The single highest-leverage field for dashboard legibility. It gets its own section.
4. subscriber.id + organizationId
Who did the work, and who they belong to. Two fields, not one. The identity model gets its own section as well.
taskType: the field that decides whether your dashboards make sense
Every LLM call in a mature system does something different, even when the underlying primitive (chat completion, embedding, tool call) is the same. A chat call that detects intent and a chat call that writes marketing copy should not land in the same bucket.
taskType is a free-form string, defined by you, that answers: what was the application trying to accomplish with this specific LLM call? Examples: generate-avatar, classify-ticket, route-intent, summarize-call-transcript, extract-entities, rerank-results.
Three rules make this field useful. First, keep values stable. Renaming them when you refactor breaks historical roll-ups. Second, keep cardinality low. A dozen to a few dozen values work well as dashboard dimensions; 400 variants make them unreadable. Third, pick one naming convention (kebab-case or camelCase) and enforce it in code review.
Derive taskType values from your own code's operation names: route handler, agent step, pipeline stage. Not from LLM output, not from prompt text.
Sidebar: The SDK includes an operationSubtype field that lets you sub-partition a single taskType. For example, taskType=classifyTicket with operationSubtype=firstPass vs refinement. Ignore it on day one. It becomes useful only when a single taskType starts masking two behaviors you genuinely need to measure separately.
Subscriber isn't one field. It's a three-level identity model
The instinct is to treat subscriber.id as "the customer." That instinct is wrong. Revenium has three distinct identity dimensions, and using them correctly is what lets a single dashboard answer both "which user?" and "which paying customer?" without reprocessing data.
Subscriber is the individual consuming the AI: an email address, a user UUID, or equivalent. Set on every transaction as subscriber.id and subscriber.email.
Organization is the grouping that subscriber belongs to: a tenant, a customer company, an internal business unit. Set via organizationId. Many subscribers can belong to one organization.
Subscriber Credential is the authentication identifier used for this specific call: an email, an API key alias, a service-account token. Set as subscriber.credential.name and subscriber.credential.value. A single subscriber can have multiple credentials, one per API key, per device, per environment, and each gets tracked separately.
The roll-up structure is credential to subscriber to organization. Any dashboard filter (per-user cost, per-tenant cost, per-API-key cost) is a different pivot on the same underlying data.
Identity in practice
Multi-tenant SaaS
Subscribers are individual users inside a tenant. The tenant goes on organizationId. Per-tenant cost rolls up on organizationId. Per-user cost inside a tenant rolls up on subscriber.id filtered by org. Don't collapse these two levels. Ten users in one tenant should appear as ten subscribers under one organization, not as one subscriber with ten sessions.
Embedded AI / API-product workloads
You expose AI through an API that your customers' applications call. organizationId maps to your paying customer (the company on the contract). subscriber.id maps to the specific individual or service account making the call, if your customer forwards that identity. subscriber.credential maps to the API key they authenticated with. subscriptionId maps to the specific plan or contract, if you track that.
If your customer doesn't forward an individual identity, set subscriber.id to the service account or application name and let subscriber.credential carry the per-key detail.
Anti-pattern (full diagnosis in Post 6): stuffing the customer's company name into subscriber.id and leaving organizationId blank. You lose the ability to distinguish two users from the same customer, and no future credential-level breakdown is possible.
End-user products (B2C)
If there's no organization concept and an individual logs in, consumes, and pays, then subscriber.id is the user and organizationId can stay blank. Most teams get this one right without thinking about it.
A simple test
Before you set any identity field, run through this: subscriber.id is the individual user. organizationId is the paying customer or tenant. If you're about to put a company name or tenant UUID in subscriber.id, it almost certainly belongs on organizationId instead.
What this unlocks
The Revenium SDK captures cost, tokens, latency, and provider data without any configuration. Everything useful on the dashboard after that comes from fields you fill in: what type of work was being done, which workflow execution it belonged to, what the specific LLM call was trying to accomplish, and who initiated it.
Those four pieces of context are what turn a cost log into something a business can actually reason with. Finance gets per-outcome cost. Product gets per-feature cost. Engineering gets the data it needs to prove that an optimization worked. None of that requires a new tool or a richer data source. It requires the right fields, populated consistently, from the start.
Want to see how this maps to your system? Book a walkthrough and we’ll show you how these four fields work against your actual workloads.



