Domain Registrar / Per-SKU Catalog
The "one logical product, hundreds of priced variants" pattern: customers buy domain names, but each TLD has its own wholesale cost from the registry, its own retail price, its own renewal duration, and its own pricing curve. Modeling each TLD as a separate Kontorion product would mean 1,500+ catalog entries; modeling them as keyed siblings on a single product — one product opted into a key set whose entries are the TLDs, plus one price per key — collapses the catalog to one row plus a price-per-key.
Real-world examples. Namecheap, GoDaddy, Cloudflare Registrar, Porkbun, Hover, Gandi, Squarespace Domains, Domain.com. Common shape: 1,500+ TLDs, prices ranging from $0.99 (.xyz on first year) to $5,000+ (premium .com / domain auctions), per-TLD renewal pricing, regional pricing differences (some TLDs have different wholesale costs for EU vs US registrants), bulk transfer flows.
The shape of the problem
Catalogs that grow with the underlying registry inventory create distinct billing challenges:
- Catalog explosion. Naively modeling 1,500 TLDs as 1,500 products means 1,500 plan attachments, 1,500 prices per currency, 1,500 places to update when wholesale costs shift. The catalog becomes its own maintenance burden.
- New TLDs land continuously. ICANN periodically delegates new TLDs (
.app,.dev,.ai). Your catalog has to absorb new SKUs without breaking ingest for in-flight registrations. - Unknown / mistyped SKUs. Upstream resellers sometimes send events for TLDs that aren't in your catalog (typos, deprecated TLDs, brand-new gTLDs). A keyed product's
price_keyis validated against its key set at ingest: an event whose key is not an active, non-excluded entry is rejected with aprice_keyvalidation error (HTTP 400), so a mistyped or unknown TLD never lands as an un-priced line — you add the missing key to the set before retrying. - Asymmetric renewal pricing. First-year
.xyzis $0.99; renewal is $9.99. The customer pays first-year price at registration, renewal price every year after. Different price keys (or different prices on the same key with different effective dates) handle this. - Bulk operations. Customers often register or renew 10+ domains in one transaction. The invoice should show one line per (TLD, action), not collapse into a single mush.
- Premium domains. Some specific names (single words, short strings) have wholesale prices set by the registry that override the standard TLD price. Customer-specific overrides handle these.
Kontorion blueprint
| Concern | Kontorion primitive |
|---|---|
| One product, many priced variants | Keyed product opted into a key set via key_set_id (TLDs are the set's keys) |
| Per-TLD price | One Price per (product, price_key=".tld") |
| Per-TLD display name | Price.display_name (e.g., ".com domain renewal") |
| Unknown SKU behavior | price_key validated against the key set at ingest; unknown keys are rejected (400) |
| Asymmetric first-year vs renewal | Different price keys per lifecycle event (e.g. .io-register / .io-renew), or effective-dated price versions |
| Bulk operations | Multiple usage events in one transaction; aggregator splits them |
| Premium domain pricing | Customer price override |
Build it
1. Define the keyed product
A keyed product is one opted into an org-global key set via key_set_id. The set's active entries (here, the TLDs you support) are the valid price_key values; key_set_excluded_keys is a subtract-only list for keys you want to opt this product out of. Because the billed quantity comes from usage events, set quantity_source to METERED.
Code
The product's price_key domain is the key set's active entries minus key_set_excluded_keys. An ingested usage event whose price_key is not an active, non-excluded entry is rejected with a price_key validation error (HTTP 400) — so an unknown or mistyped TLD bounces at ingest, and your catalog team adds the missing key to the set before retrying. There is no per-product "unknown SKU policy"; key-set membership is the gate.
2. Add prices for the most common TLDs
Each TLD is one Price on the product, keyed by price_key. The price carries the per-unit rate (a decimal string in major units) and currency; billing cadence is not a price field — it lives on the plan/subscription (billing_interval_unit + billing_interval_count).
Code
For 1,500+ TLDs, this is a script run from your wholesale-cost ingest pipeline - one POST per TLD per currency, automated. Each price_key must already be an active entry of the product's key set.
3. Customer registers a domain
Code
The event flows to the .io price; the next invoice carries one line: ".io - 1 × $35.00".
4. Bulk renewal of 5 domains
Submit one event per domain:
Code
Configure the line-grouping on the plan as expanded (one line per price_key) and the resulting invoice carries 5 lines, one per domain renewed, with the price's display_name and the domain in the metadata for rendering.
5. Premium domain (override)
A premium .com priced at $5,000:
Code
The override is time-boxed to the registration window via valid_from/valid_to (the customer-facing window for a negotiated price; distinct from effective_from/effective_to, which are the price-version publish/archive timestamps). Events for that customer + price_key during that window pick up the premium price; standard .com registrations are unaffected.
Variations
- Asymmetric first-year vs renewal pricing. Give each lifecycle event its own key (e.g. add
.io-registerand.io-renewas separate key-set entries with their own prices), and send the matchingprice_keyon the registration vs renewal event. Alternatively, keep one key per TLD and use effective-dated price versions when the renewal rate differs by date. - Bulk discount over N domains. STAIRCASE pricing on the .com price:
[{up_to: 10, unit_amount: "11.50"}, {up_to: null, unit_amount: "9.50"}]. Customers renewing 11+ domains get the lower rate on the tail. - Catch unknown TLDs with a set price. Create one price with
is_set_price: true(a NULLprice_key) on the product: the resolver falls back to it for any effective key that has no exact-key price, so new TLDs added to the key set get a safe default rate while your catalog team backfills per-TLD prices. (Keys that are not in the set at all are still rejected at ingest.) - Per-region wholesale cost. Same TLD might have different prices in different regions. Add
country_codeto the price; the right price wins based on the customer's country. - Other catalog explosion patterns. This same blueprint covers: per-IP-block IP allocations (one IP allocation product, keyed by
/24), per-region cloud SKUs (one VM product, keyed by region+size), per-cert-CA SSL certs (one cert product, keyed by issuing CA).
What you don't have to build
- 1,500 separate product records (one keyed product plus a key set replaces them)
- Per-TLD plan-attachment maintenance (prices attach directly to plans)
- "Unknown SKU" validation (key-set membership is enforced at ingest; unknown keys are rejected with a 400)
- Premium-domain overrides as a separate codepath (customer price overrides handle them)
- Bulk operation aggregation and deduplication (idempotency keys on the events)
Next steps
- Pricing - Keyed Prices - the core feature this pattern showcases
- Customer Price Overrides - premium SKUs and negotiated rates
- Usage Metering - event ingest and idempotency
- Custom Fields - storing domain-specific metadata on subscriptions