.NET SDK — report usage and meter consumption
Oclavex distinguishes two kinds of usage. Reporting is telling the platform what happened (a feature was used, a session started) for analytics and utilization. Metering is drawing down a quota or credit balance, where the answer “is there room?” must be authoritative. The SDK exposes both off the session. Some reporting happens on its own: feature adoption is counted from the license checks your application already makes.
Feature adoption is counted for you
Section titled “Feature adoption is counted for you”Every license check your application already makes is a record that someone reached for that feature, so adoption needs no reporting calls of its own:
if (!license.FeatureValidation.IsValidFeature("reports.export")) return Forbid(); // counted as a refused check// ... the feature runs ... counted as a granted checkRefused checks are counted separately from granted ones, and that separation is what makes them worth having: a feature customers are repeatedly refused is one they would pay for.
Gating is counted, reading is not. IsValidFeature and ValidateUsageQuota decide whether
a user may do something, so they count. GetLicenseFeature and LicensedFeatures only read
the license, so a pricing table or feature matrix that renders every capability does not
inflate the usage of every feature on it. If your code gates by fetching a feature and
inspecting it, switch it to IsValidFeature(code) so the check is counted here.
Set Usage.AutoReportFeatureChecks to false to turn this off. Nothing else you report is
affected.
Report activity
Section titled “Report activity”session.UsageReporter records activity. Reports are buffered and flushed on the
UsageReportingIntervalSeconds interval you configure, so calls are cheap and don’t block
your request:
await session.UsageReporter.ReportStartFeatureUseAsync(feature);// ... feature runs ...await session.UsageReporter.ReportEndFeatureUsageAsync(feature);Use that pair when you want to know how long a feature was open. Activities are recorded flat, not nested. Which features are used at all is already counted from your license checks, so you do not need it for adoption.
Other reports on the same reporter cover session lifecycle
(ReportSessionStartedAsync / ReportSessionEndedAsync), a raw asset amount
(ReportAssetUsageAsync), application events (ReportAppEventAsync), and feedback
(ReportProductFeedbackAsync, ReportFeatureRatingAsync). The background reporter flushes
buffered usage on shutdown within ShutdownFlushTimeoutSeconds.
Transactions queue locally — on disk with FileTransactionStore, or the in-memory
default — and upload in the background, so reporting keeps working through network
drops. In a hosted app, register your own ITransactionStore (for example a
FileTransactionStore) before AddOclavexLicenseConsumer to make the buffer durable
across restarts. On the fluent-builder path,
use UseFileTransactionStore(path) or UseTransactionProcessorBuilder(...), and call
client.FlushAsync() to drain the queue on demand.
Meter a monitored asset
Section titled “Meter a monitored asset”When a metric has to enforce a limit — API calls, credits, seats-worth of a resource —
use session.AssetConsumption. ConsumeAsync asks the server to charge the amount; the
server settles concurrent draw-down so two callers can’t both spend the last unit. It does
not throw for anything that can happen while your application runs. It returns one of three
outcomes, and your code decides what each one means:
var consumed = await session.AssetConsumption.ConsumeAsync( license.LicenseValidation.CurrentSession, "api-calls.total", amount: 1);
switch (consumed.Outcome){ case ConsumptionOutcome.Refused: return Results.StatusCode(429); // the limit is reached: stop the action case ConsumptionOutcome.Unavailable: return Results.StatusCode(503); // no answer: stop, or proceed and reconcile later}// Accepted: charged, go ahead- Accepted: the usage was charged.
Statussays where the meter stands. - Refused: the server said no and nothing was charged. A quota refusal carries the
status:
Status.QuotaUsagePolicyisDenyorSuspendUsage, andStatus.Reasonsays whether the plan limit or the credits refused it. A request the server rejects, such as a metric the license doesn’t track, carries its code inErrorCodeandErrorCodeName. - Unavailable: no answer. The server could not be reached, timed out, or failed, so the usage may or may not have been charged. Retry with the same key (below), or decide at this call site whether the action may go ahead without an answer.
Status carries the enforcement decision, not just a number. QuotaUsagePolicy ranges from
None/NotifyOnly/AllowWithOverage/AllowWithGrace (proceed) through RateLimit
(throttle) to Deny/SuspendUsage (refused), so a plan that permits billed overage and one
that blocks outright are both expressible without your code special-casing them.
CurrentValue, Limit, UsagePercentage, and PeriodResetAt describe where the meter
stands and when it rolls over.
Every consume carries an idempotency key, and the result hands it back in IdempotencyKey.
Pass your own (a render id, an order line) or let the SDK generate one. After Unavailable,
retry with that key: the server returns its original decision instead of charging twice.
To show remaining allowance without consuming, use GetUsageStatusAsync, or
GetUsageWithBalanceAsync when you want the full picture including any prepaid credit
balance. Pass the session id from license.LicenseValidation.CurrentSession. Both return a
result: Value is the status, or null when the license doesn’t track that metric. A failed
result means the server refused the read or could not be reached.
Before starting work that is expensive to throw away, ask first. PreviewConsumeAsync
answers the question ConsumeAsync would answer, with the same three outcomes and what it
would cost, without changing anything: nothing is metered, nothing is charged, and nothing
is held. Do the work, then consume it:
var preview = await session.AssetConsumption.PreviewConsumeAsync(sessionId, "video.seconds", amount: 6);
if (preview.Outcome == ConsumptionOutcome.Refused){ // preview.Status?.Reason says whether it is the plan limit or the credits: NoCredit, // SpendingDisabled, BalanceExpired, BalanceSuspended. None means the quota itself. return Results.StatusCode(429);}
// ... render the six seconds ...
var consumed = await session.AssetConsumption.ConsumeAsync( sessionId, "video.seconds", amount: 6, idempotencyKey: renderId);Because nothing is reserved between the two calls, a concurrent consume can still take the capacity the preview saw. The consume is the decision that binds, and it can still refuse: check its answer, not only the preview’s.
When the quota’s overage tier is funded by a credit balance, an accepted consume says what
it cost: Status.CreditsCharged and Status.FundedUnits, the part of your amount that fell
past the included quota. Six seconds consumed with one funded unit is “five seconds
included, one second from your credits”.
Reporting vs. metering — which to use
Section titled “Reporting vs. metering — which to use”- For adoption — which features are reached for, and which are refused — call nothing. Your license checks are already counted.
- Use
UsageReporterfor activity you want to see later: how long a feature was open, session counts, application events. It is fire-and-then-flush; a dropped report costs a data point, not correctness. - Use
AssetConsumption.ConsumeAsyncwhen the number governs behavior — when exceeding it must stop the action. The server answers before the action proceeds and settles concurrent draw-down.
One overlap is worth knowing. ReportAssetUsageAsync names a monitored asset, so when that
asset carries quota tiers or a credit balance, the queued readings are priced and charged on
arrival exactly as a consume would be — consumption costs the same whether the network was
there or not. What an upload cannot do is be refused: the work has already happened. Reach for
ConsumeAsync when you need the answer before it runs.
Feedback prompts
Section titled “Feedback prompts”GetFeatureRatingPromptAsync asks the platform which feature (if any) the current user
should be prompted to rate — targeted, not random. Render your own prompt when it returns
a target, then submit with ReportFeatureRatingAsync. It returns null when nothing is
targeted.