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.

Rutas de la API de licencias
Método y rutaQué hace
POST /v1/activateActiva este equipo con email y clave; devuelve el token firmado
POST /v1/refreshRenueva el token (caducidad, plan, unidades); acepta tokens caducados
POST /v1/deactivateLibera este equipo
POST /v1/freePide la licencia gratis; la clave llega por email
POST /v1/trialPide 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
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"
  }'
200
HTTP/1.1 200 OK
content-type: application/json; charset=utf-8

{"token":"eyJ2IjoxLCJhcHAiOiJteWFwcCIsImxpYyI6…","status":"active"}
409
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.

payload
{
  "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.

Pasos
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)
JavaScript
// 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;
}
C#Orientativo
// 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();
SwiftOrientativo
// 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.

Leer la documentación

¿Te interesa?

Apúntate a la lista de espera y te avisaremos cuando abramos la beta cerrada. Por ahora nadie puede usarlo ni contratarlo.