# Examples (/examples)
import { Flame, AppWindow, WifiOff } from 'lucide-react';
This is a collection of example apps using Keyforge. You can use these examples as a reference for your own projects.
} href="https://github.com/Nic13Gamer/keyforge-examples/tree/main/examples/electron-app" description="Basic desktop application built with Electron. Uses the Public API, no backend server required." />
} href="https://github.com/Nic13Gamer/keyforge-examples/tree/main/examples/offline-electron-app" description="Simple desktop application built with Electron. Licenses are validated offline. No backend server required." />
# Introduction (/)
Welcome to the Keyforge docs! This is a collection of guides and resources to help you get started with Keyforge and the API.
Get started [#get-started]
Getting started is easy and only takes a few minutes. Here are some resources to help you get up and running quickly.
LLMs [#llms]
AI agents can view the Keyforge documentation in Markdown by accessing [`llms-full.txt`](https://docs.keyforge.dev/llms-full.txt).
Learn more [#learn-more]
Some resources to help you learn more about Keyforge and its features.
API Reference [#api-reference]
Guides on how to use the Keyforge API.
# Licenses (/licenses)
Each license is associated with a product and can be activated on a defined number of devices. A license can also have an email associated with it, allowing customers to manage their licenses in the [portal](/portal).
License types [#license-types]
Keyforge currently supports two types of licenses:
* **Perpetual**: A license that never expires.
* **Timed**: A license that expires at a specific date.
Devices [#devices]
A license can be activated on multiple devices, as defined by the maximum active devices.
Each active device on a license has 3 properties:
* **Identifier**: A unique identifier for the device. Must have at most 96 characters.
* **Name**: A name for the device. Does not need to be unique. Must have at most 64 characters.
* **Activation date**: The date when the device was activated.
The device identifier needs to be unique inside the license scope. There are
various ways to get a unique identifier, such as the MAC address, HWID, serial
number, or a randomly generated UUID stored in the device.
# Portal (/portal)
Customers can view and manage their licenses in the Keyforge portal. To access it, customers can request an email with a link to the portal. All licenses linked to their email address will be available.
If you prefer not to make your products available for customers to manage in
the portal, you can hide them in the
[dashboard](https://keyforge.dev/dashboard/portal). You can also prevent
customers from resetting active devices.
Accessing the portal [#accessing-the-portal]
To access the portal, customers must request an access link via email. This link is valid for 48 hours. Customers can request the portal access link here: [https://keyforge.dev/portal/request](https://keyforge.dev/portal/request).
You can pre-fill the email field in the portal request form by adding the
`email` query parameter to the URL. For example:
[https://keyforge.dev/portal/request?email=john-doe@example.com](https://keyforge.dev/portal/request?email=john-doe@example.com).
Private sessions [#private-sessions]
You can also create a private portal session for a specific email address via the [dashboard](https://keyforge.dev/dashboard/portal) or [API](/api-reference/portal/create-private-session). Customers will only be able to view and manage licenses from your products. The generated link is valid for 48 hours.
# Products (/products)
A product is a software or service that you want to license and distribute to your customers.
Perpetual fallback [#perpetual-fallback]
When enabled on a product, timed licenses that have expired will still be valid, but shown as limited. You decide how to limit your product's functionality based on your needs. The status returned by the API for these licenses is `fallbacked`.
This feature is recommended to be used together with [license
renewals](/addons/payment/updates?type=renewal) to give your customers a way
to extend their licenses after expiration.
Customization [#customization]
Support email [#support-email]
A customer support email displayed in purchase emails and the portal. It's optional, but if not provided, customers will be instructed to contact you through your Keyforge account email.
Purchase note [#purchase-note]
A note displayed in purchase emails and the portal, usually to guide customers on how to activate their license.
Action button [#action-button]
A button displayed in purchase emails and the portal, usually used as a download button. You can define its text and link.
You can add the license key to the action button link by using the `{key}`
placeholder. For example, `https://example.com/download?key={key}`.
Purchase email example [#purchase-email-example]
Here is an example of a customized purchase email sent to customers:
If you're in the **Plus** tier, you can add an image to your product. This
image is currently only displayed in purchase emails.
# Quickstart (/quickstart-server)
You can set up Keyforge in your project within minutes. This guide will walk you through the steps to get started.
Choose the appropriate quickstart guide for your use case:
***
Managing licenses in the server [#managing-licenses-in-the-server]
To manage licenses in your server, use the Keyforge API. You need an API key to use it.
Prerequisites [#prerequisites]
Before you begin, make sure you have completed the following steps:
* Get an API key [here](https://keyforge.dev/dashboard/api-keys).
* Create a product [here](https://keyforge.dev/dashboard/products).
Create a license [#create-a-license]
In this example, we will create a license that never expires and can only have 1 device active at the same time. Copy the returned license key.
cURL
JavaScript
```bash
curl -X POST https://keyforge.dev/api/v1/licenses \
-H "Authorization: Bearer sk_1234" \
-H "Content-Type: application/json" \
-d '{
"productId": "p_123456",
"type": "perpetual",
"maxDevices": 1,
"email": "john-doe@example.com"
}'
```
```js
const res = await fetch('https://keyforge.dev/api/v1/licenses', {
method: 'POST',
headers: {
Authorization: 'Bearer sk_1234',
'Content-Type': 'application/json',
},
body: JSON.stringify({
productId: 'p_123456',
type: 'perpetual',
maxDevices: 1,
email: 'john-doe@example.com',
}),
});
const license = await res.json();
console.log(license.key);
```
Example response from the API when creating a license:
```json
{
"key": "ABCDE-ABCDE-ABCDE-ABCDE-ABCDE",
"userId": "05d27bfb-61c7-45f7-9d07-09a41defc88a",
"productId": "p_123456",
"type": "perpetual",
"expiresAt": null,
"revoked": false,
"email": "john-doe@example.com",
"maxDevices": 1,
"activeDevices": [],
"createdAt": "2024-05-19T18:39:33.000Z"
}
```
Activate a license [#activate-a-license]
Activate the license you have just created. Replace `LICENSE_KEY` with the license key you copied in the previous step. In this example, we will activate the license on a device with the identifier `some_device_id` and the name `My computer name`.
cURL
JavaScript
```bash
curl -X POST "https://keyforge.dev/api/v1/licenses/activate" \
-H "Authorization: Bearer sk_1234" \
-H "Content-Type: application/json" \
-H "License-Key: LICENSE_KEY" \
-d '{
"productId": "p_123456",
"device": {
"identifier": "some_device_id",
"name": "My computer name"
}
}'
```
```js
await fetch('https://keyforge.dev/api/v1/licenses/activate', {
method: 'POST',
headers: {
Authorization: 'Bearer sk_1234',
'Content-Type': 'application/json',
'License-Key': 'LICENSE_KEY',
},
body: JSON.stringify({
productId: 'p_123456',
device: {
identifier: 'some_device_id',
name: 'My computer name',
},
}),
});
```
Example response from the API when activating a license:
```json
{
"key": "ABCDE-ABCDE-ABCDE-ABCDE-ABCDE",
"userId": "05d27bfb-61c7-45f7-9d07-09a41defc88a",
"productId": "p_123456",
"type": "perpetual",
"expiresAt": null,
"revoked": false,
"email": "john-doe@example.com",
"maxDevices": 1,
"activeDevices": [
{
"identifier": "some_device_id",
"name": "My computer name",
"activatedAt": "2024-05-19T18:39:33.000Z"
}
],
"createdAt": "2024-05-19T18:39:33.000Z"
}
```
Validate a license [#validate-a-license]
Validate the license you have just activated. Replace `LICENSE_KEY` with the license key. In this example, we will verify the license on the device with the identifier `some_device_id`.
cURL
JavaScript
```bash
curl -X POST "https://keyforge.dev/api/v1/licenses/validate" \
-H "Authorization: Bearer sk_1234" \
-H "Content-Type: application/json" \
-H "License-Key: LICENSE_KEY" \
-d '{
"productId": "p_123456",
"deviceIdentifier": "some_device_id"
}'
```
```js
const res = await fetch('https://keyforge.dev/api/v1/licenses/validate', {
method: 'POST',
headers: {
Authorization: 'Bearer sk_1234',
'Content-Type': 'application/json',
'License-Key': 'LICENSE_KEY',
},
body: JSON.stringify({
productId: 'p_123456',
deviceIdentifier: 'some_device_id',
}),
});
const validation = await res.json();
console.log(validation.isValid);
```
Example response from the API when validating a license:
```json
{
"isValid": true,
"status": "active",
"device": {
"identifier": "some_device_id",
"name": "My computer name",
"activationDate": "2024-05-19T18:39:33.000Z"
},
"license": {
"key": "ABCDE-ABCDE-ABCDE-ABCDE-ABCDE",
"userId": "05d27bfb-61c7-45f7-9d07-09a41defc88a",
"productId": "p_123456",
"type": "perpetual",
"expiresAt": null,
"revoked": false,
"email": "john-doe@example.com",
"maxDevices": 1,
"activeDevices": [
{
"identifier": "some_device_id",
"name": "My computer name",
"activationDate": "2024-05-19T18:39:33.000Z"
}
],
"createdAt": "2024-05-19T18:39:33.000Z"
}
}
```
Congratulations! 🎉 [#congratulations-]
You have successfully created, activated, and validated your first license. You can manage all licenses in the [dashboard](https://keyforge.dev/dashboard/licenses).
Learn more [#learn-more]
Next steps [#next-steps]
Some features to explore and extend your licensing system.
Public API [#public-api]
Keyforge also provides a public API that you can use directly in your app, without the need to create a backend.
API Reference [#api-reference]
Learn more about the Keyforge API.
# Quickstart (/quickstart)
You can set up Keyforge in your project within minutes. This guide will walk you through the steps to get started.
Choose the appropriate quickstart guide for your use case:
***
Licensing a client application [#licensing-a-client-application]
To validate and activate licenses in the client, use the [Public API](/api-reference/public/public-validate-license) directly in your app. You can use this API anywhere, with any programming language.
Prerequisites [#prerequisites]
Before you begin, make sure you have completed the following steps:
* Create a product [here](https://keyforge.dev/dashboard/products).
* Create a license [here](https://keyforge.dev/dashboard/licenses).
Activate a license [#activate-a-license]
The first time a user opens your app, they should be prompted to activate their license.
The device identifier needs to be unique inside the license scope. You can use a MAC address, HWID, or any other type of identifier that is unique to a device.
cURL
JavaScript
Python
Go
Swift
Rust
```bash
curl -X POST https://keyforge.dev/api/v1/public/licenses/activate \
-H "Content-Type: application/json" \
-d '{
"licenseKey": "ABCDE-ABCDE-ABCDE-ABCDE-ABCDE",
"deviceIdentifier": "some_device_id",
"deviceName": "My device",
"productId": "p_123456"
}'
```
```js
await fetch('https://keyforge.dev/api/v1/public/licenses/activate', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
licenseKey: 'ABCDE-ABCDE-ABCDE-ABCDE-ABCDE',
deviceIdentifier: 'some_device_id',
deviceName: 'My device',
productId: 'p_123456',
}),
});
console.log('License activated');
```
```py
import requests
requests.post(
'https://keyforge.dev/api/v1/public/licenses/activate',
json={
'licenseKey': 'ABCDE-ABCDE-ABCDE-ABCDE-ABCDE',
'deviceIdentifier': 'some_device_id',
'deviceName': 'My device',
'productId': 'p_123456',
},
headers={
'Content-Type': 'application/json',
},
)
print('License activated')
```
```go
package main
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
)
func main() {
data := map[string]string{
"licenseKey": "ABCDE-ABCDE-ABCDE-ABCDE-ABCDE",
"deviceIdentifier": "some_device_id",
"deviceName": "My device",
"productId": "p_123456",
}
jsonData, _ := json.Marshal(data)
req, _ := http.NewRequest("POST", "https://keyforge.dev/api/v1/public/licenses/activate", bytes.NewBuffer(jsonData))
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
fmt.Println("Request failed:", err)
return
}
defer resp.Body.Close()
body, _ := ioutil.ReadAll(resp.Body)
if resp.StatusCode == http.StatusOK {
fmt.Println("License activated successfully:", string(body))
} else {
fmt.Println("Error activating license:", resp.Status, string(body))
}
}
```
```swift
import Foundation
Task {
let url = URL(string: "https://keyforge.dev/api/v1/public/licenses/activate")!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
let body: [String: Any] = [
"licenseKey": "ABCDE-ABCDE-ABCDE-ABCDE-ABCDE",
"deviceIdentifier": "some_device_id",
"deviceName": "My device",
"productId": "p_123456"
]
request.httpBody = try! JSONSerialization.data(withJSONObject: body)
_ = try! await URLSession.shared.data(for: request)
print("License activated")
}
```
```rust
use reqwest::Client;
use serde_json::json;
#[tokio::main]
async fn main() {
let client = Client::new();
client
.post("https://keyforge.dev/api/v1/public/licenses/activate")
.json(&json!({
"licenseKey": "ABCDE-ABCDE-ABCDE-ABCDE-ABCDE",
"deviceIdentifier": "some_device_id",
"deviceName": "My device",
"productId": "p_123456"
}))
.send()
.await
.unwrap();
println!("License activated");
}
```
The `productId` property can also be an array of IDs if your app supports
multiple products, like different tiers.
Example response from the API if the activation is successful.
```json
{
"isValid": true,
"status": "active",
"device": {
"identifier": "some_device_id",
"name": "My computer name",
"activationDate": "2023-05-19T18:39:33.000Z"
},
"license": {
"key": "ABCDE-ABCDE-ABCDE-ABCDE-ABCDE",
"productId": "p_123456",
"type": "perpetual",
"revoked": false,
"maxDevices": 5,
"expiresAt": null,
"createdAt": "2023-05-19T18:39:33.000Z"
}
}
```
If the activation is successful, `isValid` is always `true`, and the `status`, `device`, and `license` properties will never be `null`.
Validate a license [#validate-a-license]
When your app starts, you can verify the license to check if it's valid.
You can validate a license whenever you like, for example, every hour, to ensure that it is still valid.
cURL
JavaScript
Python
Go
Swift
Rust
```bash
curl -X POST https://keyforge.dev/api/v1/public/licenses/validate \
-H "Content-Type: application/json" \
-d '{
"licenseKey": "ABCDE-ABCDE-ABCDE-ABCDE-ABCDE",
"deviceIdentifier": "some_device_id",
"productId": "p_123456"
}'
```
```js
const res = await fetch(
'https://keyforge.dev/api/v1/public/licenses/validate',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
licenseKey: 'ABCDE-ABCDE-ABCDE-ABCDE-ABCDE',
deviceIdentifier: 'some_device_id',
productId: 'p_123456',
}),
},
);
const data = await res.json();
console.log(data.isValid);
```
```py
import requests
res = requests.post(
'https://keyforge.dev/api/v1/public/licenses/validate',
json={
'licenseKey': 'ABCDE-ABCDE-ABCDE-ABCDE-ABCDE',
'deviceIdentifier': 'some_device_id',
'productId': 'p_123456',
},
headers={
'Content-Type': 'application/json',
},
)
data = res.json()
print(data['isValid'])
```
```go
package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
)
func main() {
data := map[string]string{
"licenseKey": "ABCDE-ABCDE-ABCDE-ABCDE-ABCDE",
"deviceIdentifier": "some_device_id",
"productId": "p_123456",
}
jsonData, _ := json.Marshal(data)
req, _ := http.NewRequest("POST", "https://keyforge.dev/api/v1/public/licenses/validate", bytes.NewBuffer(jsonData))
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
fmt.Println("Request failed:", err)
return
}
defer resp.Body.Close()
var result map[string]interface{}
json.NewDecoder(resp.Body).Decode(&result)
fmt.Println(result["isValid"])
}
```
```swift
import Foundation
Task {
let url = URL(string: "https://keyforge.dev/api/v1/public/licenses/validate")!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
let body: [String: Any] = [
"licenseKey": "ABCDE-ABCDE-ABCDE-ABCDE-ABCDE",
"deviceIdentifier": "some_device_id",
"productId": "p_123456"
]
request.httpBody = try! JSONSerialization.data(withJSONObject: body)
let (data, _) = try! await URLSession.shared.data(for: request)
let json = try! JSONSerialization.jsonObject(with: data) as! [String: Any]
print(json["isValid"] ?? "Missing isValid")
}
```
```rust
use reqwest::Client;
use serde_json::json;
#[tokio::main]
async fn main() {
let client = Client::new();
let res = client
.post("https://keyforge.dev/api/v1/public/licenses/validate")
.json(&json!({
"licenseKey": "ABCDE-ABCDE-ABCDE-ABCDE-ABCDE",
"deviceIdentifier": "some_device_id",
"productId": "p_123456"
}))
.send()
.await
.unwrap()
.json::()
.await
.unwrap();
println!("{}", res["isValid"]);
}
```
The `productId` property can also be an array of IDs if your app supports
multiple products, like different tiers.
Example response from the API if the license is valid.
```json
{
"isValid": true,
"status": "active",
"device": {
"identifier": "some_device_id",
"name": "My computer name",
"activationDate": "2023-05-19T18:39:33.000Z"
},
"license": {
"key": "ABCDE-ABCDE-ABCDE-ABCDE-ABCDE",
"productId": "p_123456",
"type": "perpetual",
"revoked": false,
"maxDevices": 5,
"expiresAt": null,
"createdAt": "2023-05-19T18:39:33.000Z"
}
}
```
The `status`, `device`, and `license` properties will be `null` if the license is not valid.
Congratulations! 🎉 [#congratulations-]
You have successfully activated and validated your first license inside your app. You can manage all licenses in the [dashboard](https://keyforge.dev/dashboard/licenses).
Learn more [#learn-more]
Next steps [#next-steps]
Some features to explore and extend your licensing system.
Public API [#public-api]
Learn more about the Public API. An API key is not needed to use it.
# License tokens (/addons/license-token-js-sdk)
It's common to have apps that need to work even without an internet connection. Keyforge makes it easy to validate licenses oflline.
To make this possible, Keyforge can issue a signed **license token** that is verified on the client. The token is a JWT and contains information about the license.
An internet connection is only required to activate the license and to
occasionally refresh the license token.
Getting started [#getting-started]
There is a client SDK available for JavaScript, but license tokens can be used in any programming language that supports JWTs.
Configure license tokens for a product [#configure-license-tokens-for-a-product]
Go to [license tokens](https://keyforge.dev/dashboard/addons/license-token) and add a new product. You can edit how much time a token will be valid for, and other options after creating the new configuration.
For setups with more than one product, you can duplicate the signing key pair
from another product inside the **edit** menu. You can also import an external
key pair.
Install the client SDK [#install-the-client-sdk]
Install the Keyforge client SDK in your project:
npm
pnpm
yarn
bun
```bash
npm i @keyforge/client
```
```bash
pnpm add @keyforge/client
```
```bash
yarn add @keyforge/client
```
```bash
bun add @keyforge/client
```
Retrieve initial token [#retrieve-initial-token]
The simplest way to get and store the first license token for a device is after activating a license. The SDK provides `activateLicense`.
```js
import { activateLicense } from '@keyforge/client';
const { isValid, token } = await activateLicense({
licenseKey: 'ABCDE-ABCDE-ABCDE-ABCDE-ABCDE',
deviceIdentifier: 'some_device_id',
deviceName: 'My device',
productId: 'p_123456',
});
if (isValid && token) {
storeToken(token); // Store the token on the device
console.log('License activated successfully!');
}
```
Validate and refresh tokens [#validate-and-refresh-tokens]
The SDK provides a simple way to validate and automatically refresh license tokens. You should call `validateAndRefreshToken` when your app starts.
```js
import { validateAndRefreshToken } from '@keyforge/client/token';
const PUBLIC_KEY = '...'; // Copied from the dashboard. In JSON string or object format
const { isValid, token, data, isValidButExpired } =
await validateAndRefreshToken({
token: getStoredToken(), // The current license token
publicKeyJwk: PUBLIC_KEY,
deviceIdentifier: 'some_device_id',
productId: 'p_123456',
});
if (isValid) {
storeToken(token); // Store the new token if it was refreshed
console.log('License token is valid!', data.license.key);
} else if (!isValidButExpired) {
// A network error probably occurred. The token is expired, but was valid
// You should NOT prompt the user to activate a license
}
// If the token is not valid, you should prompt the user to activate a license
```
For periodic token checks, it's not recommended to use `validateAndRefreshToken`. Instead, use `verifyToken`, which only checks the validity of the token without refreshing it.
```js
import { verifyToken } from '@keyforge/client/token';
const PUBLIC_KEY = '...';
const { isValid, data } = await verifyToken({
token: getStoredToken(),
publicKeyJwk: PUBLIC_KEY,
deviceIdentifier: 'some_device_id',
productId: 'p_123456',
});
if (isValid) {
console.log('License token is valid!', data.license.key);
}
```
To retrieve a new token from the Keyforge API, you can use `fetchToken`.
```js
import { fetchToken } from '@keyforge/client/token';
const { isValid, token } = await fetchToken({
licenseKey: 'ABCDE-ABCDE-ABCDE-ABCDE-ABCDE',
deviceIdentifier: 'some_device_id',
productId: 'p_123456',
});
if (isValid) {
storeToken(token);
}
```
The `fetchToken` function does not verify if the new token is valid. You
should always verify the new token.
Learn more [#learn-more]
Example [#example]
An example desktop app built with Electron using the client SDK and license tokens is available [here](https://github.com/Nic13Gamer/keyforge-examples/tree/main/examples/offline-electron-app).
API Reference [#api-reference]
# License tokens (/addons/license-token-no-sdk)
It's common to have apps that need to work even without an internet connection. Keyforge makes it easy to validate licenses oflline.
To make this possible, Keyforge can issue a signed **license token** that is verified on the client. The token is a JWT and contains information about the license.
An internet connection is only required to activate the license and to
occasionally refresh the license token.
Getting started [#getting-started]
There is a client SDK available for JavaScript, but license tokens can be used in any programming language that supports JWTs.
Configure license tokens for a product [#configure-license-tokens-for-a-product]
Go to [license tokens](https://keyforge.dev/dashboard/addons/license-token) and add a new product. You can edit how much time a token will be valid for, and other options after creating the new configuration.
For setups with more than one product, you can duplicate the signing key pair
from another product inside the **edit** menu. You can also import an external
key pair.
Retrieve initial token [#retrieve-initial-token]
The simplest way to get and store the first license token for a device is after activating a license. You should use the [activate license](/api-reference/public/public-activate-license) API endpoint.
```bash
curl -X POST https://keyforge.dev/api/v1/public/licenses/activate \
-H "Content-Type: application/json" \
-d '{
"licenseKey": "ABCDE-ABCDE-ABCDE-ABCDE-ABCDE",
"deviceIdentifier": "some_device_id",
"deviceName": "My device",
"productId": "p_123456"
}'
```
A `token` property will be returned in the response. Store this token in the device's storage.
Example response from the API if the activation is successful.
```json
{
"isValid": true,
"status": "active",
"token": "...", // The license token
"device": {
"identifier": "some_device_id",
"name": "My device",
"activationDate": "2023-05-19T18:39:33.000Z"
},
"license": {
"key": "ABCDE-ABCDE-ABCDE-ABCDE-ABCDE",
"productId": "p_123456",
"type": "perpetual",
"revoked": false,
"maxDevices": 5,
"expiresAt": null,
"createdAt": "2023-05-19T18:39:33.000Z"
}
}
```
Verify the token [#verify-the-token]
To verify the token, you can use any [JWT library](https://jwt.io/libraries) available in your programming language. Here are some tips you should follow:
* The token is signed using an **ES256** key pair. The public key is in the [dashboard](https://keyforge.dev/dashboard/addons/license-token).
* Check the `exp` claim to see if the token is still valid.
* Check the `productId` and `deviceIdentifier` to make sure the token is valid for the current product and device.
* Do not ask the user to activate a license if the token is expired but was valid at some point.
The token should be verified when the app starts, but it can also be verified
periodically.
Refresh the token [#refresh-the-token]
The token needs to be refreshed periodically to ensure it remains valid. Use the [license token](/api-reference/public/public-license-token) API endpoint.
You should refresh the token some time before it expires, for example, 3 days
before the expiration date.
```bash
curl -X POST https://keyforge.dev/api/v1/public/licenses/token \
-H "Content-Type: application/json" \
-d '{
"licenseKey": "ABCDE-ABCDE-ABCDE-ABCDE-ABCDE",
"deviceIdentifier": "some_device_id",
"productId": "p_123456"
}'
```
A `token` property will be returned in the response. Store this token in the device's storage.
Example response from the API if the token was signed successfully.
```json
{
"isValid": true,
"status": "active",
"token": "..." // The license token
}
```
Learn more [#learn-more]
Token payload [#token-payload]
A license token contains the following data:
```json
{
"status": "active",
"license": {
"key": "ABCDE-ABCDE-ABCDE-ABCDE-ABCDE",
"productId": "p_123456",
"type": "perpetual",
"expiresAt": null,
"createdAt": 1684521573, // Unix timestamp in seconds
"maxDevices": 5,
"email": null
},
"device": {
"identifier": "some_device_id",
"name": "My device",
"activationDate": 1684521573 // Unix timestamp in seconds
}
}
```
There are also some additional claims in the token, such as `exp` (expiration
time) and `iat` (issued at time). It is signed using the **ES256** algorithm.
API Reference [#api-reference]
# License updates (/addons/payment/updates)
Keyforge also handles license upgrades and renewals for one-time purchases through your payment provider. Customers can easily upgrade their licenses or renew them when they expire, all through the portal.
This guide is only for one-time purchase products. For subscriptions, this is
already built-in. See
[here](/addons/payment/setup/subscription#additional-information) for more
information.
Set up license updates [#set-up-license-updates]
The process is similar to the [initial setup](/addons/payment/setup/one-time), but with the "Update license" action selected. Select whether you want to set up renewals or upgrades below.
import { RotateCw, ArrowUpCircle } from 'lucide-react';
Renewals
>
),
},
{
value: 'upgrade',
label: (
<>
Upgrades
>
),
},
]}
/>
Configure which properties to update [#configure-which-properties-to-update]
Enable which properties of the license should be updated when a customer purchases the connected payment product.
For renewals, it's recommended to use an offset expiration. This way, the license expiration date will be extended by the duration configured. There can be up to 3 renewal options per Keyforge product.
License renewals can also be combined with [perpetual
fallback](/products#perpetual-fallback) to allow customers to keep using the
product with limited functionality after their license expires.
For upgrades, it's recommended to tweak the options in **"portal settings"**, so the customer knows what they're upgrading to. There can be up to 3 upgrade options per Keyforge product.
Here's an example of how a customer sees their upgrade options in the portal:
You're done! 🎉 [#youre-done-]
That's it! When a customer with an expired license purchases the connected payment product, their license will be automatically updated according to your configuration.
Renewals are offered only to customers who have an expired license.
That's it! When a customer with an existing license purchases the connected payment product, their license will be automatically updated according to your configuration.
Trigger updates from outside the portal [#trigger-updates-from-outside-the-portal]
By default, the Keyforge portal creates checkout sessions for your customers. If you create a checkout session outside the portal and want it to update a license, you must include a custom metadata field `keyforge_license_key` containing the license key to update.
If a renewal/upgrade is purchased through an external checkout that wouldn't
be available in the portal, the license **will not** be updated.
# Set up payments (/addons/payment/setup/one-time)
import { StripeIcon, LemonSqueezyIcon, PolarIcon } from '@/components/icons';
Keyforge can integrate with various payment providers to automatically generate, upgrade, and renew licenses. No code or webhook setup needed.
Before starting, select the payment provider you're using for a personalized guide.
Stripe
>
),
},
{
value: 'lmsqzy',
label: (
<>
Lemon Squeezy
>
),
},
{
value: 'polar',
label: (
<>
Polar
>
),
},
]}
/>
Keyforge uses Stripe restricted API keys with only the necessary permissions. Secret keys are securely stored and encrypted with AES-256.
All Lemon Squeezy API keys are securely stored and encrypted with AES-256.
Keyforge uses Polar Organization Access Tokens with only the necessary permissions. Tokens are securely stored and encrypted with AES-256.
***
Creating licenses for one-time payments [#creating-licenses-for-one-time-payments]
After purchasing a product through your payment provider, a license is automatically created on Keyforge, and the customer receives an email with their license details.
Make sure you have an account with your chosen payment provider, a product set up there, and a corresponding product on Keyforge.
Connect payment account [#connect-payment-account]
Go to [payment accounts](https://keyforge.dev/dashboard/addons/payment/accounts), click on "Connect account", and follow the instructions. A webhook pointing to Keyforge will be automatically created.
After connecting your account, go into **Settings** and enable invoices to generate invoices for one-time checkout sessions created by Keyforge (e.g., license renewals and upgrades). Additional Stripe fees may apply.
If you are connecting an account in the Polar Sandbox environment, you might notice an `sb_` prefix in the account ID after connecting. This is expected behavior for sandbox accounts.
Connect payment product [#connect-payment-product]
Go to [payment products](https://keyforge.dev/dashboard/addons/payment/products), click on your Keyforge product, select the corresponding product from your payment provider, and choose the options for created licenses.
If you're looking to set up upgrades or renewals for one-time purchases, take
a look at [license updates](/addons/payment/updates).
You're done! 🎉 [#youre-done-]
It's as simple as that! Create a payment link on your provider and start selling. When a customer purchases the product, a license will be automatically created on Keyforge and they will get emailed.
Do not change a product to a subscription after it has been connected to
Keyforge, as this may lead to unexpected behavior. Reconnect the product
if you desire to switch to subscriptions.
Additional information [#additional-information]
Invoices [#invoices]
If invoices are enabled in your payment provider, customers are able to download their invoices from the [portal](/portal). You can also download invoices from the dashboard.
Timed licenses [#timed-licenses]
You can choose between two options if you want to create timed licenses with payments:
1. **Offset expiration**: The expiration date of the license will be set to the creation date plus the defined offset.
2. **Fixed expiration**: The expiration date of the license will be set to a fixed date, regardless of the purchase date.
If the fixed expiration date has already passed at the time of purchase, the
created license will be expired.
# Set up payments (/addons/payment/setup/subscription)
import { StripeIcon, LemonSqueezyIcon, PolarIcon } from '@/components/icons';
Keyforge can integrate with various payment providers to automatically generate, upgrade, and renew licenses. No code or webhook setup needed.
Before starting, select the payment provider you're using for a personalized guide.
Stripe
>
),
},
{
value: 'lmsqzy',
label: (
<>
Lemon Squeezy
>
),
},
{
value: 'polar',
label: (
<>
Polar
>
),
},
]}
/>
Keyforge uses Stripe restricted API keys with only the necessary permissions. Secret keys are securely stored and encrypted with AES-256.
All Lemon Squeezy API keys are securely stored and encrypted with AES-256.
Keyforge uses Polar Organization Access Tokens with only the necessary permissions. Tokens are securely stored and encrypted with AES-256.
***
Linking licenses with subscriptions [#linking-licenses-with-subscriptions]
After subscribing to a product through your payment provider, a license is automatically created on Keyforge, and the customer receives an email with their license details.
Make sure you have an account with your chosen payment provider, a recurring product set up there, and a corresponding product on Keyforge.
Connect payment account [#connect-payment-account]
Go to [payment accounts](https://keyforge.dev/dashboard/addons/payment/accounts), click on "Connect account", and follow the instructions. A webhook pointing to Keyforge will be automatically created.
After connecting your account, go into **Settings** and enable invoices to generate invoices for one-time checkout sessions created by Keyforge (e.g., license renewals and upgrades). Additional Stripe fees may apply.
If you are connecting an account in the Polar Sandbox environment, you might notice an `sb_` prefix in the account ID after connecting. This is expected behavior for sandbox accounts.
Connect subscription payment product [#connect-subscription-payment-product]
Go to [payment products](https://keyforge.dev/dashboard/addons/payment/products), click on your Keyforge product, select the corresponding subscription product from your payment provider, and choose the options for created licenses.
Make sure to enable the [billing
portal](https://dashboard.stripe.com/settings/billing/portal) on your Stripe
account, customers use it to manage their subscriptions.
Make sure to enable the [customer portal](https://app.lemonsqueezy.com/design/customer-portal) on your Lemon Squeezy
store, customers use it to manage their subscriptions.
You're done! 🎉 [#youre-done-]
It's as simple as that! Create a payment link on your provider and start selling. When a customer subscribes to the product, a license will be automatically created on Keyforge and they will get emailed.
Do not change a product to a one-time purchase after it has been connected
to Keyforge, as this may lead to unexpected behavior. Reconnect the
product if you desire to switch to one-time purchases.
Additional information [#additional-information]
Managing subscriptions [#managing-subscriptions]
Changing plans [#changing-plans]
If customers switch plans in the portal, ensure each plan has a connected corresponding Keyforge product. The license will be updated accordingly.
Plan quantity [#plan-quantity]
For plans with a quantity greater than 1, the license **maximum active devices** will be multiplied by the quantity.
Disconnecting & unlinking [#disconnecting--unlinking]
* Disconnecting a subscription payment product won't stop license updates on renewal.
* To **unlink a subscription** from a license, do so on the licenses page, but note that the license will no longer update on renewal, and the customer may continue paying.
License expiration [#license-expiration]
Generated licenses are **timed** and expire if the subscription isn't renewed. The expiration date updates automatically when an invoice is paid.
Free trials [#free-trials]
You can offer free trials by setting them up in your payment provider. No change is needed on Keyforge.
# OpenAPI Specification
{
"openapi": "3.1.1",
"info": {
"title": "Keyforge API",
"version": "1.0.0"
},
"servers": [
{
"url": "https://keyforge.dev"
}
],
"tags": [
{
"name": "public",
"x-displayName": "Public"
},
{
"name": "portal",
"x-displayName": "Portal"
},
{
"name": "products",
"x-displayName": "Products"
},
{
"name": "licenses",
"x-displayName": "Licenses"
}
],
"paths": {
"/api/v1/public/licenses/validate": {
"post": {
"tags": [
"public"
],
"operationId": "public-validate-license",
"summary": "Validate license",
"description": "Verify if a license is valid.",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"licenseKey": {
"type": "string",
"description": "The license key.",
"example": "ABCDE-ABCDE-ABCDE-ABCDE-ABCDE"
},
"deviceIdentifier": {
"type": "string",
"description": "The identifier of the device to validate the license for.",
"example": "some_device_id"
},
"productId": {
"oneOf": [
{
"type": "string",
"example": "p_123456"
},
{
"type": "array",
"items": {
"type": "string"
},
"example": [
"p_123456",
"p_654321"
]
}
],
"description": "The product ID of the license. Can be a single string or an array of strings."
}
},
"required": [
"licenseKey",
"deviceIdentifier",
"productId"
]
}
}
}
},
"responses": {
"200": {
"description": "Success. The license can be valid or invalid. For invalid licenses, `status`, `device`, and `license` are null.",
"content": {
"application/json": {
"schema": {
"type": "object",
"description": "The response to a license validation request.",
"properties": {
"isValid": {
"type": "boolean",
"description": "Whether the license is valid.",
"example": true
},
"status": {
"$ref": "#/components/schemas/LicenseStatus",
"nullable": true
},
"device": {
"$ref": "#/components/schemas/PublicApiDevice"
},
"license": {
"$ref": "#/components/schemas/PublicApiLicense"
}
},
"required": [
"isValid",
"status",
"device",
"license"
]
}
}
}
},
"400": {
"description": "Bad Request"
}
}
}
},
"/api/v1/public/licenses/activate": {
"post": {
"tags": [
"public"
],
"operationId": "public-activate-license",
"summary": "Activate license",
"description": "Activate a license on a device.",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"licenseKey": {
"type": "string",
"description": "The key of the license to activate.",
"example": "ABCDE-ABCDE-ABCDE-ABCDE-ABCDE"
},
"deviceIdentifier": {
"type": "string",
"description": "A unique identifier of the device. Must have at most 96 characters.",
"example": "some_device_id"
},
"deviceName": {
"type": "string",
"description": "The name of the device. Must have at most 64 characters.",
"example": "My computer name"
},
"productId": {
"oneOf": [
{
"type": "string",
"example": "p_123456"
},
{
"type": "array",
"items": {
"type": "string"
},
"example": [
"p_123456",
"p_654321"
]
}
],
"description": "The product ID of the license. Can be a single string or an array of strings."
}
},
"required": [
"licenseKey",
"deviceIdentifier",
"deviceName",
"productId"
]
}
}
}
},
"responses": {
"200": {
"description": "Success. The license is valid and has been activated on the device.\n\nThe `token` will only be present if license tokens are configured for the product.",
"content": {
"application/json": {
"schema": {
"type": "object",
"description": "The response to a license activation request.",
"properties": {
"isValid": {
"type": "boolean",
"description": "Whether the license is valid. Always true for this endpoint.",
"example": true
},
"status": {
"$ref": "#/components/schemas/LicenseStatus"
},
"token": {
"type": "string",
"description": "If license tokens are configured for the product, this will contain the token. Otherwise, this property will not be present.",
"example": "..."
},
"device": {
"$ref": "#/components/schemas/PublicApiDevice"
},
"license": {
"$ref": "#/components/schemas/PublicApiLicense"
}
},
"required": [
"isValid",
"status",
"device",
"license"
]
}
}
}
},
"400": {
"description": "Bad Request. If the license is invalid, or another error occurs.",
"content": {
"application/json": {
"schema": {
"type": "object",
"description": "The response to a bad request.",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string",
"description": "The error code.",
"enum": [
"invalid_license",
"license_revoked",
"license_expired",
"max_devices_reached",
"unknown_error"
],
"example": "license_revoked"
},
"message": {
"type": "string",
"description": "The error message.",
"example": "License is revoked."
}
},
"required": [
"code",
"message"
]
}
}
}
}
}
}
}
}
},
"/api/v1/public/licenses/token": {
"post": {
"tags": [
"public"
],
"operationId": "public-license-token",
"summary": "Get license token",
"description": "Get a new signed token for a license. License tokens need to be configured for the product to use this endpoint.",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"licenseKey": {
"type": "string",
"description": "The license key.",
"example": "ABCDE-ABCDE-ABCDE-ABCDE-ABCDE"
},
"deviceIdentifier": {
"type": "string",
"description": "The identifier of the current device.",
"example": "some_device_id"
},
"productId": {
"oneOf": [
{
"type": "string",
"example": "p_123456"
},
{
"type": "array",
"items": {
"type": "string"
},
"example": [
"p_123456",
"p_654321"
]
}
],
"description": "The product ID of the license. Can be a single string or an array of strings."
}
},
"required": [
"licenseKey",
"deviceIdentifier",
"productId"
]
}
}
}
},
"responses": {
"200": {
"description": "Success. The license token is returned.",
"content": {
"application/json": {
"schema": {
"type": "object",
"description": "The response to a license token request.",
"properties": {
"isValid": {
"type": "boolean",
"description": "Always true for this endpoint, as it only returns a token for valid licenses.",
"example": true
},
"status": {
"$ref": "#/components/schemas/LicenseStatus"
},
"token": {
"type": "string",
"description": "The signed JWT for the license.",
"example": "..."
}
},
"required": [
"isValid",
"status",
"token"
]
}
}
}
},
"400": {
"description": "Bad Request. If the license is invalid, the product does not support license tokens, or another error occurs.",
"content": {
"application/json": {
"schema": {
"type": "object",
"description": "The response to a bad request.",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string",
"description": "The error code.",
"enum": [
"invalid_license",
"license_revoked",
"license_expired"
],
"example": "invalid_license"
},
"message": {
"type": "string",
"description": "The error message.",
"example": "Invalid license."
}
},
"required": [
"code",
"message"
]
}
}
}
}
}
}
}
}
},
"/api/v1/public/products/{productId}/license-token/public-key": {
"get": {
"tags": [
"public"
],
"operationId": "public-product-license-token-public-key",
"summary": "Get license token public key",
"description": "Get the public key JWK of a product for verifying license tokens. This endpoint is only available if the product supports license tokens and exposes its public key.",
"parameters": [
{
"$ref": "#/components/parameters/ProductIdParam"
}
],
"responses": {
"200": {
"description": "Success. The public key JWK is returned.",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"jwk": {
"type": "object",
"description": "The public key JWK used to verify license tokens for the product.",
"properties": {
"kty": {
"type": "string",
"example": "EC"
},
"x": {
"type": "string",
"example": "..."
},
"y": {
"type": "string",
"example": "..."
},
"crv": {
"type": "string",
"example": "P-256"
},
"alg": {
"type": "string",
"example": "ES256"
}
},
"required": [
"kty",
"x",
"y",
"crv",
"alg"
]
}
},
"required": [
"jwk"
]
}
}
}
},
"404": {
"description": "Not Found. If the product does not exist, does not support license tokens, or does not expose its public key."
}
}
}
},
"/api/v1/products": {
"get": {
"tags": [
"products"
],
"security": [
{
"ApiKey": []
}
],
"operationId": "list-products",
"summary": "List products",
"description": "Retrieve a list of products.",
"responses": {
"200": {
"description": "Success. The list of products is returned.",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ApiProduct"
}
}
}
}
}
}
},
"post": {
"tags": [
"products"
],
"security": [
{
"ApiKey": []
}
],
"operationId": "create-product",
"summary": "Create product",
"description": "Create a new product.",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "The name of the product.",
"example": "My product"
},
"description": {
"type": "string",
"description": "The description of the product."
},
"supportEmail": {
"type": "string",
"nullable": true,
"description": "The support email for the product."
}
},
"required": [
"name"
]
}
}
}
},
"responses": {
"200": {
"description": "Success. The product has been created.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiProduct"
}
}
}
}
}
}
},
"/api/v1/products/{productId}": {
"get": {
"tags": [
"products"
],
"security": [
{
"ApiKey": []
}
],
"operationId": "get-product",
"summary": "Get product",
"description": "Retrieve information about a product.",
"parameters": [
{
"$ref": "#/components/parameters/ProductIdParam"
}
],
"responses": {
"200": {
"description": "Success. The product information is returned.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiProduct"
}
}
}
},
"404": {
"description": "Not Found."
}
}
},
"patch": {
"tags": [
"products"
],
"security": [
{
"ApiKey": []
}
],
"operationId": "update-product",
"summary": "Update product",
"description": "Update an existing product.",
"parameters": [
{
"$ref": "#/components/parameters/ProductIdParam"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "The name of the product."
},
"description": {
"type": "string",
"description": "The description of the product."
},
"supportEmail": {
"type": "string",
"nullable": true,
"description": "The support email for the product."
},
"portalShow": {
"type": "boolean",
"description": "Whether the product is shown in the portal."
},
"portalAllowDeviceReset": {
"type": "boolean",
"description": "Whether device resets are allowed for the product in the portal."
}
}
}
}
}
},
"responses": {
"200": {
"description": "Success. The product has been updated.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiProduct"
}
}
}
},
"404": {
"description": "Not Found."
}
}
},
"delete": {
"tags": [
"products"
],
"security": [
{
"ApiKey": []
}
],
"operationId": "delete-product",
"summary": "Delete product",
"description": "Delete an existing product.",
"parameters": [
{
"$ref": "#/components/parameters/ProductIdParam"
}
],
"responses": {
"200": {
"description": "Success. The product has been deleted."
},
"404": {
"description": "Not Found."
}
}
}
},
"/api/v1/licenses": {
"post": {
"tags": [
"licenses"
],
"security": [
{
"ApiKey": []
}
],
"operationId": "create-license",
"summary": "Create license",
"description": "Create a new license.",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"productId": {
"type": "string",
"description": "The product ID for which to create the license.",
"example": "p_123456"
},
"type": {
"type": "string",
"description": "The type of the license. Can be `perpetual` or `timed`.",
"enum": [
"perpetual",
"timed"
],
"example": "perpetual"
},
"expiresAt": {
"type": "string",
"description": "The date when the license expires. Must be null for perpetual licenses.",
"example": null
},
"maxDevices": {
"type": "integer",
"description": "The maximum number of devices that can activate this license.",
"example": 5
},
"email": {
"type": "string",
"nullable": true,
"description": "The email associated with the license.",
"example": "john-doe@example.com"
}
},
"required": [
"productId",
"type",
"maxDevices"
]
}
}
}
},
"responses": {
"200": {
"description": "Success. The license has been created.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiLicense"
}
}
}
}
}
},
"get": {
"tags": [
"licenses"
],
"security": [
{
"ApiKey": []
}
],
"operationId": "get-license",
"summary": "Get license",
"description": "Retrieve information about a license.",
"parameters": [
{
"$ref": "#/components/parameters/LicenseKeyHeader"
}
],
"responses": {
"200": {
"description": "Success. The license information is returned.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiLicense"
}
}
}
}
}
},
"patch": {
"tags": [
"licenses"
],
"security": [
{
"ApiKey": []
}
],
"operationId": "update-license",
"summary": "Update license",
"description": "Update an existing license.",
"parameters": [
{
"$ref": "#/components/parameters/LicenseKeyHeader"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"type": {
"type": "string",
"description": "The type of the license. Can be `perpetual` or `timed`.",
"enum": [
"perpetual",
"timed"
]
},
"maxDevices": {
"type": "integer",
"description": "The maximum number of devices that can activate this license."
},
"email": {
"type": "string",
"nullable": true,
"description": "The email associated with the license."
},
"expiresAt": {
"type": "string",
"description": "The date when the license expires. Must be null for perpetual licenses."
},
"revoked": {
"type": "boolean",
"description": "Whether the license has been revoked."
}
}
}
}
}
},
"responses": {
"200": {
"description": "Success. The license has been updated.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiLicense"
}
}
}
}
}
},
"delete": {
"tags": [
"licenses"
],
"security": [
{
"ApiKey": []
}
],
"operationId": "delete-license",
"summary": "Delete license",
"description": "Delete an existing license.",
"parameters": [
{
"$ref": "#/components/parameters/LicenseKeyHeader"
}
],
"responses": {
"200": {
"description": "Success. The license has been deleted."
}
}
}
},
"/api/v1/licenses/validate": {
"post": {
"tags": [
"licenses"
],
"security": [
{
"ApiKey": []
}
],
"operationId": "validate-license",
"summary": "Validate license",
"description": "Verify if a license is valid.",
"parameters": [
{
"$ref": "#/components/parameters/LicenseKeyHeader"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"deviceIdentifier": {
"type": "string",
"description": "The identifier of the device to validate the license for. Optional, but recommended."
},
"productId": {
"type": "string",
"description": "The product ID of the license. Optional, but recommended."
}
}
}
}
}
},
"responses": {
"200": {
"description": "Success. The license can be valid or invalid.",
"content": {
"application/json": {
"schema": {
"type": "object",
"description": "The response to a license validation request.",
"properties": {
"isValid": {
"type": "boolean",
"description": "Whether the license is valid.",
"example": true
},
"status": {
"$ref": "#/components/schemas/LicenseStatus"
},
"device": {
"$ref": "#/components/schemas/ApiDevice"
},
"license": {
"$ref": "#/components/schemas/ApiLicense"
}
},
"required": [
"isValid",
"status",
"device",
"license"
]
}
}
}
}
}
}
},
"/api/v1/licenses/activate": {
"post": {
"tags": [
"licenses"
],
"security": [
{
"ApiKey": []
}
],
"operationId": "activate-license",
"summary": "Activate license",
"description": "Activate a license on a device.",
"parameters": [
{
"$ref": "#/components/parameters/LicenseKeyHeader"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"productId": {
"type": "string",
"description": "The product ID of the license.",
"example": "p_123456"
},
"device": {
"type": "object",
"properties": {
"identifier": {
"type": "string",
"description": "A unique identifier of the device. Must have at most 96 characters.",
"example": "some_device_id"
},
"name": {
"type": "string",
"description": "The name of the device. Must have at most 64 characters.",
"example": "My computer name"
}
},
"required": [
"identifier",
"name"
]
}
},
"required": [
"productId",
"device"
]
}
}
}
},
"responses": {
"200": {
"description": "Success. The license is valid and has been activated on the device.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiLicense"
}
}
}
},
"400": {
"description": "Bad Request. If the license is invalid, or another error occurs.",
"content": {
"application/json": {
"schema": {
"type": "object",
"description": "The response to a bad request.",
"properties": {
"error": {
"type": "object",
"properties": {
"message": {
"type": "string",
"description": "The error message.",
"example": "License is revoked."
}
},
"required": [
"message"
]
}
}
}
}
}
}
}
}
},
"/api/v1/licenses/devices/reset": {
"post": {
"tags": [
"licenses"
],
"security": [
{
"ApiKey": []
}
],
"operationId": "reset-devices",
"summary": "Reset devices",
"description": "Reset all active devices for a license.",
"parameters": [
{
"$ref": "#/components/parameters/LicenseKeyHeader"
}
],
"responses": {
"200": {
"description": "Success. All active devices for the license have been reset.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiLicense"
}
}
}
}
}
}
},
"/api/v1/licenses/devices/{deviceId}/remove": {
"post": {
"tags": [
"licenses"
],
"security": [
{
"ApiKey": []
}
],
"operationId": "remove-device",
"summary": "Remove device",
"description": "Remove a specific active device from a license.",
"parameters": [
{
"$ref": "#/components/parameters/LicenseKeyHeader"
},
{
"name": "deviceId",
"in": "path",
"description": "The identifier of the device to remove.",
"required": true,
"schema": {
"type": "string",
"example": "some_device_id"
}
}
],
"responses": {
"200": {
"description": "Success. The device has been removed from the license.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiLicense"
}
}
}
}
}
}
},
"/api/v1/portal/sessions/private": {
"post": {
"tags": [
"portal"
],
"security": [
{
"ApiKey": []
}
],
"operationId": "create-private-session",
"summary": "Create private session",
"description": "Create a private portal session showing only your Keyforge products.",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"email": {
"type": "string",
"description": "The email address.",
"example": "john-doe@example.com"
}
},
"required": [
"email"
]
}
}
}
},
"responses": {
"200": {
"description": "Success. The private session has been created.",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"url": {
"type": "string",
"description": "The URL of the portal session.",
"example": "https://keyforge.dev/portal?token=..."
}
},
"required": [
"url"
]
}
}
}
}
}
}
}
},
"components": {
"schemas": {
"LicenseStatus": {
"type": "string",
"description": "The status of a license.",
"enum": [
"active",
"expired",
"revoked",
"fallbacked"
],
"example": "active"
},
"PublicApiDevice": {
"type": "object",
"properties": {
"identifier": {
"type": "string",
"description": "The identifier of the device.",
"example": "some_device_id"
},
"name": {
"type": "string",
"description": "The name of the device.",
"example": "My computer name"
},
"activationDate": {
"type": "string",
"description": "The date when the license was activated on this device.",
"example": "2024-05-19T18:39:33.000Z"
}
},
"required": [
"identifier",
"name",
"activationDate"
]
},
"PublicApiLicense": {
"type": "object",
"properties": {
"key": {
"type": "string",
"description": "The license key.",
"example": "ABCDE-ABCDE-ABCDE-ABCDE-ABCDE"
},
"productId": {
"type": "string",
"description": "The product ID of the license.",
"example": "p_123456"
},
"type": {
"type": "string",
"description": "The type of the license. Can be `perpetual` or `timed`.",
"example": "perpetual"
},
"revoked": {
"type": "boolean",
"description": "Whether the license has been revoked.",
"example": false
},
"maxDevices": {
"type": "integer",
"description": "The maximum number of devices that can activate this license.",
"example": 5
},
"expiresAt": {
"type": "string",
"nullable": true,
"description": "The date when the license expires. Null if the license is perpetual.",
"example": null
},
"createdAt": {
"type": "string",
"description": "The date when the license was created.",
"example": "2024-05-19T18:39:33.000Z"
}
},
"required": [
"key",
"productId",
"type",
"revoked",
"maxDevices",
"expiresAt",
"createdAt"
]
},
"ApiProduct": {
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "The unique identifier of the product.",
"example": "p_123456"
},
"userId": {
"type": "string",
"description": "The Keyforge user ID of the product.",
"example": "05d27bfb-61c7-45f7-9d07-09a41defc88a"
},
"name": {
"type": "string",
"description": "The name of the product.",
"example": "My product"
},
"description": {
"type": "string",
"description": "The description of the product.",
"example": "This is my product."
},
"supportEmail": {
"type": "string",
"nullable": true,
"description": "The support email for the product.",
"example": null
},
"createdAt": {
"type": "string",
"description": "The date when the product was created.",
"example": "2024-05-19T04:31:03.000Z"
},
"portalShow": {
"type": "boolean",
"description": "Whether the product is shown in the portal.",
"example": true
},
"portalAllowDeviceReset": {
"type": "boolean",
"description": "Whether device resets are allowed for the product in the portal.",
"example": true
},
"hasImage": {
"type": "boolean",
"description": "Whether the product has an image.",
"example": false
},
"fallbackEnabled": {
"type": "boolean",
"description": "Whether expired timed licenses for this product are still valid, but limited.",
"example": false
},
"purchaseNote": {
"type": "string",
"nullable": true,
"description": "The purchase note for the product.",
"example": null
},
"actionButtonText": {
"type": "string",
"nullable": true,
"description": "The text for the action button of the product.",
"example": null
},
"actionButtonUrl": {
"type": "string",
"nullable": true,
"description": "The URL for the action button of the product.",
"example": null
}
},
"required": [
"id",
"userId",
"name",
"description",
"supportEmail",
"createdAt",
"portalShow",
"portalAllowDeviceReset",
"hasImage",
"fallbackEnabled",
"purchaseNote",
"actionButtonText",
"actionButtonUrl"
]
},
"ApiDevice": {
"type": "object",
"properties": {
"identifier": {
"type": "string",
"description": "The unique identifier of the device.",
"example": "some_device_id"
},
"name": {
"type": "string",
"description": "The name of the device.",
"example": "My computer name"
},
"activationDate": {
"type": "string",
"description": "The date when the device was activated.",
"example": "2024-05-19T18:39:33.000Z"
}
},
"required": [
"identifier",
"name",
"activationDate"
]
},
"ApiLicense": {
"type": "object",
"properties": {
"key": {
"type": "string",
"description": "The license key.",
"example": "ABCDE-ABCDE-ABCDE-ABCDE-ABCDE"
},
"userId": {
"type": "string",
"description": "The Keyforge user ID of the license.",
"example": "05d27bfb-61c7-45f7-9d07-09a41defc88a"
},
"productId": {
"type": "string",
"description": "The product ID of the license.",
"example": "p_123456"
},
"type": {
"type": "string",
"description": "The type of the license. Can be `perpetual` or `timed`.",
"example": "perpetual"
},
"expiresAt": {
"type": "string",
"nullable": true,
"description": "The date when the license expires. Null if the license is perpetual.",
"example": null
},
"revoked": {
"type": "boolean",
"description": "Whether the license has been revoked.",
"example": false
},
"email": {
"type": "string",
"nullable": true,
"description": "The email associated with the license.",
"example": "john-doe@example.com"
},
"maxDevices": {
"type": "integer",
"description": "The maximum number of devices that can activate this license.",
"example": 5
},
"activeDevices": {
"type": "array",
"description": "The list of currently active devices for this license.",
"items": {
"$ref": "#/components/schemas/ApiDevice"
}
},
"createdAt": {
"type": "string",
"description": "The date when the license was created.",
"example": "2024-05-19T18:39:33.000Z"
}
},
"required": [
"key",
"userId",
"productId",
"type",
"expiresAt",
"revoked",
"email",
"maxDevices",
"activeDevices",
"createdAt"
]
}
},
"securitySchemes": {
"ApiKey": {
"type": "http",
"scheme": "bearer",
"description": "Your API key."
}
},
"parameters": {
"ProductIdParam": {
"name": "productId",
"in": "path",
"description": "The product ID.",
"required": true,
"schema": {
"type": "string",
"example": "p_123456"
}
},
"LicenseKeyHeader": {
"name": "License-Key",
"in": "header",
"description": "The license key.",
"required": true,
"schema": {
"type": "string",
"example": "ABCDE-ABCDE-ABCDE-ABCDE-ABCDE"
}
}
}
},
"x-ext-urls": {}
}