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/activateandPOST /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/productsand/api/v1/customers, including customer merge.
The source is in zerodev-ca/Kora-SDKs.
| Platform | Package | Version |
|---|---|---|
| Node.js and TypeScript | @zerodev-ca/kora | 3.1.1 |
| Java 17+ (Paper, Spigot, Velocity, Folia) | ca.zerodev:kora-java | 3.1.1 |
| Python 3.8+ | zerodev-kora | 3.1.1 |
| FiveM and RedM | kora-fivem | 3.1.1 |
| .NET 8 | ZeroDev.Kora | 3.1.1 |
PHP 8.1+ with sodium | zerodev/kora | 3.1.1 |
| Go 1.21+ | github.com/zerodev-ca/Kora-SDKs/packages/go/v3 | 3.1.1 |
| Rust | zerodev-kora | 3.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.js | Java | Python | FiveM | |
|---|---|---|---|---|
Extra fields such as hwid | validate(key, product, { hwid }) | validate(key, product, Map.of("hwid", id)) | validate(key, hwid=id) | config.fields |
| Take a seat and validate | activate(key, product, fields) | activate(key, fields) | activate(key, **fields) | activate(config, key, callback) |
| Free a seat | deactivate(key, fields) | deactivate(key, fields) | deactivate(key, **fields) | deactivate(config, key, callback) |
| Download a license file | file(id) | file(id) | file(id) | |
| Check a license file offline | offline(file) | offline(file) | offline(file) | offline(config, file, callback) |
| Merge customers | merge(id, duplicateId) | merge(id, duplicateId) | merge(id, duplicate_id) | customers(config, "merge", { id, customer_id }) |
| .NET | PHP | Go | Rust | |
|---|---|---|---|---|
| Validate | ValidateAsync(key, product, fields) | validate($key, $fields) | Validate(ctx, key, fields) | validate(&key, &[("hwid", id)]) |
| Take a seat and validate | ActivateAsync(key, product, fields) | activate($key, $fields) | Activate(ctx, key, fields) | activate(&key, fields) |
| Free a seat | DeactivateAsync(key, fields) | deactivate($key, $fields) | Deactivate(ctx, key, fields) | deactivate(&key, fields) |
| Download a license file | FileAsync(id) | file($id) | File(ctx, id) | file(id) |
| Check a license file offline | Offline(file) | offline($file) | Offline(file) | offline(&file) |
| Merge customers | MergeAsync(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/koraimport { 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-korafrom 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.Korausing 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/korause 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/v3client := 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-koralet 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).