Documentation
Everything you need to sell with sellkeys and license your software.
Create a shop
Sign up, create a shop in the dashboard and pick an address. The shop is available right away at <name>.sellkeys.cc.
You set the name, logo, design, legal texts and notifications under Settings. With Pro you connect a custom domain (Settings → Custom domain) and get webhooks, API and unlimited products.
Connect Coldsweep
Payments run through Coldsweep, a non-custodial crypto gateway: buyers pay straight to your wallets, sellkeys never holds your money.
Click “Payments not connected” at the top of the dashboard, sign in to Coldsweep and allow access. sellkeys creates your store there and receives every confirmed payment via webhook; after that, delivery happens automatically.
As long as Coldsweep is not connected, your shop cannot sell anything.
Products
| Type | Delivery |
|---|---|
| Keys | One key per line in stock; after payment, one key per item is sent out automatically. |
| File | The buyer downloads the file after payment. |
| Service | The buyer receives your fixed text, e.g. instructions or an invite link. |
| Dynamic | sellkeys sends the signed order to your URL and delivers your response. |
| License | Generates a license key for your Auth app on payment. |
You set up variants with their own price and stock, addons, bundles, coupons and subscriptions in the catalog.
Webhooks
Under Settings → Webhooks & API (Pro) you enter an https URL and choose the events. sellkeys sends JSON via POST and retries failed deliveries several times.
Events: order.delivered, subscription.created, subscription.renewed, subscription.cancelled, subscription.expired, license.created, license.activated, license.banned, auth.user_registered.
License events contain licenses (id, app, key, level, status, expires_at, devices …), registrations additionally user (id, username).
Signature: X-Sellkeys-Signature: sha256=<HMAC-SHA256(Body, Webhook-Secret)>. Verify it before you use the body.
API keys
Under Settings → Webhooks & API you create an API key (Pro). Send it as Authorization: Bearer sk_…; bodies are JSON.
| Call | |
|---|---|
GET /api/v1/products | Products with variants and stock |
POST /api/v1/products/{id}/stock | Top up keys: {"keys": ["…"]}, up to 5000 per call |
GET /api/v1/orders?limit=&before= | Orders, newest first |
POST /api/v1/license/verify | Check a purchased key: {shop, key, hwid?}, no API key needed |
MCP
sellkeys is an MCP server: https://sellkeys.cc/mcp (sign-in via OAuth). Connect it to Claude or another MCP client and manage products, stock, orders, customers, coupons, subscriptions, tickets and licenses in chat.
License tools: list_auth_apps, auth_stats, create_licenses, find_license, license_action, auth_user_action.
Auth: client API
License checking for your software: your program starts a session, signs in with a license key and the hardware ID, and re-checks from time to time. Licenses come from purchases (product type License) or are generated in the dashboard (Auth → Licenses). App ID and public key: Dashboard → Auth → Apps.
Endpoint: POST https://auth.sellkeys.cc/v1 with a form or JSON body. Every request contains type, app (app ID) and nonce (16–128 characters [A-Za-z0-9_-], new for every request).
Responses are JSON: success, code, message, further fields, nonce and time.
Verify responses (required)
Every response carries the header X-Sellkeys-Signature: the Base64 Ed25519 signature of the exact body bytes, created with your app's private key. Verify it with the app's public key and check that nonce matches the nonce you sent. Otherwise discard the response: this stops fake servers, proxies and replayed “login ok” responses.
Only errors that occur before the app is known (unknown_app, bad_nonce, rate_limited) come without a signature. Never treat an unsigned response as a success.
Calls
| type | Fields and result |
|---|---|
| init | version, optional hash (SHA-256 of your program file). Returns session and app (name, version, update_url). |
| license | session, key, hwid. Checks the key, ban and expiry, binds the hardware ID up to the device limit and activates the key on first use. Returns license (level, expires_at, devices, device_limit). |
| register | session, username, password, key, hwid. Creates an account and redeems the key on it (one key = one account). Only if the app allows accounts. |
| login | session, username, password, hwid. Requires at least one valid license on the account. |
| upgrade | session, username, key. Redeems another key on an account (renewal, higher level). |
| check | session. Re-checks the license or account of the session; call it every few minutes. |
| var | session, name. Global variable from the dashboard; those marked “after login only” require a signed-in session. |
| getvar / setvar | session, name, value (max. 4000 characters). Variables per account, at most 50. |
| file | session, file. Returns name, size, sha256 and a download url valid for 5 minutes. Compare the SHA-256 before use. |
| webhook | session, webid, optional params and body. sellkeys calls the stored URL so that API keys never end up in your program. Returns status and response. |
| log | session, message (max. 500 characters). Appears under Auth → Logs. |
| logout | session. Ends the session. |
Error codes
| code | Meaning |
|---|---|
| unknown_app, bad_nonce | wrong app ID or missing nonce (unsigned) |
| rate_limited | too many requests from this IP (unsigned, HTTP 429) |
| app_paused, app_disabled | you paused or disabled the app |
| update_required | version below the minimum version; see update_url |
| hash_mismatch | program file hash not approved |
| invalid_session | session unknown or expired, call init again |
| invalid_key | the key does not exist for this app |
| hwid_required, hwid_mismatch | hardware ID missing or device limit reached |
| blacklisted | IP, country or hardware ID is on the app's blacklist |
| register_disabled | the app does not allow accounts |
| invalid_username, weak_password, username_taken | registration input |
| key_used | key is already redeemed on an account |
| invalid_credentials | wrong username or password |
| user_banned, no_active_license | account banned or no valid license left |
| not_logged_in | the call requires a signed-in session |
| var_not_found, file_not_found, level_too_low | Content |
| webhook_not_found, webhook_failed, invalid_params | Webhook proxy |
| license_banned, license_expired | License banned or expired |
SDKs
All SDKs implement the complete protocol (signature and nonce checks, hashed device ID, file download with SHA-256 check), with the same method names and error codes. Each file starts with a short example.
| Language | File | Dependencies |
|---|---|---|
| C# (.NET 6+) | SellkeysAuth.cs | BouncyCastle.Cryptography |
| Unity 2021.2+ (C#) | SellkeysAuthUnity.csSellkeysAuthExample.cs | com.unity.nuget.newtonsoft-json, BouncyCastle.Cryptography.dll |
| C++17 | sellkeys_auth.hpp | libcurl, libsodium, nlohmann/json |
| Python 3 | sellkeys_auth.py | requests, cryptography |
| JavaScript (Node.js 18+) | sellkeys-auth.mjs | none |
| TypeScript (Node.js 18+) | sellkeys-auth.ts | none |
| PHP 7.4+ | SellkeysAuth.php | ext-curl, ext-sodium |
| Rust | sellkeys_auth.rs | ureq 2, ed25519-dalek 2, sha2, base64, getrandom, serde_json |
| Java 17+ | SellkeysAuth.java | none |
| Go 1.21+ | sellkeys_auth.go | none |
| Lua 5.1–5.4 / LuaJIT | sellkeys_auth.lua | luasocket, lua-cjson, luasodium, luasec |
| Perl 5.14+ | SellkeysAuth.pm | CryptX, IO::Socket::SSL |
License API
Manage licenses from your own backend (Pro), with the same API key.
| Call | |
|---|---|
GET /api/v1/auth/apps | Apps with ID, public key and totals |
GET /api/v1/auth/licenses?app=&status=&limit=&offset= | List, newest first; status = unused, active, expired or banned |
GET /api/v1/auth/licenses?key=… | look up a key |
POST /api/v1/auth/licenses | Generate keys: {app, count?, level?, days?, devices?, mask?, note?} |
GET · POST · DELETE /api/v1/auth/licenses/{id} | read, change (reset, ban, unban, extend with days) or delete |
GET /api/v1/auth/users · POST · DELETE /api/v1/auth/users/{id} | List, reset, ban or delete accounts |
GET · PUT · DELETE /api/v1/auth/vars | global variables |
Moving from KeyAuth
Export your licenses as CSV and import them under Auth → Licenses → Import. Recognized columns: key/license, level, days/duration, expires, note, hwid, banned/status; a file with one key per line works too.
Keys with a hardware ID are imported as active and bound to it. Accounts with passwords cannot be carried over: users register again with their key.