Skip to content

Subscriptions and entitlements

An account has one subscription with one item per enrolled paid product: your account can be on Free for Flags, Standard for Logging, and Pro for Config at the same time. Products on Free are simply absent from the subscription — paid products show up as items. Each plan publishes a set of entitlement keys (numerical limits) that the platform enforces on writes. When you hit a limit, the API returns 402 Payment Required.

A concrete example

Your team is starting out. You sign up — every product begins on the implicit Free plan, and your account has no subscription yet. You stay on Free Flags forever (your usage fits within the Free limits) but find yourself wanting more managed loggers than Free Logging allows. You enroll Logging at Standard — the platform creates your subscription with one item for Logging. Three months later, configs grow — you add a Config item at Standard too. Flags stays implicit Free with no item on the subscription.

The same subscription now has two paid items (Logging and Config), each at its own plan. Both bill on the same monthly cycle.

Plans

Each product has four plans:

  • Free — the default. No card required. Generous-but-limited entitlements.
  • Standard — paid monthly. Bigger limits, suited to teams shipping to production.
  • Pro — paid monthly. Larger limits built for scale.
  • Enterprise — paid monthly. Largest limits, plus governance, security, and SSO controls for whole-org rollouts.

Plans are defined per product in the platform's catalog. The exact entitlement values live in the catalog and are surfaced via GET /api/v1/products (the public products endpoint, also powering the pricing page).

bash
curl https://app.smplkit.com/api/v1/products

Returns each product's plans, prices, limits, and marketing copy. Fully cacheable; no auth required.

Entitlement keys

Every plan publishes limits keyed by entitlement key. A value of -1 in any limit means unlimited. The keys with finite values today:

KeyMeaningBundled / FreeStandardProEnterprise
logging.managed_loggersLoggers with managed = true101001000unlimited
logging.groupsLog groups325100unlimited
config.itemsConfigs per account1050250unlimited
config.keysItems per config (per write)252502500unlimited
config.value_size_bytesPer-item value byte size1024102401024001048576
config.inheritance_depthMax parent chain depth2510unlimited
flags.itemsFlags with managed = true1050250unlimited
flags.rulesRules per flag (per write)525100unlimited
audit.events_per_monthCustomer-emitted audit events per month1,000100,0001,000,00010,000,000
audit.retention_daysDays customer audit events are retained3036518253650
audit.siem_streamingSIEM streaming (forwarders) enabled0001

Smpl Audit's "Bundled" tier ships free with any other smplkit product subscription — there's no separate audit subscription at that level. smplkit-emitted audit events (resource types prefixed smpl.) are unlimited at every tier and never count toward audit.events_per_month.

audit.siem_streaming is enforced at the fan-out path (POST /api/v1/events), not at the management surface. Forwarder configuration is plan-agnostic on every plan — you can create, list, update, retry, and delete forwarders regardless of subscription. When the entitlement is 0, the audit service short-circuits before any forwarder lookup: no HTTP delivery is attempted and no delivery records are written. See SIEM streaming for the full delivery-status semantics.

Other keys (config.environments, config.servers, config.users, flags.environments, flags.servers, flags.users, logging.environments, logging.servers, logging.users) are defined and may be enforced in the future, but ship -1 (unlimited) or 0 on every plan today. Authoritative values come from GET /api/v1/products.

How counting differs across products

Logging charges you only for managed loggers — auto-discovery is free, so a service that imports thousands of library loggers doesn't fill your quota. Logging groups count separately and are always managed.

Flags has the same managed/discovered split as Logging: only flags with managed = true (typically created via POST /api/v1/flags) count toward flags.items. Auto-discovery via POST /api/v1/flags/bulk is exempt from governance entirely, so a service that imports thousands of flag references doesn't fill your quota.

Config has no managed/discovered split; every config is explicit and counts toward config.items.

What happens at quota

When a write would push you past a limit, the platform returns:

http
HTTP/1.1 402 Payment Required
Content-Type: application/vnd.api+json
json
{
  "errors": [{
    "status": "402",
    "code": "entitlement_limit_reached",
    "title": "Subscription limit reached",
    "detail": "Your free plan allows a maximum of 15 managed loggers. Upgrade your subscription to increase this limit.",
    "meta": {
      "limit_key": "logging.managed_loggers",
      "current": 15,
      "maximum": 15,
      "plan": "free"
    }
  }]
}

The meta.limit_key tells you exactly which limit blocked you. Existing resources keep working — only the write that would exceed the limit fails. Reads, evaluations, and metric ingestion are unaffected.

The console surfaces these as inline upgrade prompts when you click an action that would fail.

Subscription statuses

Stripe-aligned. The status field on the subscription resource takes one of:

  • ACTIVE — the customary good state.
  • PAST_DUE — last invoice failed; Stripe is retrying. Functionality continues during the retry window.
  • CANCELED — subscription ended. Functionality continues until current_period_end, after which all items are dropped and the account reverts to implicit Free across the board.
  • null — fully discounted: an administrator has set discount_source = OVERRIDE at 100%, and Stripe is not involved. All paid items are honored without billing.

Until you enroll your first paid item the account has no subscription row at all — GET /api/v1/accounts/current/subscription returns 404. Free state is derived from the absence of a subscription, or from a subscription that simply has no item for a given product.

Subscription lifecycle

The whole subscription is one resource you replace with a PUT. Each call to PUT /api/v1/accounts/current/subscription sends the desired items list — the server diffs against your current state and reconciles. Mechanically, that produces five kinds of per-item transition:

  • New item — a product not currently on the subscription is added. Takes effect immediately; Stripe creates prorations for the remainder of the current period.
  • Upgrade — an existing item moves to a higher plan. Takes effect immediately; prorated charge today.
  • Downgrade — an existing item moves to a lower paid plan. Scheduled for current_period_end; the higher plan continues until then. The item shows pending_plan_change and scheduled_change_effective_at until the change takes effect.
  • Drop — a product currently on the subscription is omitted from the request. Scheduled for current_period_end; reverts to implicit Free then.
  • Unchanged — an item already at the requested plan is left as-is.

To reverse a pending downgrade or drop before period end, send another PUT with the item back at its current paid plan — the diff classifies it as UNCHANGED and clears the pending change. Cancellation of the whole subscription is just a PUT with an empty items list; if no paid items remain, Stripe schedules cancellation at period end.

Before committing a change you can call POST /api/v1/accounts/current/subscription/actions/preview with the same body to project per-item effects, prorated charges due today, and the next-invoice total without mutating anything.

The first paid item requires a payment method. Subsequent changes reuse the account's default payment method unless you specify another. See Manage subscriptions.

Cross-product platform limits

Some platform-wide capabilities are plan-resolved entitlements — they vary by the highest plan you hold across any paid product and return 402 Payment Required when exceeded. The rest are flat operational caps that are the same on every plan and return 409 Conflict.

Plan-resolved (platform.*)

The platform pseudo-product resolves at the highest plan held across your paid products (config/flags/logging/audit). If you're on Flags Pro and Logging Free, your platform.* entitlements come from the Pro tier. See GET /api/v1/products for the exact values per plan.

KeyWhat it gatesFreeStandardProEnterprise
platform.managed_environmentsEnvironments with managed = true (only managed environments count)2310unlimited
platform.writersAccount members whose role is OWNER, ADMIN, or MEMBER. Pending non-VIEWER invitations also count.1unlimitedunlimitedunlimited
platform.readersAccount members whose role is VIEWERunlimitedunlimitedunlimitedunlimited

Two new error codes carry context for the most common 402s and 400s on these surfaces:

  • environment_unmanaged (400) — a write to flags / config / logging tried to set a per-environment value on an unmanaged environment. Promote it first via PUT /api/v1/environments/{id} with managed: true.
  • environment_not_found (400) — the referenced environment doesn't exist for this account.

Flat operational caps (409 Conflict)

These remain the same on every plan; they exist to bound platform resource usage. Contact support if you legitimately need more.

  • 50 API keys per account
  • 50 context types per account
  • 10,000 context instances per account (includes services)
  • 100 pending invitations per account

Who can do what

ActionOWNERADMINMEMBERVIEWER
Read subscription, products
Read invoices, billing history
Replace subscription (add, upgrade, downgrade, drop items)
Manage payment methods