Docs

Everything you need to wire SublimeKeys into your app. No account needed to read this.

$SUBLIMEKEYS_API_KEY

Every request below uses this as a placeholder for your real key. Sign in (free, no card) to get yours and see these examples filled in with your own product.

QUICKSTART — 3 CALLS

1. Register your product (done once, in Products)

curl -X POST https://api.sublimearts.io/v1/products \
  -H "Authorization: Bearer $SUBLIMEKEYS_API_KEY" \
  -d '{"slug":"my-app","key_prefix":"MYAPP"}'

2. Issue a key on every sale

curl -X POST https://api.sublimearts.io/v1/licenses \
  -H "Authorization: Bearer $SUBLIMEKEYS_API_KEY" \
  -d '{"product":"my-app","email":"buyer@mail.com","max_activations":3}'

→ {"key":"MYAPP-K3F7Q-9WZ2M-P8RT4-XN5CB", ...}
productrequired — the product slug from step 1
emailoptional — shown in your dashboard's license list
max_activationsoptional, defaults to 1 — how many different machine_ids this one key can be active on at the same time. Set it to whatever your own pricing promises (e.g. 3 for a "personal use, 3 devices" tier). Capped by your SublimeKeys plan, not your end customer's — 5 on Free, 25 on Pro, 100 on Business (see pricing).
expires_atoptional — ISO date; omit for a lifetime key
notesoptional — free text, your own reference only

Or skip this call entirely — see AUTOMATE below.

3. Verify inside your app

Python — official SDK (recommended)
# pip install sublimekeys
from sublimekeys import SublimeKeysClient

client = SublimeKeysClient(product_id="my-app")
result = client.activate(license_key=key)   # first run
result = client.verify(license_key=key)     # every launch after — offline-first,
                                             # verifies a signed lease locally,
                                             # zero network calls for up to 7 days
if result.valid:
    unlock_full_version()
Node.js / Electron — official SDK (recommended)
// npm install sublimekeys
import { SublimeKeysClient } from "sublimekeys";

const client = new SublimeKeysClient("my-app");
const activated = await client.activate(key);     // first run
const result = await client.verify(key);          // every launch after — offline-first,
                                                   // verifies a signed lease locally,
                                                   // zero network calls for up to 7 days
if (result.valid) unlockFullVersion();

Not on Python or Node.js, or want to call the raw API yourself? Python SDK on PyPI · Node.js SDK on npm or use the endpoints directly below.

Python — raw API
import requests

r = requests.post("https://api.sublimearts.io/activate", json={
    "license_key": key,
    "machine_id": machine_id,
    "product_id": "my-app",
})
if r.json()["valid"]:
    unlock_full_version()
Node.js — raw API
const r = await fetch("https://api.sublimearts.io/activate", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ license_key, machine_id, product_id: "my-app" }),
});
const { valid } = await r.json();
if (valid) unlockFullVersion();
cURL
curl -X POST https://api.sublimearts.io/activate \
  -d '{"license_key":"...","machine_id":"...","product_id":"my-app"}'
▸ New to this? Where these calls actually go in your app

The example above only shows /activate on its own — in a real app you call different endpoints at different moments:

App starts
├─ No license key saved yet (first run)
│    → show your "enter license key" screen
│    → user submits it → call /activate once → save the result locally
│
├─ A license key is already saved (every later launch)
│    → call /verify instead (not /activate again) — confirms it's
│      still valid on this machine
│
├─ API unreachable (user is offline)
│    → both /activate and /verify return a signed "lease" (valid 7 days)
│    → verify its signature locally instead of blindly trusting the last
│      result — the Python and Node.js SDKs do this automatically; other
│      languages can verify the Ed25519 signature themselves (public key
│      at GET /public-key)
│
└─ User uninstalls / signs out (optional)
     → call /deactivate so the seat frees up for another machine

On Python or Node.js/Electron, the official SDKs (PyPI · npm) already handle this — including real cryptographic verification of the offline lease. The raw version below skips that (it just trusts the last saved result while offline) — simpler, but not cryptographically checked. Use it if you're not on Python/Node.js or want full control.

Python — full app-start flow (raw API, no SDK)
import requests, json, os

STATE_FILE = "license_state.json"

def check_license(machine_id):
    saved = json.load(open(STATE_FILE)) if os.path.exists(STATE_FILE) else None

    try:
        if saved is None:
            # first run — ask the user for their key once
            key = prompt_for_license_key()
            r = requests.post("https://api.sublimearts.io/activate", json={
                "license_key": key, "machine_id": machine_id, "product_id": "my-app",
            }, timeout=5)
        else:
            # every later launch — re-check the saved key, don't ask again
            r = requests.post("https://api.sublimearts.io/verify", json={
                "license_key": saved["license_key"], "machine_id": machine_id, "product_id": "my-app",
            }, timeout=5)

        result = r.json()
        json.dump(result, open(STATE_FILE, "w"))
        return result["valid"]

    except requests.RequestException:
        # offline — trust the last known result instead of locking the user out
        return saved["valid"] if saved else False

Already comfortable with the concept? Skip this — the calls above are all you need.

OFFICIAL SDKS

🐍 Python — sublimekeys

Wraps the whole API and does offline verification for you — activate/verify/deactivate with a signed lease checked locally so most launches make zero network calls, and trial checks that survive an offline stretch too. Also installs a sublimekeys CLI for testing an integration from a terminal.

pip install sublimekeys
View on PyPI →

⚡ Node.js / Electron — sublimekeys

Same offline-capable design, zero runtime dependencies — Ed25519 verification uses Node's built-in crypto, so there's nothing for Electron's asar packaging to trip over.

npm install sublimekeys
View on npm →

More languages are on the roadmap. In the meantime, every endpoint below is a plain REST call — call it from anything that can make an HTTPS request.

ENDPOINT REFERENCE — PUBLIC (called by your app)

POST /activate

body: { "license_key", "machine_id", "product_id" }

▸ Details

First run — binds a license key to a machine_id. Safe to call again on the same machine — it won't consume another activation slot. Returns valid:false with a message if it can't (e.g. activation limit reached on a different machine). On success, also returns a signed lease (7-day offline trust window) in the response's lease field — the Python SDK verifies this locally on later launches instead of calling the API every time.

POST /verify

body: { "license_key", "machine_id", "product_id" }

▸ Details

Every subsequent launch — confirms the key is still valid on this machine. Doesn't touch your activation count, so call it as often as you like. Also refreshes the signed lease (see /activate) on every successful call.

POST /deactivate

body: { "license_key", "machine_id", "product_id" }

▸ Details

User signs out / uninstalls — frees the seat for another machine.

POST /trial/start

body: { "machine_id", "product_id" }

▸ Details

Get-or-create a 7-day trial for a machine. Idempotent — reinstalling never resets the clock.

POST /trial/status

body: { "machine_id", "product_id" }

▸ Details

Read-only trial check — never starts one.

No Authorization header on these — the license_key itself is the credential. Full account-scoped API (create/list/revoke) lives under /v1/*, see the dashboard.

⚡ AUTOMATE — ZERO-BACKEND KEY DELIVERY

Skip step 2 above entirely. Point your payment provider's webhook at your product's URL and every sale issues a key and emails it to your buyer — automatically.

  1. Stripe Dashboard → Developers → Webhooks → Add endpoint
  2. Paste your product's URL (sign in and create a product to get yours)
  3. Select event: checkout.session.completed
  4. Save. That's it — no server, no code.
  5. Optional but recommended: Stripe shows a Signing secret right after — paste it into "Signature verification" on the Products page, so we reject anything that isn't genuinely from Stripe (not just anyone who has this URL).

🔔 Want an email yourself on every sale, too? Toggle "Notify me" next to this block on the Products page.

MIGRATE FROM GUMROAD, KEYGEN OR YOUR OWN SERVER

Export your existing customers to a CSV with one email column, then run this once per product. It bulk-issues one fresh SublimeKeys license per row, prefixed with your brand.

migrate.py
import csv, os, requests

API_KEY = os.environ["SUBLIMEKEYS_API_KEY"]
PRODUCT = "my-app"

with open("customers.csv") as f:
    for row in csv.DictReader(f):
        r = requests.post("https://api.sublimearts.io/v1/licenses",
            headers={"Authorization": f"Bearer {API_KEY}"},
            json={"product": PRODUCT, "email": row["email"]})
        print(row["email"], "->", r.json()["key"])

This issues brand-new keys — it does not preserve your old key strings (we don't accept custom key values, by design). Email customers their new key, or run both checks side by side in your app for a transition window before retiring the old system.

PLAN LIMITS

FREE

1 product · 100 keys · 5 devices/key

PRO

5 products · 1,000 keys · 25 devices/key

BUSINESS

unlimited products & keys · 100 devices/key

Hitting a limit never breaks existing keys — they keep verifying forever. You just can't issue new ones until you upgrade. Downgrading or canceling never deletes anything, either.