Configuration
The core config files that tailor licenses, records, and logs.
Beyond the main config/config.json, core keeps a few self-healing files in config/core/. You
usually will not need to touch these, but they are there when you do. Restart Kora after editing.
| File | Controls |
|---|---|
licenses.json | The format of generated keys (for example XXXXX-XXXXX-XXXXX-XXXXX-XXXXX), the statuses a license can have, and the validation response template. |
customers.json | Extra fields stored on each customer (email, Discord id, ...). |
products.json | Extra fields on each product, plus reusable product templates. |
addons.json | Extra fields stored on each addon (such as a price). |
logs.json | Whether the request and activity logs are on, how many rows each keeps, and what each request captures. |
geo.json | Where Kora downloads the GeoLite2 databases used for geo lookups and geo rules. |
These files are created for you and self-heal, just like the main config. See the site-wide
Configuration guide for more, and
the main config/config.json reference for the app-level settings.
Geo
config/core/geo.json controls the GeoLite2 databases Kora uses to turn a request's IP into a country,
region, and network (ASN). These power the geo columns in the request log and the country, region,
and asn rule types.
| Setting | Default | Description |
|---|---|---|
ENABLE | true | Download and load the geo databases on startup. |
DATABASE_URL | (Zero bucket) | URL of the GeoLite2 City database (.mmdb), used for country and region. |
ASN_URL | (Zero bucket) | URL of the GeoLite2 ASN database (.mmdb). |
Kora downloads each database once into a geo/ folder and reads it locally, so lookups are offline and
fast after the first start. If a database can't be downloaded or loaded, geo lookups return empty and
geo rules are skipped, so nothing breaks.
The databases are free from MaxMind. Host the two
.mmdb files anywhere Kora can reach and point DATABASE_URL / ASN_URL at them.
Request logs
config/core/logs.json holds the request and activity log settings under LOGS.
| Setting | Default | Description |
|---|---|---|
REQUESTS.ENABLE | true | Record incoming API requests. |
REQUESTS.RETENTION | 5000 | How many request rows to keep. |
REQUESTS.GEO | true | Add country, region, and ASN to each request (needs the geo databases above). |
REQUESTS.BODY | true | Store the request body, truncated to 8KB. |
REQUESTS.HEADERS | true | Store the request headers. Sensitive ones (authorization, cookie, ...) are redacted. |
ACTIVITY.ENABLE | true | Record licensing activity (validations, rejections, ...). |
ACTIVITY.RETENTION | 5000 | How many activity rows to keep. |
Each request row is expandable on the Requests dashboard page to show its location, network, headers, and body.
License statuses
config/core/licenses.json under LICENSES.STATUS defines the statuses a license can hold and how
they behave. A license's status is checked on every validation: a negative status rejects the key
and returns its message as the reason.
| Setting | Default | Description |
|---|---|---|
DEFAULT | ACTIVE | The status a new license gets when you don't set one. Must be one of the TYPES. |
TYPES | (list) | Every status a license may have. Assigning a status outside this list is rejected. |
LINK | (map) | Wires the built-in behaviors (active, disabled, expired, expiring) to your status names. |
Each entry in TYPES is:
| Field | Description |
|---|---|
NAME | The status name, used when you create or edit a license. |
POSITIVITY | true if the key passes validation, false to reject it. |
MESSAGE | The message returned on validation. For a negative status, this is the rejection reason. |
LINK maps the four built-in behaviors onto status names, so you can rename them:
| Key | Description |
|---|---|
ACTIVE.STATUS | The normal, valid status. |
DISABLED.STATUS | The status used for a manually disabled key. |
EXPIRED.STATUS | The status a license reports once its expires_at has passed. Validation is rejected. |
EXPIRING.STATUS | The status a still-valid license reports when it is within EXPIRING.WINDOW of expiring. |
EXPIRING.WINDOW | How long before expiry the EXPIRING status kicks in (for example 7d). |
Expiry is resolved automatically from the license's expires_at: a key past its expiry reports the
EXPIRED status and fails validation, while one inside the EXPIRING window reports the EXPIRING
status but still validates.
"STATUS": {
"DEFAULT": "ACTIVE",
"LINK": {
"ACTIVE": { "STATUS": "ACTIVE" },
"DISABLED": { "STATUS": "DISABLED" },
"EXPIRED": { "STATUS": "EXPIRED" },
"EXPIRING": { "STATUS": "EXPIRING", "WINDOW": "7d" }
},
"TYPES": [
{ "POSITIVITY": true, "NAME": "ACTIVE", "MESSAGE": "License is valid & active!" },
{ "POSITIVITY": false, "NAME": "SUSPENDED", "MESSAGE": "License has been suspended." },
{ "POSITIVITY": false, "NAME": "DISABLED", "MESSAGE": "License has been manually disabled." }
]
}Durations use a number plus a unit: ms, s, m, h, or d.
Custom validation response
By default, license validation returns a fixed JSON shape. If your product expects a different shape,
turn on a custom response in config/core/licenses.json under LICENSES.VALIDATION and build the JSON
yourself with placeholders.
| Setting | Default | Description |
|---|---|---|
CUSTOM | false | Use your VALID / INVALID templates instead of the built-in response. |
VALID | (template) | The JSON returned when a key is valid. |
INVALID | (template) | The JSON returned when a key is rejected. |
Any string in a template is rendered with the placeholders below. A value that is exactly one
placeholder keeps its real type (so "%ADDONS%" becomes an array, not a string); placeholders inside a
longer string are substituted as text.
| Placeholder | Value |
|---|---|
%VALID% | true or false. |
%MESSAGE% | The status or rejection message. |
%KEY% | The license key. |
%PRODUCT% | The product name. |
%CUSTOMER% | The customer. |
%STATUS% | The status name (for example ACTIVE). |
%EXPIRES_AT% | The expiry timestamp. |
%ADDONS% | The addons array. |
%USER_ID% | The linked user id, if any. |
%VARIABLES% | The resolved variables object. |
%USER_VARIABLES% | The license's user variables object. |
%SESSION% | The session object ({ token }) when the Sessions extension issues one, otherwise null. |
%TIMESTAMP% | The response time in milliseconds. |
"VALIDATION": {
"CUSTOM": true,
"VALID": {
"success": "%VALID%",
"plan": "%PRODUCT%",
"expires": "%EXPIRES_AT%",
"features": "%VARIABLES%"
},
"INVALID": {
"success": "%VALID%",
"reason": "%MESSAGE%"
}
}
