Files
keysat-docs/index.html
T
Keysat 19a969f797 Topnav: brand logo links to keysat.xyz; drop redundant Marketing entry
Standard docs-site convention: top-left brand goes to the marketing
home, the 'Docs' badge next to it signals you're in the docs section.
The separate 'Marketing' nav item is no longer needed once the brand
itself handles that link.
2026-05-11 09:57:36 -05:00

164 lines
9.1 KiB
HTML

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Keysat Docs — Introduction</title>
<link rel="icon" type="image/svg+xml" href="assets/favicon.svg">
<link rel="stylesheet" href="docs.css">
</head>
<body>
<div class="topnav">
<a href="https://keysat.xyz" class="brand" title="Back to keysat.xyz"><img src="assets/keysat-mark.svg" alt=""><span>Keysat</span></a>
<span class="docs-tag">Docs</span>
<nav>
<a href="install.html">Install</a>
<a href="integrate.html">Integrate</a>
<a href="wire-format.html">Wire format</a>
<a href="operate.html">Operate</a>
</nav>
</div>
<div class="layout">
<aside class="side">
<div class="group">
<div class="glabel">Get started</div>
<a href="index.html" class="active">Introduction</a>
<a href="install.html">Install &amp; setup</a>
<a href="integrate.html">Integrate the SDK</a>
</div>
<div class="group">
<div class="glabel">Concepts</div>
<a href="index.html#architecture">Architecture</a>
<a href="index.html#products-policies">Products &amp; policies</a>
<a href="index.html#discounts">Discount codes</a>
<a href="index.html#revocation">Revocation strategy</a>
</div>
<div class="group">
<div class="glabel">Reference</div>
<a href="wire-format.html">Wire format</a>
<a href="integrate.html#api">Admin API</a>
<a href="integrate.html#sdks">SDKs</a>
</div>
<div class="group">
<div class="glabel">Operate</div>
<a href="operate.html#backups">Backups</a>
<a href="operate.html#migrate">Migrate hardware</a>
<a href="operate.html#troubleshooting">Troubleshooting</a>
</div>
</aside>
<main class="prose">
<div class="crumb">Get started · Introduction</div>
<h1>Welcome to Keysat.</h1>
<p class="lead">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.</p>
<p>These docs cover both ends:</p>
<div class="next-grid">
<a class="next-card" href="install.html">
<span class="eyebrow">Operator</span>
<h4>Install &amp; setup &rarr;</h4>
<p>Sideload the package, connect BTCPay, define your first product. About an afternoon, end to end.</p>
</a>
<a class="next-card" href="integrate.html">
<span class="eyebrow">Developer</span>
<h4>Integrate the SDK &rarr;</h4>
<p>Add the SDK to your app, embed your public key, verify a license at startup. About five lines of code.</p>
</a>
</div>
<h2 id="architecture">Architecture</h2>
<p>Keysat is the licensing layer sitting on top of your existing payments stack. Three boxes:</p>
<ul>
<li><strong>BTCPay Server</strong> &mdash; takes the payment. On-chain Bitcoin or Lightning, settling to your wallet. Lives on your Start9.</li>
<li><strong>Keysat</strong> &mdash; your private licensing service. Holds the Ed25519 signing key. Hosts the public purchase URLs at <code>/buy/&lt;product&gt;</code>. Listens for BTCPay payment webhooks and issues a signed license on each settlement. Lives on your Start9.</li>
<li><strong>Your software</strong> &mdash; the thing you sell. Ships with the Keysat <em>public</em> key embedded at compile time. On startup it reads the user&rsquo;s license and verifies the signature offline. No network call.</li>
</ul>
<p>The key word is <em>offline</em>. 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&rsquo;s comfortable to print on a receipt.</p>
<div class="callout">
<i data-lucide="info"></i>
<p><strong>Why offline matters.</strong> 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&rsquo;s sovereignty.</p>
</div>
<h2 id="products-policies">Products &amp; policies</h2>
<p>You declare two things in Keysat: products and policies.</p>
<p>A <strong>product</strong> is the thing you sell &mdash; "Bitcoin Ticker Pro", "Aurora Plugin", whatever. It has a slug, a display name, a description, and a price in sats.</p>
<p>A <strong>policy</strong> is a license template attached to a product. It specifies:</p>
<table class="t">
<thead><tr><th>Field</th><th>Meaning</th></tr></thead>
<tbody>
<tr><td><code>duration_seconds</code></td><td>How long the license is valid. <code>0</code> means perpetual.</td></tr>
<tr><td><code>grace_seconds</code></td><td>Extra time after expiry before the verifier rejects.</td></tr>
<tr><td><code>max_machines</code></td><td>Seat cap. <code>0</code> means unlimited.</td></tr>
<tr><td><code>is_trial</code></td><td>Sets a <code>TRIAL</code> bit so your app can show a "trial" banner.</td></tr>
<tr><td><code>entitlements</code></td><td>Free-form list of feature flags baked into the signed key (e.g. <code>core</code>, <code>sync</code>, <code>export</code>).</td></tr>
</tbody>
</table>
<p>Each product has one policy slugged <code>default</code> &mdash; that&rsquo;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.</p>
<h2 id="discounts">Discount codes</h2>
<p>Three kinds:</p>
<table class="t">
<thead><tr><th>Kind</th><th>What it does</th></tr></thead>
<tbody>
<tr><td><code>percent</code></td><td>Buyer appends <code>?code=FOUNDERS50</code> to the purchase URL; price drops by N%.</td></tr>
<tr><td><code>fixed_sats</code></td><td>Like above, but a flat sat amount comes off.</td></tr>
<tr><td><code>free_license</code></td><td>No payment at all. Buyer redeems the code via <code>POST /v1/redeem</code> and gets a signed license back.</td></tr>
</tbody>
</table>
<p>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.</p>
<h2 id="revocation">Revocation strategy</h2>
<p>This is the one piece of the architecture that requires a design decision from you.</p>
<p>Because verification is offline, a license that was once issued continues to verify forever &mdash; even if you mark it as revoked in the admin UI. The verifier in your app doesn&rsquo;t know about your admin actions.</p>
<p>You have three options:</p>
<ul>
<li><strong>Don&rsquo;t support revocation at all.</strong> Many indie developers do this. Once a key is sold, it stays valid. Refunds are still possible &mdash; you send sats back via BTCPay; the key still works but the customer agreed to stop using it.</li>
<li><strong>Periodic online check.</strong> 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.</li>
<li><strong>Short-lived licenses with renewal.</strong> Issue 30-day licenses; the app fetches a fresh signed token before expiry. v0.2 will ship recurring renewals as a first-class flow.</li>
</ul>
<div class="callout">
<i data-lucide="key-round"></i>
<p><strong>You decide the policy.</strong> Keysat doesn&rsquo;t force a particular revocation model. The default is no revocation &mdash; that&rsquo;s the simplest, sovereign-by-default choice. If you need stronger guarantees, layer them on with the patterns above.</p>
</div>
<h2 id="next">Where to next</h2>
<div class="next-grid">
<a class="next-card" href="install.html">
<span class="eyebrow">Step 1 for operators</span>
<h4>Install &amp; setup &rarr;</h4>
<p>Get Keysat running on your Start9, connect BTCPay, define your first product.</p>
</a>
<a class="next-card" href="integrate.html">
<span class="eyebrow">Step 1 for integrators</span>
<h4>Integrate the SDK &rarr;</h4>
<p>Embed your public key, add the SDK to your app, verify a license offline.</p>
</a>
</div>
</main>
<aside class="toc">
<div class="label">On this page</div>
<a href="#architecture">Architecture</a>
<a href="#products-policies">Products &amp; policies</a>
<a href="#discounts">Discount codes</a>
<a href="#revocation">Revocation strategy</a>
<a href="#next">Where to next</a>
</aside>
</div>
<script src="https://unpkg.com/lucide@latest"></script>
<script>lucide.createIcons();</script>
</body>
</html>