# 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
Getting started is easy and only takes a few minutes. Here are some resources to help you get up and running quickly.
### LLMs
AI agents can view the Keyforge documentation in Markdown by accessing [`llms-full.txt`](https://docs.keyforge.dev/llms-full.txt).
## Learn more
Some resources to help you learn more about Keyforge and its features.
## 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
Keyforge currently supports two types of licenses:
* **Perpetual**: A license that never expires.
* **Timed**: A license that expires at a specific date.
## 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
To access the portal, customers must request an access link via email. This link is valid for 2 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
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 12 hours.
# Products (/products)
A product is a software or service that you want to license and distribute to your customers.
## Customization
### 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
A note displayed in purchase emails and the portal, usually to guide customers on how to activate their license.
### 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
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 whether to integrate Keyforge directly in your client or 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
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
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 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 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! 🎉
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
### Next steps
Some features to explore and extend your licensing system.
### 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
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 whether to integrate Keyforge directly in your client or server.
***
## 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
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
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,
"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`. The `device` and `license` properties will never be `null` in this case.
### 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,
"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 `device` and `license` properties will be `null` if the license is not valid.
### 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
### Next steps
Some features to explore and extend your licensing system.
### Public API
Learn more about the Public API. An API key is not needed to use it.
# Offline licensing (/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 can be 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
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
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 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
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
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
### 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
# Offline licensing (/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 can be 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
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
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
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/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,
"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
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
The token needs to be refreshed periodically to ensure it remains valid. Use the [license token](/api-reference/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,
"token": "..." // The license token
}
```
## Learn more
### Token payload
A license token contains the following data:
```json
{
"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
# Payments (/addons/payments-one-time)
Keyforge uses Stripe restricted API keys with only the necessary permissions.
The secret keys are securely stored and encrypted with AES-256.
Keyforge integrates with Stripe for automatic license generation. No code or webhook setup needed.
***
## Setup one-time purchases
Upon purchasing a Stripe product, a license is automatically created on Keyforge, and the customer receives a thank you email with the license key.
Make sure you have a Stripe account, a product set up on Stripe, and a corresponding product on Keyforge.
### Connect Stripe account
Go to [payments](https://keyforge.dev/dashboard/integrations/stripe) and click on "Connect account". A Stripe webhook pointing to Keyforge will be automatically created.
### Connect Stripe product
Click on "Connect product".
Your Stripe accounts and products with their prices will be automatically listed. Select the Stripe product price and the corresponding Keyforge product that customers will receive a license for upon purchase.
Choose the options for the created licenses.
### You're done! 🎉
It's as simple as that! Create a payment link for your product and start selling. When a customer purchases the product, a license will be automatically created on Keyforge and they will get emailed the license key.
## Learn more
### Timed licenses
You can choose between two options for creating timed licenses using the Stripe integration:
1. **Offset**: The expiration date of the license will be set to the creation date plus the defined offset.
2. **Fixed**: All created licenses will share the same expiration date, as specified in the options.
If a customer purchases a fixed timed license that has already expired, they
will get an inactive license that **will not work**.
### Invoices
If you enable invoice generation in the Stripe payment link, customers will be able to download their invoice from the [portal](/portal). You can also download the invoice from the dashboard.
# Payments (/addons/payments-subscription)
Keyforge uses Stripe restricted API keys with only the necessary permissions.
The secret keys are securely stored and encrypted with AES-256.
Keyforge integrates with Stripe for automatic license generation. No code or webhook setup needed.
***
## Setup subscriptions
Upon subscribing to a Stripe product, a license is automatically created on Keyforge, and the customer receives a thank you email with the license key.
Make sure you have a Stripe account, a recurring product set up on Stripe, and a corresponding product on Keyforge.
### Connect Stripe account
Go to [payments](https://keyforge.dev/dashboard/integrations/stripe) and click on "Connect account". A Stripe webhook pointing to Keyforge will be automatically created.
### Connect Stripe subscription
Click on "Connect product".
Your Stripe accounts and products with their prices will be automatically listed. Select the Stripe recurring price and the corresponding Keyforge product that customers will receive a license for upon subscribing.
Don't forget to enable the [billing
portal](https://dashboard.stripe.com/settings/billing/portal) on your Stripe
account, customers will use it to manage their subscriptions.
### You're done! 🎉
It's as simple as that! Create a payment link for your product and start selling. When a customer subscribes to the product, a license will be automatically created on Keyforge and they will get emailed the license key.
## Learn more
### License expiration & renewals
Licenses are **timed** and expire if the subscription isn't renewed. The expiration date updates automatically when an invoice is paid.
### Subscription management
#### Changing plans
If customers switch plans in the Stripe billing portal, ensure each plan has a connected corresponding Keyforge product. The license will be updated accordingly.
#### Plan quantity
For plans with a quantity greater than 1, the license **maximum active devices** will be multiplied by the quantity.
#### Disconnecting & unlinking
* Removing a connected Stripe subscription 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.
### Free Trials
You can offer free trials by setting them up in Stripe. No change is needed on Keyforge.
# Activate license (/api-reference/licenses/activate-license)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Activate a license for a device.
# Create license (/api-reference/licenses/create-license)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Create a new license.
# Delete license (/api-reference/licenses/delete-license)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Delete an existing license.
# Get license (/api-reference/licenses/get-license)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Retrieve information about a license.
# Remove device (/api-reference/licenses/remove-device)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Remove a specific active device from a license.
# Reset devices (/api-reference/licenses/reset-devices)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Reset all active devices for a license.
# Update license (/api-reference/licenses/update-license)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Update an existing license.
# Validate license (/api-reference/licenses/validate-license)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Verify if a license is valid.
# Create private session (/api-reference/portal/create-private-session)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Create a private portal session showing only your Keyforge products.
# Create product (/api-reference/products/create-product)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Create a new product.
# Delete product (/api-reference/products/delete-product)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Delete an existing product.
# Get product (/api-reference/products/get-product)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Retrieve information about a product.
# List products (/api-reference/products/list-products)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Retrieve a list of products.
# Update product (/api-reference/products/update-product)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Update an existing product.
# Activate license (/api-reference/public/public-activate-license)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Activate a license for a device.
# Get license token (/api-reference/public/public-license-token)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Get a new signed token for a license. License tokens need to be configured for the product to use this endpoint.
# Get license token public key (/api-reference/public/public-product-license-token-public-key)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
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.
# Validate license (/api-reference/public/public-validate-license)
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
Verify if a license is valid.