Kora/developer documentation/SDKs

SDKs

Check license keys, activate devices, read offline licenses and manage licenses, products and customers from Node.js, Java, Python, FiveM, .NET, PHP, Go or Rust.

The SDKs do two things:

  • Check a license key against POST /api/v1/licenses/validate, and optionally verify the response signature.
  • Take and free device seats with POST /api/v1/licenses/activate and POST /api/v1/licenses/deactivate.
  • Download signed offline license files and check them without a network call.
  • Manage licenses, products and customers through /api/v1/licenses, /api/v1/products and /api/v1/customers, including customer merge.

The source is in zerodev-ca/Kora-SDKs.

PlatformPackageVersion
Node.js and TypeScript@zerodev-ca/kora3.1.1
Java 17+ (Paper, Spigot, Velocity, Folia)ca.zerodev:kora-java3.1.1
Python 3.8+zerodev-kora3.1.1
FiveM and RedMkora-fivem3.1.1
.NET 8ZeroDev.Kora3.1.1
PHP 8.1+ with sodiumzerodev/kora3.1.1
Go 1.21+github.com/zerodev-ca/Kora-SDKs/packages/go/v33.1.1
Rustzerodev-kora3.1.1

A Paper plugin template has the license check, seat activation and an offline fallback built in. Copy it and add your own code.

The Integration page in the dashboard shows your request URL and public key, SDK and HTTP snippets filled in with your own address and products, and a tester that runs a real validation.

Authentication

Validation is public unless Require an API token with the validate scope is on in the Licenses config. Management calls always need an API token from the API Tokens page, sent as Authorization: Bearer <token>, with the matching scopes (licenses.view, licenses.create, products.edit and so on).

Validation Response

{
    "valid": true,
    "code": "valid",
    "message": "This license is valid.",
    "license": {
        "key": "XXXXX-XXXXX-XXXXX-XXXXX",
        "status": "active",
        "product": { "id": 1, "name": "Test Product 1", "version": "1.0.0", "active": true },
        "policy": { "id": 2, "name": "Pro", "type": "subscription" },
        "customer": { "id": 4, "name": "Test Customer 1", "email": "[email protected]", "discord_id": null },
        "addons": [],
        "expires_at": null,
        "whitelists": {}
    },
    "timestamp": 1791083116035
}

code is one of valid, invalid_request, not_found, missing_product, wrong_product, disabled, expired, inactive, leaked, not_whitelisted, seats_full, blacklisted, blocked, unauthorized, or one of your negative statuses. See Result Codes. license is null unless the key is valid. license.policy is the policy the key was issued under, or null. Check its name to unlock features per plan. The x-kora-signature header is an Ed25519 signature of the raw response body, checked against the key from GET /api/v1/public-key.

Devices and Offline Licenses

Node.jsJavaPythonFiveM
Extra fields such as hwidvalidate(key, product, { hwid })validate(key, product, Map.of("hwid", id))validate(key, hwid=id)config.fields
Take a seat and validateactivate(key, product, fields)activate(key, fields)activate(key, **fields)activate(config, key, callback)
Free a seatdeactivate(key, fields)deactivate(key, fields)deactivate(key, **fields)deactivate(config, key, callback)
Download a license filefile(id)file(id)file(id)
Check a license file offlineoffline(file)offline(file)offline(file)offline(config, file, callback)
Merge customersmerge(id, duplicateId)merge(id, duplicateId)merge(id, duplicate_id)customers(config, "merge", { id, customer_id })
.NETPHPGoRust
ValidateValidateAsync(key, product, fields)validate($key, $fields)Validate(ctx, key, fields)validate(&key, &[("hwid", id)])
Take a seat and validateActivateAsync(key, product, fields)activate($key, $fields)Activate(ctx, key, fields)activate(&key, fields)
Free a seatDeactivateAsync(key, fields)deactivate($key, $fields)Deactivate(ctx, key, fields)deactivate(&key, fields)
Download a license fileFileAsync(id)file($id)File(ctx, id)file(id)
Check a license file offlineOffline(file)offline($file)Offline(file)offline(&file)
Merge customersMergeAsync(id, duplicateId)merge($id, $duplicateId)Merge(ctx, id, duplicateID)merge(id, duplicate_id)

offline needs the public key, verifies the signature and refuses files whose refresh_after or expires_at has passed. Validate online first and read the file only when Kora cannot be reached. See Offline Licenses.

Node.js

npm install @zerodev-ca/kora
import { Client } from "@zerodev-ca/kora";

const kora = new Client({ url: "https://kora.example.com", product: "Test Product 1", publicKey: process.env.KORA_PUBLIC_KEY });

const result = await kora.validate(licenseKey);
if (!result.valid) throw new Error(result.message);
const admin = new Client({ url: "https://kora.example.com", apiKey: process.env.KORA_TOKEN });

const product = await admin.products.create({ name: "Test Product 1", version: "1.0.0" });
const customer = await admin.customers.create({ name: "Test Customer 1", email: "[email protected]" });
const license = await admin.licenses.create({ product_id: product.id, customer_id: customer.id });

const { total, rows } = await admin.licenses.list({ status: "active", limit: 25 });
await admin.licenses.update(license.id, { status: "suspended" });
await admin.licenses.delete(license.id);

Failed management calls throw a KoraError with status.

Java

<dependency>
    <groupId>ca.zerodev</groupId>
    <artifactId>kora-java</artifactId>
    <version>3.1.1</version>
</dependency>
KoraClient kora = new KoraClient.Builder()
    .url("https://kora.example.com")
    .product("Test Product 1")
    .publicKey(publicKeyPem)
    .build();

KoraResponse result = kora.validate(licenseKey);
if (!result.isValid()) {
    getLogger().severe(result.getMessage());
    getServer().getPluginManager().disablePlugin(this);
}

kora.licenses(), kora.products() and kora.customers() return a KoraResource with list, get, create, update and delete, working on Gson JsonObject values. Failures throw KoraException with getStatus().

Python

pip install zerodev-kora
from kora import Client

kora = Client("https://kora.example.com", product="Test Product 1", public_key=public_key_pem)

result = kora.validate(license_key)
if not result.valid:
    raise SystemExit(result.message)

admin = Client("https://kora.example.com", api_key=token)
license = admin.licenses.create(product_id=1, customer_id=4)
page = admin.licenses.list(status="active")

Failures raise KoraError with status.

FiveM

Copy kora-fivem into your resources and ensure kora-fivem before your own resource.

local config = { url = "https://kora.example.com", product = "Test Product 1" }

exports["kora-fivem"]:validate(config, GetConvar("license_key", ""), function(valid, data)
    if not valid then
        print("^1" .. data.message .. "^7")
        StopResource(GetCurrentResourceName())
    end
end)

licenses, products and customers take an action (list, get, create, update, delete) and an arguments table with id, fields, query or force:

exports["kora-fivem"]:licenses({ url = "https://kora.example.com", apiKey = GetConvar("kora_token", "") }, "list", { query = { status = "active" } }, function(success, page)
    if success then print(page.total) end
end)

.NET

dotnet add package ZeroDev.Kora
using Kora;

using var kora = new KoraClient("https://kora.example.com", product: "Test Product 1", publicKey: publicKeyPem);

var result = await kora.ValidateAsync(licenseKey);
if (!result.Valid) throw new Exception(result.Message);

kora.Licenses, kora.Products and kora.Customers have ListAsync, GetAsync, CreateAsync, UpdateAsync and DeleteAsync. Failures throw KoraException with Status.

PHP

composer require zerodev/kora
use ZeroDev\Kora\Client;

$kora = new Client("https://kora.example.com", product: "Test Product 1", publicKey: $publicKeyPem);

$result = $kora->validate($licenseKey);
if (!$result["valid"]) {
    exit($result["message"]);
}

$kora->licenses, $kora->products and $kora->customers have list, get, create, update and delete. Failures throw KoraException with status.

Go

go get github.com/zerodev-ca/Kora-SDKs/packages/go/v3
client := kora.New(kora.Options{URL: "https://kora.example.com", Product: "Test Product 1", PublicKey: publicKeyPem})

result, err := client.Validate(context.Background(), licenseKey, nil)
if err != nil {
    log.Fatal(err)
}
if !result.Valid {
    log.Fatal(result.Message)
}

client.Licenses, client.Products and client.Customers have List, Get, Create, Update and Delete. Failed calls return a *kora.Error with Status.

Rust

cargo add zerodev-kora
let kora = kora::Client::new("https://kora.example.com").product("Test Product 1").public_key(public_key_pem);

let result = kora.validate(&license_key, &[])?;
if !result.valid {
    return Err(result.message.into());
}

kora.licenses(), kora.products() and kora.customers() have list, get, create, update and delete. Failed calls return Error::Status(status, message).