Keysat Docs
Get started · Introduction

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, 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:

Architecture

Keysat is the licensing layer sitting on top of your existing payments stack. Three boxes:

The key word is offline. Once a license is issued, your software does not need to phone home to verify it. The verification is a pure function of the license bytes and the public key. This is the same model used by signed JWTs, except wrapped in a small fixed-width format that’s comfortable to print on a receipt.

Why offline matters. Online license servers are a single point of failure for every customer who ever bought your software. With Keysat, if your Start9 disappears tomorrow, every previously-issued license still verifies. That’s sovereignty.

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 (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:

FieldMeaning
duration_secondsHow long the license is valid. 0 means perpetual.
grace_secondsExtra time after expiry before the verifier rejects.
max_machinesSeat cap. 0 means unlimited.
is_trialSets a TRIAL bit so your app can show a "trial" banner.
is_recurring + renewal_period_daysAuto-renew on a cycle (weekly / monthly / annual / custom). The daemon mints a fresh invoice + signed license per cycle.
entitlementsSubset of the product’s catalog this policy grants. Baked into the signed license.
metadata.marketing_bulletsOperator-authored ✓ items rendered on the buy-page tier card. Pure marketing copy. Not enforced.
metadata.hidden_entitlementsSlugs 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.

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.

Merchant profiles

By default a Keysat instance sells for one business. On first boot it creates a single default merchant profile from your operator name, and every product you create attaches to it. If you only ever run one business, you can ignore profiles entirely: everything works against the default.

On the Pro and Patron tiers you can run more than one business from the same instance. A merchant profile is one business identity, and it owns:

So one operator, on one Start9, can sell Recaps under the Recaps brand (settling to the Recaps wallet) and Aurora under the Aurora brand (settling to the Aurora wallet) side by side, with separate checkout branding and separate books. Keysat is still not a shared SaaS: two independent sellers each run their own box. What profiles add is multiple businesses under one operator.

ConceptWhat it is
Default profileAuto-created on first boot, exactly one per instance. A product with no explicit profile resolves to it.
Payment providerOne configured BTCPay or Zaprite account, attached to a profile. A profile can have more than one.
RailA buyer-facing payment method: Lightning, on-chain, or card. BTCPay serves Lightning and on-chain; Zaprite adds card.
Rail preferenceA tie-breaker for when a profile has two providers that both serve the same rail: it sets which one wins.

Manage all of this in the admin UI under Merchant profiles: create a profile, set its branding, connect BTCPay or Zaprite to it, mark one as the default, and attach products. Connecting a provider to a profile uses the same one-click authorize handshake as Connect BTCPay in setup, just scoped to the profile you start it from.

Running one business? Skip this. The default profile is created for you and every product uses it automatically. Merchant profiles only start to matter when you want a second brand or a second wallet on the same instance, which is a Pro and Patron capability.

Discount codes

Four kinds:

KindWhat it does
percentBuyer appends ?code=FOUNDERS50 to the purchase URL; price drops by N%.
fixed_satsLike 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_priceOverrides 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_licenseNo 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 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:

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.

Because verification is offline, a license that was once issued continues to verify forever, even if you mark it as revoked in the admin UI. The verifier in your app doesn’t know about your admin actions.

You have three options:

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:

Where to next