SDKs and API
Any language can activate and verify a license
With OwnLicense, activating, renewing and deactivating a license is a small HTTP API on your own Worker. The token it returns is signed with Ed25519, so your app checks it offline with just the public key. SDKs make it shorter, but they're optional.
Status
HTTP API
Exists in the licensing system our own apps use; the generic version for other makers comes with the beta
C# SDK (.NET)
In development
Swift SDK
Planned
JS/TS, Python, Kotlin/Java, PHP, Go, Rust/C++
Later. Until then, the HTTP API works from any language
The API at a glance
JSON over HTTPS on your Worker's domain. No API keys: activation takes the customer's email and license key, and the other calls take the signed token.
| Method and path | What it does |
|---|---|
POST /v1/activate | Activates this device with email and key; returns the signed token |
POST /v1/refresh | Renews the token (expiry, plan, units); accepts expired tokens |
POST /v1/deactivate | Frees up this device |
POST /v1/free | Requests the free license; the key arrives by email |
POST /v1/trial | Requests a time-limited trial for this device; the key arrives by email |
GET /v1/prices/<app> | Paid plans and current prices, to show them in your app |
GET /v1/revoked/<app> | Signed list of revoked licenses |
Activate: request and response
The device id (dev) is a SHA-256 hash of your app id and the machine id, so the raw machine id never leaves the device.
curl -sS https://licenses.example.com/v1/activate \
-H 'content-type: application/json' \
-d '{
"app": "myapp",
"email": "ana@example.com",
"key": "MYAPP-7Q2KD-H9XNM-4TRVA-C8PWZ-K",
"dev": "9f2c4e6a8b0d1f3e5a7c9e1b3d5f7a9c2e4b6d8f0a1c3e5b7d9f1a3c5e7b9d0f",
"appVersion": "2.4.0",
"os": "Windows 11 24H2"
}'HTTP/1.1 200 OK
content-type: application/json; charset=utf-8
{"token":"eyJ2IjoxLCJhcHAiOiJteWFwcCIsImxpYyI6…","status":"active"}HTTP/1.1 409 Conflict
content-type: application/json; charset=utf-8
{"error":"seats_exhausted","message":"All 3 seats are in use"}A token you verify offline
The token is base64url(payload) + "." + base64url(signature). The signature covers the payload characters exactly as they are. Your app ships with the public key (and its kid), checks the signature, the app, the device and the expiry, and never waits for the network to start.
app- Your app's slug
lic- License id (the one in the revocation list)
plan / pid- Kind of license (lifetime, subscription, free, trial) and plan code
qty / feat- Units and features the app should unlock
dev- Hash of the device it was activated on
exp- Expiry in Unix seconds, or null for lifetime and free
The token carries no email or other personal data.
{
"v": 1,
"kid": "myapp-live-1",
"app": "myapp",
"lic": "lic_01h2xcm7q9k3v5b8n4t6r2w0yz",
"plan": "subscription",
"pid": "annual",
"per": "year",
"qty": 1,
"feat": ["pro"],
"dev": "9f2c4e6a…",
"iat": 1792540800,
"exp": 1825372800
}Verifying the token
The same steps in any language. The JavaScript example uses the standard WebCrypto API and works as is; the C# and Swift ones show what the SDKs are meant to look like and may change.
function isValid(token, now):
parts = token.split(".") # exactly two non-empty parts
payload = json(base64url_decode(parts[0])) # read kid; don't trust it yet
publicKey = EMBEDDED_KEYS[payload.kid] # unknown kid → invalid
if not ed25519_verify(publicKey, base64url_decode(parts[1]), ascii(parts[0])): return NO
if payload.v != 1 or payload.app != THIS_APP: return NO
if payload.dev != deviceHash(THIS_APP): return NO
if payload.lic in savedRevokedList: return NO
if payload.exp != null and payload.exp <= now: return EXPIRED # try /v1/refresh
return YES(pro = payload.plan != "free", units = payload.qty, features = payload.feat)// WebCrypto (Node 20+, Deno, Bun or a modern browser). Works as is.
const b64url = (s) =>
Uint8Array.from(atob(s.replace(/-/g, "+").replace(/_/g, "/").padEnd(Math.ceil(s.length / 4) * 4, "=")), (ch) => ch.charCodeAt(0));
async function verify(token, publicKeyB64) {
const [body, sig] = token.split(".");
if (!body || !sig) return null;
const raw = Uint8Array.from(atob(publicKeyB64), (ch) => ch.charCodeAt(0));
const key = await crypto.subtle.importKey("raw", raw, { name: "Ed25519" }, false, ["verify"]);
// The signature covers the payload characters exactly as received.
const ok = await crypto.subtle.verify({ name: "Ed25519" }, key, b64url(sig), new TextEncoder().encode(body));
return ok ? JSON.parse(new TextDecoder().decode(b64url(body))) : null;
}// Illustrative: what the C# SDK is meant to look like. The API may change.
var licensing = new OwnLicenseClient(
baseUrl: new Uri("https://licenses.example.com"),
app: "myapp",
publicKeys: new() { ["myapp-live-1"] = "BASE64_PUBLIC_KEY" });
// Activate once, with the email and key the customer typed.
var result = await licensing.ActivateAsync(email, key);
if (!result.Ok) ShowError(result.Error); // invalid_key, seats_exhausted…
// On every launch: verify offline, renew in the background.
var license = licensing.Current(); // null = free mode
_ = licensing.RefreshIfNeededAsync();// Illustrative: verification with Apple's CryptoKit; the Swift SDK will wrap it.
import CryptoKit
import Foundation
func base64URLDecode(_ s: String) -> Data? {
var b = s.replacingOccurrences(of: "-", with: "+").replacingOccurrences(of: "_", with: "/")
b += String(repeating: "=", count: (4 - b.count % 4) % 4)
return Data(base64Encoded: b)
}
func verify(token: String, publicKeyBase64: String) -> [String: Any]? {
let parts = token.split(separator: ".").map(String.init)
guard parts.count == 2,
let keyData = Data(base64Encoded: publicKeyBase64),
let key = try? Curve25519.Signing.PublicKey(rawRepresentation: keyData),
let signature = base64URLDecode(parts[1]),
key.isValidSignature(signature, for: Data(parts[0].utf8)),
let body = base64URLDecode(parts[0]) else { return nil }
return try? JSONSerialization.jsonObject(with: body) as? [String: Any]
}Errors
Errors come back as {"error": "code", "message": "…"}. Your app decides with error (invalid_key, seats_exhausted, expired, rate_limited…) and translates it; message is only for debugging. The full list is in the reference for POST /v1/activate.
Interested?
Join the waitlist and we'll let you know when the private beta opens. Nobody can use it or sign up for it yet.