# Core Data

URL: https://zerodev.ca/docs/kora/developer-documentation/core-data

> Issue, find and change licenses, products and customers from your own module.



Licenses, products, policies and customers belong to the `core` module. Work with them through its handlers rather than the models, so keys, defaults, addons and events are handled the same way the dashboard does.

```js
get licenses() {
    return this.kora.modules.handlers.get("core", "licenses");
}
```

Add `"core"` to your manifest's `requires` so your module only loads when core is on.

## Licenses

```js
const license = await this.licenses.issue({ product: "Test Product 1", customer: "customer1@example.com" });
await this.licenses.update(license.key, { status: "suspended" });
await this.licenses.remove(license.key);
```

| Method                          | What it does                                                                                                                                                                                     |
| ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `issue(input)`                  | Create a license. Returns it described.                                                                                                                                                          |
| `update(reference, input)`      | Change a license. Returns it described.                                                                                                                                                          |
| `remove(reference)`             | Delete a license.                                                                                                                                                                                |
| `resolve(reference)`            | Find by ID or key, or return `null`.                                                                                                                                                             |
| `require(reference)`            | The same, but throws a 404 when it is missing.                                                                                                                                                   |
| `describe(rows)`                | Add product, customer, addons and whitelist details to one row or a list.                                                                                                                        |
| `file(reference)`               | A signed offline license file as `{ license, filename, content }`. Throws a 409 for licenses that do not pass validation, and a 403 when offline files are disabled globally or for the product. |
| `deactivate(body, ip, headers)` | Free the seats matching the request, like `POST /api/v1/licenses/deactivate`.                                                                                                                    |

A reference is a license ID or its key. Keys are trimmed and upper-cased before matching.

### Input

| Field                       | On issue                  | What it does                                                                              |
| --------------------------- | ------------------------- | ----------------------------------------------------------------------------------------- |
| `product` or `product_id`   | Required                  | A product ID or name.                                                                     |
| `customer` or `customer_id` | Required                  | A customer ID, Discord ID or email.                                                       |
| `policy` or `policy_id`     | None                      | A policy ID or name from the same product. Throws a 400 if it belongs to another product. |
| `key`                       | Generated                 | Your own key. Throws a 409 if it is taken.                                                |
| `status`                    | First status              | One of the configured statuses.                                                           |
| `addons`                    | Policy's addons           | Addon IDs. IDs from other products are dropped.                                           |
| `max_machines`              | Policy's value, else `0`  | Seats in each whitelist group. `0` uses each group's own limit.                           |
| `expires_at`                | Policy or product default | A date, or `""` to use the policy's duration, or the product's without a policy.          |
| `leaked`                    |                           | `true` flags a leak, `false` clears it and restarts the count.                            |

Changing a license's product keeps only the addons that also belong to the new product, and drops its policy unless you send one for the new product. Changing the policy on an existing license does not copy its defaults again. Moving an expired license's expiry into the future sets it back to the first passing status.

### Described Licenses

`describe`, `issue` and `update` return rows with extra fields:

| Field                           | What it is                                                                                |
| ------------------------------- | ----------------------------------------------------------------------------------------- |
| `status`                        | The effective status. A passing license past its expiry and grace period reads `expired`. |
| `product`                       | `{ id, name, version, active, whitelist_groups }`.                                        |
| `customer`                      | `{ id, name, email, discord_id }`.                                                        |
| `product_name`, `customer_name` | For tables.                                                                               |
| `addons`, `addon_names`         | Addon IDs and their names.                                                                |
| `policy`, `policy_name`         | `{ id, name, type }` and the name, or `null`.                                             |
| `leaked`                        | Whether a leak was flagged.                                                               |
| `whitelists`                    | Whitelisted values by group.                                                              |

### Statuses

| Method          | Returns                                                                       |
| --------------- | ----------------------------------------------------------------------------- |
| `statuses()`    | Every status as `{ value, label, valid, message }`, with `expired` last.      |
| `status(value)` | One status.                                                                   |
| `names()`       | Every status value.                                                           |
| `passing()`     | The values of statuses that pass validation.                                  |
| `options()`     | `{ value, label }` pairs for a `select`.                                      |
| `generate()`    | A new key in the configured format. Use `unique()` for one not already taken. |

### Validating a Key

```js
const result = await this.licenses.validate({ key, product: "Test Product 1", hwid }, ip, headers, false);
if (!result.valid) return this.fail(403, result.message);
```

This runs the same checks as the public validation endpoint, including access rules and blocked customers, writes a validation log entry, runs leak detection and fires `license.validated`. Pass `true` as the last argument to take free whitelist seats first, like `POST /api/v1/licenses/activate`. `result` has the same shape as the [API response](/docs/kora/extensions/core#checking-a-key), without the signature.

## Products

```js
const products = this.kora.modules.handlers.get("core", "products");
const product = await products.create({ name: "Test Product 4", price: 9.99, currency: "USD", duration: 30, duration_unit: "days" });
```

| Method               | What it does                                                                                                                                                |
| -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `create(input)`      | Create a product. `name` is required.                                                                                                                       |
| `update(id, input)`  | Change a product.                                                                                                                                           |
| `remove(id, force)`  | Delete a product. Throws a 409 while it has licenses, unless `force` is `true`, which deletes them too. Its addons and policies are always deleted with it. |
| `resolve(reference)` | Find by ID or exact name.                                                                                                                                   |
| `expiry(product)`    | The expiry date a new license would get.                                                                                                                    |

| Field                            | What it is                                                                                   |
| -------------------------------- | -------------------------------------------------------------------------------------------- |
| `name`, `description`, `version` | Text.                                                                                        |
| `price`                          | Rounded to two decimals.                                                                     |
| `currency`                       | One of `products.currencies`. Defaults to `USD`.                                             |
| `duration`, `duration_unit`      | How long licenses last. `0` is lifetime. Units are `days`, `weeks`, `months` or `years`.     |
| `active`                         | `false` makes every key for it fail with `disabled`.                                         |
| `offline`                        | Boolean. `false` disables offline license file exports for this product. Defaults to `true`. |
| `whitelist_groups`               | Whitelist group IDs.                                                                         |

## Policies

```js
const policies = this.kora.modules.handlers.get("core", "policies");
const policy = await policies.create({ product_id: product.id, name: "Pro", type: "subscription", max_machines: 2, duration: 365, duration_unit: "days" });
await this.licenses.issue({ product: product.id, customer: "customer1@example.com", policy: "Pro" });
```

| Method                          | What it does                                                        |
| ------------------------------- | ------------------------------------------------------------------- |
| `create(input)`                 | Create a policy. `product_id` and `name` are required.              |
| `update(id, input)`             | Change a policy. Moving it to another product unlinks its licenses. |
| `remove(id)`                    | Delete a policy. Its licenses keep their values and lose the link.  |
| `resolve(reference, productId)` | Find a policy of that product by ID or exact name.                  |
| `describe(rows)`                | Add `product_name` and the `licenses` count to one row or a list.   |

| Field                       | What it is                                                                                              |
| --------------------------- | ------------------------------------------------------------------------------------------------------- |
| `type`                      | `subscription` or `perpetual`, from `policies.types`. Perpetual policies always have a duration of `0`. |
| `max_machines`              | Seats in each whitelist group for licenses issued under it. `0` uses each group's own limit.            |
| `duration`, `duration_unit` | Same as on products.                                                                                    |
| `addons`                    | Addon IDs of the same product.                                                                          |

## Customers

```js
const customers = this.kora.modules.handlers.get("core", "customers");
const found = await customers.resolve(discordId);
const customer = found ? found : await customers.create({ discord_id: discordId, name: username });
```

| Method                   | What it does                                                                                                                               |
| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------ |
| `create(input)`          | Create a customer from `name`, `email`, `discord_id`, `notes` and `blocked`. Without a name, the part of the email before the `@` is used. |
| `update(id, input)`      | Change a customer.                                                                                                                         |
| `remove(id, force)`      | Delete a customer. Throws a 409 while they own licenses, unless `force` is `true`.                                                         |
| `resolve(reference)`     | Find by ID, Discord ID or email.                                                                                                           |
| `merge(id, duplicateId)` | Move the duplicate's licenses to the customer, fill in missing details, delete the duplicate and fire `customer.merged`.                   |

A customer with `blocked: true` fails validation with `blocked` on every license they own.

## Other Handlers

| Handler       | Use it for                                                                                                                            |
| ------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| `addons`      | `create`, `update`, `remove` and `details(ids)` for addons.                                                                           |
| `whitelists`  | Groups (`createGroup`, `attach` to a product), entries (`add`, `remove`, `reset`, `forLicense`) and seats (`activate`, `deactivate`). |
| `rules`       | Access rules: `create`, `update`, `remove`, and `match(productId, { body, ip, headers })`, which returns `{ blocked, excluded }`.     |
| `validations` | `record` a validation yourself and `prune` old logs.                                                                                  |

## Errors

Handler methods throw with a status and a plain message, such as a 404 for a missing license. Inside a [route](/docs/kora/developer-documentation/routes) you can let them through, and the client gets that status with `{ error: message }`. Elsewhere, catch them:

```js
try {
    await this.licenses.update(key, { status: "revoked" });
} catch (error) {
    this.kora.console.log(this.kora.console.levels.warning, `Could not revoke ${key}: ${error.message}`);
}
```

## Events

Every change fires an event with the described record, such as `license.created` with `{ license, input }`. See [Events](/docs/kora/developer-documentation/events#events) for the full list.
