Welcome to Keysat.
-Keysat lets independent software creators sell their work on their own terms. You ship software — open source, closed source, free / paid versions, whatever fits — and Keysat handles the buy page, the Bitcoin payment via BTCPay, and a signed license for each buyer. How you use that license inside your software is up to you: a one-time purchase to unlock the whole app, a free + paid split with specific paid features, a tip-jar style supporter badge — all legitimate. The licensing layer is a primitive, not a script.
+Keysat lets independent software creators sell their work on their own terms. You ship software — open source, closed source, free / paid versions, whatever fits — and Keysat handles the buy page, payment via BTCPay, and a signed license for each buyer.
+How you use that license inside your software is up to you: a one-time purchase to unlock the whole app, a free + paid split with specific paid features, a tip-jar style supporter badge — all legitimate. The licensing layer is a primitive, not a script.
These docs cover both ends:
@@ -86,7 +87,7 @@Products & policies
You declare two things in Keysat: products and policies.
-A product is the thing you sell — "Bitcoin Ticker Pro", "Aurora Plugin", whatever. It has a slug, a display name, a description, and a price in sats.
+A product is the thing you sell — "Bitcoin Ticker Pro", "Aurora Plugin", whatever. It has a slug, a display name, a description, and a price (sats / USD / EUR). Each product also carries an entitlements catalog — the typed list of feature slugs your software cares about, plus their display names and descriptions. Policies pick entitlements from this catalog.
A policy is a license template attached to a product. It specifies:
grace_seconds | Extra time after expiry before the verifier rejects. |
max_machines | Seat cap. 0 means unlimited. |
is_trial | Sets a TRIAL bit so your app can show a "trial" banner. |
entitlements | Free-form list of feature flags baked into the signed key (e.g. core, sync, export). |
is_recurring + renewal_period_days | Auto-renew on a cycle (weekly / monthly / annual / custom). The daemon mints a fresh invoice + signed license per cycle. |
entitlements | Subset of the product’s catalog this policy grants. Baked into the signed license. |
metadata.marketing_bullets | Operator-authored ✓ items rendered on the buy-page tier card. Pure marketing copy — not enforced. |
metadata.hidden_entitlements | Slugs the license still grants but the buy-page card hides — useful when a higher tier uses "Everything in X, plus:" copy and doesn’t want to repeat implied entitlements. |
Each product has one policy slugged default — that’s the one consumed by the public purchase URL. You can attach additional named policies for manual issuance: a longer-duration "Lifetime" policy you hand out at conferences, a richer-entitlement "Pro" policy for upsells, etc.
A product can have one policy or many. Multi-tier ladders (think Basic / Pro / Max) are first-class: when a product has two or more public policies, the buy page renders a tier picker and the buyer chooses before paying. The displayed tier is selected from a ?policy=<slug> URL hint, then the highlighted ("most popular") policy if any, then the cheapest. Tier ordering on the picker is operator-controlled via drag-and-drop in the admin UI (or tier_rank in the API).
You can also attach private policies for manual issuance — e.g. a longer-duration "Lifetime" comp for conferences, a richer-entitlement "Internal" tier for support cases. Private policies don’t appear on the buy page; the admin API issues them directly.
Discount codes
-Three kinds:
+Four kinds:
| Kind | What it does |
|---|---|
percent | Buyer appends ?code=FOUNDERS50 to the purchase URL; price drops by N%. |
fixed_sats | Like above, but a flat sat amount comes off. |
fixed_sats | Like above, but a flat amount comes off. Denominated in the code’s discount_currency (sats / USD / EUR), so the same code can sit on top of multi-currency products. |
set_price | Overrides the tier price with a flat number, regardless of base. Useful for "first 100 buyers at 25k sats" promos where you want the price to be a specific round number rather than a percentage off. |
free_license | No payment at all. Buyer redeems the code via POST /v1/redeem and gets a signed license back. |
Codes can be capped at N uses, dated to expire, restricted to a single product, and tagged with a referrer label so you can see which campaign drove which sales in the audit log.
+Codes can be capped at N uses, dated to expire, restricted to one product (and optionally to a subset of policies on that product — e.g. "applies to Pro and Max but not Basic"), and tagged with a referrer label so you can see which campaign drove which sales in the audit log.
+Codes can also be marked featured — a "launch special" mode. A featured code:
+-
+
- Renders a diagonal "LAUNCH SPECIAL" ribbon + struck-through original price on the matching tier cards on the buy page. +
- Auto-applies for buyers who don’t type any code, with the input pre-filled so they can see what’s been applied. +
- Stops surfacing once it hits its
max_usescap or expires — the ribbon disappears and pricing reverts to standard automatically.
+
Operator-typed codes always take precedence: a buyer who pastes a non-featured code in the form gets that code instead of the auto-applied featured one.
Revocation strategy
This is the one piece of the architecture that requires a design decision from you.
@@ -124,7 +137,7 @@- Don’t support revocation at all. Many indie developers do this. Once a key is sold, it stays valid. Refunds are still possible — you send sats back via BTCPay; the key still works but the customer agreed to stop using it.
- Periodic online check. Your app fetches a small revocation list from your Keysat (or a CDN you point at it) once a week / month. Adds a "soft-online" requirement. -
- Short-lived licenses with renewal. Issue 30-day licenses; the app fetches a fresh signed token before expiry. v0.2 will ship recurring renewals as a first-class flow. +
- Short-lived licenses with renewal. Issue 30-day licenses; the app fetches a fresh signed token before expiry. Recurring renewals are first-class in v0.2 — define a policy with
is_recurring=true+renewal_period_daysand Keysat handles the cycle (invoice → settle → re-sign → webhook).
You decide the policy. Keysat doesn’t force a particular revocation model. The default is no revocation — that’s the simplest, sovereign-by-default choice. If you need stronger guarantees, layer them on with the patterns above.
Operator tiers
+Keysat itself ships under a tiered self-license. The daemon runs out of the box at the free Creator tier with caps that are generous for a solo developer; paid Pro and Patron tiers lift caps and unlock recurring billing + the Zaprite payment gateway. Caps are enforced by the daemon at create-time only — existing resources are always grandfathered if you downgrade.
+As of this writing, Creator caps at 5 products / 5 policies per product / 10 active discount codes, and Pro / Patron are unlimited. The exact tier list, prices, entitlements, and any active launch-special discount are operator-controlled on the master Keysat and may change — the canonical sources are:
+-
+
- The live tier cards on keysat.xyz (rendered dynamically from the master Keysat). +
- The pricing page on these docs for the human-readable breakdown. +
GET https://licensing.keysat.xyz/v1/products/keysat/policiesfor the machine-readable shape (entitlements, marketing bullets, featured discount, etc.).
+ - Your local daemon’s
GET /v1/admin/tierfor current tier + caps + usage from inside the admin context.
+