SDKs y API
Cualquier lenguaje puede activar y comprobar una licencia
Con OwnLicense, activar, renovar y desactivar una licencia es una API HTTP pequeña en tu propio Worker. El token que devuelve va firmado con Ed25519, así que tu app lo comprueba sin conexión solo con la clave pública. Los SDKs lo acortan, pero son opcionales.
Estado
API HTTP
Existe en el sistema de licencias que usan nuestras apps; la versión genérica para otros creadores llega con la beta
SDK de C# (.NET)
En desarrollo
SDK de Swift
Previsto
JS/TS, Python, Kotlin/Java, PHP, Go, Rust/C++
Más adelante. Mientras, la API HTTP funciona desde cualquier lenguaje
La API de un vistazo
JSON sobre HTTPS en el dominio de tu Worker. Sin claves de API: activar pide el email y la clave del cliente, y el resto de llamadas, el token firmado.
| Método y ruta | Qué hace |
|---|---|
POST /v1/activate | Activa este equipo con email y clave; devuelve el token firmado |
POST /v1/refresh | Renueva el token (caducidad, plan, unidades); acepta tokens caducados |
POST /v1/deactivate | Libera este equipo |
POST /v1/free | Pide la licencia gratis; la clave llega por email |
POST /v1/trial | Pide una prueba por tiempo para este equipo; la clave llega por email |
GET /v1/prices/<app> | Planes de pago y precios actuales, para enseñarlos en tu app |
GET /v1/revoked/<app> | Lista firmada de licencias anuladas |
Activar: petición y respuesta
El identificador del equipo (dev) es un SHA-256 del identificador de tu app y el del equipo, así que el identificador real del equipo nunca sale de él.
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"}Un token que compruebas sin conexión
El token es base64url(payload) + "." + base64url(firma). La firma cubre los caracteres del payload tal cual. Tu app lleva dentro la clave pública (y su kid), comprueba la firma, la app, el equipo y la caducidad, y nunca espera a la red para arrancar.
app- Identificador (slug) de tu app
lic- Identificador de la licencia (el que sale en la lista de anuladas)
plan / pid- Tipo de licencia (lifetime, subscription, free, trial) y código del plan
qty / feat- Unidades y funciones que debe desbloquear la app
dev- Hash del equipo en el que se activó
exp- Caducidad en segundos Unix, o null en perpetuas y gratis
El token no lleva el email ni ningún otro dato personal.
{
"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
}Comprobar el token
Los mismos pasos en cualquier lenguaje. El ejemplo de JavaScript usa la API estándar WebCrypto y funciona tal cual; los de C# y Swift enseñan cómo querríamos que fueran los SDKs y pueden cambiar.
función esVálido(token, ahora):
parts = token.split(".") # dos partes, no vacías
payload = json(base64url_decode(parts[0])) # leer kid; aún sin confiar
publicKey = EMBEDDED_KEYS[payload.kid] # kid desconocido → no válido
si no ed25519_verify(publicKey, base64url_decode(parts[1]), ascii(parts[0])): devolver NO
si payload.v != 1 o payload.app != THIS_APP: devolver NO
si payload.dev != deviceHash(THIS_APP): devolver NO
si payload.lic en savedRevokedList: devolver NO
si payload.exp != null y payload.exp <= now: devolver CADUCADO # probar /v1/refresh
devolver SÍ(pro = payload.plan != "free", unidades = payload.qty, funciones = payload.feat)// WebCrypto (Node 20+, Deno, Bun o un navegador moderno). Funciona tal cual.
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"]);
// La firma cubre los caracteres del payload tal cual llegan.
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;
}// Orientativo: cómo querríamos que fuera el SDK de C#. La API puede cambiar.
var licensing = new OwnLicenseClient(
baseUrl: new Uri("https://licenses.example.com"),
app: "myapp",
publicKeys: new() { ["myapp-live-1"] = "BASE64_PUBLIC_KEY" });
// Activar una vez, con el email y la clave que escribe el cliente.
var result = await licensing.ActivateAsync(email, key);
if (!result.Ok) ShowError(result.Error); // invalid_key, seats_exhausted…
// En cada arranque: comprobar sin conexión y renovar en segundo plano.
var license = licensing.Current(); // null = modo gratuito
_ = licensing.RefreshIfNeededAsync();// Orientativo: comprobación con CryptoKit de Apple; el SDK de Swift lo envolverá.
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]
}Errores
Los errores llegan como {"error": "código", "message": "…"}. Tu app decide con error (invalid_key, seats_exhausted, expired, rate_limited…) y lo traduce; message es solo para depurar. La lista completa está en la referencia de POST /v1/activate.
¿Te interesa?
Apúntate a la lista de espera y te avisaremos cuando abramos la beta cerrada. Por ahora nadie puede usarlo ni contratarlo.