Features and meters
This page defines the system semantics behind features and meters, beyond endpoint field definitions.
What each concept controls
feature_codeis the gate/quota axis and the entitlement lookup axis.meter_codeis the pricing/settlement axis.feature_family_codeis the inheritance/grouping axis for entitlement requirements and family-level entitlements.
Practical implication: a single commit can be accepted or blocked by feature-level policy while being priced by one or more meters.
Code format and normalization
feature_codeandmeter_codeare normalized to lowercase.- Allowed charset is
[a-z0-9._/@:-]+. - Length must be
1..128. - Code must start and end with
[a-z0-9].
Data model invariants
- Every feature belongs to exactly one feature family.
- A feature must have exactly one primary meter whose
meter_code == feature_code. - If you create a feature without explicitly providing meters, the system creates a primary meter automatically (
meter_code=feature_code, unit=unit, scale=0, rounding=round). - If you provide only non-primary meters, the system infers and adds a primary meter for the feature.
Auto-registry fallback family
- The system has a fallback feature family:
auto.registry. - When gate traffic auto-registers an unknown feature (for example, unknown
feature_codein authorize/commit flow), that feature can be placed into this fallback family. - This fallback family is for temporary landing only. You should move the feature to a business-meaningful family in operations.
- If not moved, the feature can continue to appear as needing follow-up configuration in management views (for example
features/needs-configwithmissing_price_reason=auto_registryorboth).
Semantic kind split: activity vs outcome
authorize+commituseactivitymeters.- direct
ingest(public) also expectsactivity. - event-to-ratings processing expects
outcome.
If an event policy matches but the event semantic kind is not outcome, processing is skipped (not committed).
Feature quantity vs meter quantities
The system intentionally keeps two dimensions:
- feature-level quantity (
quantity_minor): used for lease/quota/rate accounting. - meter-level quantities (
meters[].quantity_minor): used for pricing line items.
They are not required to be equal.
- In
commit, feature quantity is required and must be greater than0; quota and rate limits are evaluated against this feature-level quantity dimension. - In
ingest, feature quantity is optional; when omitted, canonical feature quantity becomessum(meters[].quantity_minor).
Meter allowlist behavior per feature
Commit/ingest validates each submitted meter against feature-meter mappings for the expected semantic kind.
- unknown or disallowed meters are treated as invalid for that feature.
- with auto-registry disabled:
ingestreturns422 meter_not_allowed_for_feature.commitis quarantined (no applied quantity/settlement), and returnsfeature.meter_not_allowedhint.
- with auto-registry enabled:
- unknown meters can be auto-created and attached to the feature, then revalidated.
Primary meter fallback rules
When client omits meters[]:
commitandingestuse the feature primary meter in the corresponding semantic kind.- If no primary meter is available for that semantic kind, treat it as a configuration error and fix the feature-meter mapping.
Create and update rules in management APIs
- In feature update, a meter code cannot be listed in both
meters(upsert) anddelete_metersin the same request. - Primary meter cannot be deleted.
- Deleting a feature is soft or hard depending on references:
- if referenced by plan entitlements, delete becomes soft-delete (
active=false); - if not referenced, delete removes the feature record.
- if referenced by plan entitlements, delete becomes soft-delete (
Default value strategy for omitted fields
Feature create/update
name:- create: defaults to
feature_codewhen omitted; - update: keeps existing value when omitted.
- create: defaults to
description:- create: defaults to empty string;
- update: keeps existing value when omitted.
active:- create: defaults to
true; - update: keeps existing value when omitted.
- create: defaults to
entitlement_required:- create: omitted means unset at feature level (family-level rule applies);
- update: keeps existing value when omitted.
metadata:- create: defaults to
{}; - update: keeps existing value when omitted.
- create: defaults to
meters:- create/update omitted: feature keeps its current mapping; for brand-new feature, system ensures a primary meter exists.
Meter create/update
semantic_kind: defaults toactivitywhen omitted on create; keeps existing value on update.unit: defaults touniton create; keeps existing value on update.scale: defaults to0on create; keeps existing value on update.rounding(meter-level): defaults toroundon create; keeps existing value on update.active: defaults totrueon create; keeps existing value on update.metadata: defaults to{}on create; keeps existing value on update when omitted.
Meter pricing defaults (when meter_prices is provided but fields are omitted)
unit_cost_xusd: defaults to0.unit_price_base_xusd: if omitted, derived fromunit_cost_xusd.unit_price_dynamic_xusd: defaults to0.unit_price_xusd: if omitted, derived frombase + dynamic.unit_quantity_minor/cost_unit_quantity_minor: if one side omitted, backfilled from the other side; if both omitted, default1.rounding/cost_rounding:- if one side omitted, backfilled from the other side;
- if both omitted, default
nearest.
effective_at: defaults to current time when omitted.
Pricing behavior that affects feature/meter modeling
- Pricing is resolved at commit/ingest time from latest
meter_pricesrow per meter (effective_atdescending). - Contract pricing terms can adjust base meter price, but only when an active
meter_pricesrow exists for that meter at the same time point. - If a contract term exists but base meter price row is missing, contract term is ignored and warning hint
pricing.meter_price_missingis emitted. - Missing meter pricing in commit causes quarantined application status and
pricing.not_configuredhint; line snapshots are still materialized with zeroed "missing" provenance. - Residual rounding is per billing account + meter + pricing identity; meter metadata
residual_mode='prepaid'changes rounding behavior from postpaid residual carry to prepaid carry.
Entitlement resolution linked to features/families
- Effective
entitlement_requiredis resolved as:- feature override if set
- else fallback to feature-family
entitlement_required.
- Plan entitlements can target:
- exact feature
- feature family
- wildcard (both null), then specificity/priority resolution applies.
- Missing entitlement returns
ENTITLEMENT.REQUIRED; explicit deny returnsENTITLEMENT.DENIED.
Event -> ratings DSL coupling to feature/meter model
DSL outputs follow these constraints:
emit.intents[].feature_codeandmeters[].meter_codemust be slug format.match.event_typecurrently supports exact matching only.- Each emitted intent becomes one ingest commit in the billing engine.
- Intent
feature_quantity_minoris optional:- present: written as feature quantity.
- absent: feature quantity derived from meter sum during ingest.
Design implication: DSL is not only mapping logic; it must target valid feature-meter topology (including semantic kind and allowlisted meters), or intents will fail/quarantine at ingest.
Next
- Usage-based model: Usage-based billing model (authorize then commit)
- Product catalog definition: Define your product catalog
- Metadata keys: Metadata-driven configuration