Files
keysat-docs/index.html
T
2026-05-07 10:42:46 -05:00

165 lines
8.9 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="index.html" class="brand"><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>
<a href="https://keysat.xyz">Marketing</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 is a self-hosted licensing service for software creators who want to be paid in Bitcoin. Buyers pay through your own BTCPay; your software verifies signed keys offline. You own the signing key, the customer list, and the payment rails.</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>