Skip to main content

Features and meters

This page defines the system semantics behind features and meters, beyond endpoint field definitions.

What each concept controls​

  • feature_code is the gate/quota axis and the entitlement lookup axis.
  • meter_code is the pricing/settlement axis.
  • feature_family_code is 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_code and meter_code are 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_code in 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-config with missing_price_reason=auto_registry or both).

Semantic kind split: activity vs outcome​

  • authorize + commit use activity meters.
  • direct ingest (public) also expects activity.
  • 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 than 0; quota and rate limits are evaluated against this feature-level quantity dimension.
  • In ingest, feature quantity is optional; when omitted, canonical feature quantity becomes sum(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:
    • ingest returns 422 meter_not_allowed_for_feature.
    • commit is quarantined (no applied quantity/settlement), and returns feature.meter_not_allowed hint.
  • 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[]:

  • commit and ingest use 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) and delete_meters in 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.

Default value strategy for omitted fields​

Feature create/update​

  • name:
    • create: defaults to feature_code when omitted;
    • update: keeps existing value when omitted.
  • description:
    • create: defaults to empty string;
    • update: keeps existing value when omitted.
  • active:
    • create: defaults to true;
    • update: keeps existing value when omitted.
  • 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.
  • 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 to activity when omitted on create; keeps existing value on update.
  • unit: defaults to unit on create; keeps existing value on update.
  • scale: defaults to 0 on create; keeps existing value on update.
  • rounding (meter-level): defaults to round on create; keeps existing value on update.
  • active: defaults to true on create; keeps existing value on update.
  • metadata: defaults to {} on create; keeps existing value on update when omitted.

Meter rate input​

When meter_prices is provided, it contains two complete exact rational rates:

  • revenue: unit_price_xusd / unit_quantity_minor;
  • provider cost: unit_cost_xusd / cost_unit_quantity_minor.

Both unit-price/reference-quantity pairs are required. Unit prices are non-negative integer strings and reference quantities are positive integer strings. A reference quantity preserves the configured "per D" quote scale; it is used as the mathematical divisor but is not a minimum billing block.

unit_price_base_xusd defaults to unit_price_xusd and unit_price_dynamic_xusd defaults to 0. When either is supplied, the effective unit price must equal base plus dynamic. effective_at is managed by the server: a no-op update preserves it and a material rate change advances it. Write APIs accept a valid effective_at echoed by read APIs for object round trips, but ignore its value; clients cannot choose the effective timestamp.

Pricing behavior that affects feature/meter modeling​

  • Pricing is resolved at commit/ingest time from latest meter_prices row per meter (effective_at descending).
  • Contract pricing v2 terms adjust complete rational rates, but only when an active meter_prices row exists for that meter at the same time point.
  • A selected malformed pricing term fails rating atomically; it never falls back to the catalog rate.
  • Missing meter pricing in commit causes quarantined application status and pricing.not_configured hint; it uses an explicit missing-pricing snapshot rather than a synthetic zero rate.
  • Revenue and cost are rated only with integer_rational_floor_v1. Their independent numerator remainders are scoped by billing user, meter, pricing kind, and rate identity.

Entitlement resolution linked to features/families​

  • Effective entitlement_required is resolved as:
    • feature override if set
    • else fallback to feature-family entitlement_required.
  • If effective entitlement_required=false, access does not require a matching plan entitlement entry. The feature can still be controlled by gate policies, bundles, budgets, and funding rules at runtime.
  • If effective entitlement_required=true, access requires a matching entitlement allow path before runtime limits are considered.
  • Plan entitlements can target:
    • exact feature
    • feature family
    • wildcard (both null), then specificity/priority resolution applies.
  • Missing entitlement returns ENTITLEMENT.REQUIRED; explicit deny returns ENTITLEMENT.DENIED.

Practical model:

  • Entitlements answer "is this entitlement-required feature/family allowed for this account?"
  • Gate policies and bundles answer "if it is allowed, how much/how fast can usage occur?"
  • These are independent configuration surfaces; do not model them as one combined per-plan resource-and-limit list.

Event -> ratings DSL coupling to feature/meter model​

DSL outputs follow these constraints:

  • emit.intents[].feature_code and meters[].meter_code must be slug format.
  • match.event_type currently supports exact matching only.
  • Each emitted intent becomes one ingest commit in the billing engine.
  • Intent feature_quantity_minor is 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​