# Introduction

### Welcome to the LicenseSpring Documentation!

We strive to make our documentation complete, concise, up to date, and useful.

We maintain this publicly available documentation for the benefit of our existing and prospective customers.

### What is LicenseSpring?

LicenseSpring® is a general-purpose license manager. It primarily allows software vendors to easily control the state of their applications according to their license agreements.

The service is offered as a SaaS in a managed service or as a single tenancy option. Some of our most popular capabilities include:

* On-premises floating license servers
* Air-gapped license activation
* Ability to license individual features within your software
* A white labeled end-user portal for your customers to manage their own licenses
* A distributor/reseller portal
* License consumptions/usage metering per component
* Analytics in aggregate or per individual licenses
* Ability to add single-sign-on as part of the license authentication process

You can watch the following videos to get an idea of everything you can do with LicenseSpring:

{% embed url="<https://youtu.be/PTPx4EnM5Ro>" %}

{% embed url="<https://www.youtube.com/watch?v=7EVFGdu91XQ>" %}

### Why Does LicenseSpring Exist?

#### Inception

LicenseSpring was initially developed as a homegrown License Manager, built for PDF Pro, a popular Windows-based PDF editor (which still uses LicenseSpring to this day!).

#### Purpose

We want to supercharge your software. Concretely, we want to be a platform that makes life easier for all internal and external users of a software department in an organization interacting with the product.

### What is Cense Data Inc.?

Cense Data Inc DBA LicenseSpring is the company that develops and commercializes the LicenseSpring service. The company was founded in 2015 by [Edmon Moren](https://www.linkedin.com/in/edmonmoren/), and has offices in Vancouver, Canada.

### What is Kraken Ltd.?

Kraken Systems ltd. is the software engineering agency that develops and maintains the core LicenseSpring service and infrastructure. The company was founded by [Marin Bek](https://www.linkedin.com/in/m-bek/). Kraken Systems ltd. is [ISO 27001 and ISO 9001 certified](https://krakensystems.co/quality-and-information-security-policy), and has offices in Croatia, in the cities of Zagreb and Čakovec.


# Basic Concepts

If you are new to LicenseSpring, or to software licensing in general, you might find it helpful to review the following concepts so that you can get up and running as quickly as possible.

### Product

![A Defined Product within the LicenseSpring Platform](/files/09355e1d97c3b4142fdbd27474bea26fd5a2a41d)

This is what you wish to add software licensing to. A product can be a web app, a desktop application, a plugin, a device/hardware or a microservice for example. A product can have many possible attributes, but here are a few that you should be aware of:

#### Product Name

This is what you call your app. It can be anything, and is not particularly consequential for configuring your LicenseSpring account. The Product name can be edited at any time by a user of the vendor platform.

#### Product Code

The product code is important as it is a required field for most of the License API calls.

{% hint style="warning" %}
You cannot change a product code once you have defined a product in LicenseSpring, so choose it wisely!
{% endhint %}

#### Product Authorization

LicenseSpring supports key-based or user-based authorization:

* **Key-based licenses** use a license key to authorize a license with the License API. It is mostly used for Node-Locking licenses to a machine or device.
* **User-based licenses** require that an email is assigned to a license. The user's email and password (or Single Sign On) is used as part of the Authorization Process.

### Licenses

![An example license detail page](/files/add5553613eba3a50e534e16b6abbb305e8c4817)

Licenses serve as a record of entitlements granted for a given product. These license entitlements can be issued, modified, transferred, revoked, and contain many configuration options. Here are a few of the main options to be aware of.

#### License Types

There are 4 elementary types of licenses which can be issued using LicenseSpring:

* **Perpetual**: These licenses do not have expiry dates.
* **Time-Limited**: These licenses have a fixed end date. This end date can affect the license status.
* **Subscription**: The license status is determined by an external source of truth (like a recurring billing system such as Stripe). Subscriptions also have subscription periods recorded.
* **Consumption**: Allows for usage metering, such as counting the times a license application is run, or number of API requests.

#### License Max Activations

Max activations is a field determining how many unique devices can be bound to a license.

#### License Total Activations

Total activations is also a field that belongs to a license, and counts how many distinct devices have been bound to a given license.

{% hint style="info" %}
Total activations will always be less than or equal to max activations.
{% endhint %}

#### License Status

This is the state of a given license. There are many different states covered in different section in these docs. The main ones to be aware of:

* **Inactive**: The License has not been bound to any device but can currently be used. Specifically, the `total activations` field is 0.
* **Active**: The License has been bound to at least 1 device, and can currently be used (is not disabled or expired).
* **Expired**: For licenses that can expire (eg: time limited licenses).
* **Disabled**: The Software vendor can set a license as disabled, which overrides other statuses. A Subscription license is disabled when the subscription is no longer active.

### Activation

The License Activation is the act of binding a license to a device, also known as node-locking. License Activation requires:

* HardwareID
* The license authorization method (either a license key, or user identification like email / password or Single Sign On)
* The product code

#### Hardware ID

This is a unique identifier of a device that activated a license, and is sent on almost all requests to the license API.

### LicenseSpring's RESTful APIs

There are two primary RESTful APIs maintained to interface between LicenseSpring and your product and technology:

<details>

<summary><strong>License API</strong></summary>

The License API is what your client-facing application will use to connect to LicenseSpring's cloud. It allows for a limited set of operations such as license checks, activations, or to count consumptions. It uses a signing and verifying key in order to communicate with the server securely.

</details>

<details>

<summary><strong>Management API</strong></summary>

The Management API is intended for all of your back office operations, such as creating new licenses, exporting license usage to a BI tool, or integrating with your internal ERP, CRM, or Recurring billing for instance. It is not intended to be customer facing.

</details>

### LicenseSpring's SDKs

We have developed and maintain several wrappers to the APIs to make it easier to add LicenseSpring to your software, as well as to make it easier to use. For example the SDKs will automatically compute the hardwareID, enable local license checks, prevent clock tampering, or cache consumptions, to name a few of the additional features that the SDKs provide.

### Events & Webhooks

#### Events API

**At a Glance**\
The Events API provides real-time notifications of important actions and changes within the LicenseSpring system, such as new customer creation, license activations, or license expirations. These events form the foundation for building automations, integrations, and custom workflows.

**Technical Overview**

* Events are generated automatically whenever key actions occur (e.g., customer creation, order updates, license lifecycle changes).
* These events are dispatched in real time and can be consumed directly by your applications.
* Events power downstream services such as **webhooks**, enabling external systems to respond promptly.
* Common use cases include integrating with CRMs, automating internal operations, or triggering user engagement flows.

#### Webhooks

**At a Glance**\
Webhooks provide a simple way for LicenseSpring to notify your systems about events as they happen. Instead of polling APIs, your application receives HTTPS requests containing event details in JSON format, making it easier to stay in sync with license activity.

**Technical Overview**

* You configure a webhook endpoint (listener URL) within your LicenseSpring account.
* You select which events you want to subscribe to (e.g., license created, license expired, customer added).
* When those events occur, LicenseSpring automatically sends an HTTPS POST request with a JSON payload to your endpoint.
* Your system can then parse and act on the event—such as updating records, sending alerts, or triggering workflows.

{% hint style="info" %}
To set up and configure webhooks, click the link for detailed instructions: [**Webhooks**](/events-api/webhooks)
{% endhint %}

### Integrations

LicenseSpring connects seamlessly with leading platforms to streamline licensing operations and boost efficiency.

* **Salesforce:** Manage customer relationships and licensing data in one place while gaining actionable insights.
* **FastSpring:** Automate license provisioning, order processing, and subscription management for smoother operations.
* **Stripe:** Simplify payment handling, subscription billing, and secure financial transactions.
* **Shopify:** Easily manage licenses within your online store, making product monetization simple.

Why Choose LicenseSpring Integrations?

* **Ease of Use:** Simple setup for quick adoption.
* **Efficiency:** Reduce manual work and improve productivity.
* **Scalability:** Works for both startups and large enterprises.
* **Reliability:** Secure, well-maintained integrations you can trust.

{% hint style="info" %}
Find an up-to-date list of native integrations supported by LicenseSpring, along with detailed setup instructions, here: [**Integrations**](/integrations/introduction)
{% endhint %}


# Opening an Account

While our publicly available documentation includes browsing capabilities and the option to download our SDKs, certain essential functionalities like obtaining API keys, defining products, and issuing new licenses require users to create an account with us.

Once you have an account, you can access these additional features to integrate our services seamlessly into your applications.

### Types of Accounts Available

As of the current date, we offer four types of accounts, each tailored to cater to specific requirements related to the sophistication of the software entitlement mechanism desired by the vendor, as well as the volume of API requests needed.

For a detailed understanding of the available tiers and their associated features, I recommend referring to our [**pricing page**](https://licensespring.com/pricing/).

There, you will find comprehensive information about the different account types, their capabilities, and the corresponding costs to help you choose the one that best suits your needs.

### How to Open an Account

{% stepper %}
{% step %}

### Complete the contact form

Fill out our [**contact form**](https://licensespring.com/get-started/).
{% endstep %}

{% step %}

### Review

Once received, our staff will review your information to ensure LicenseSpring is the right fit for your needs.
{% endstep %}

{% step %}

### Account creation & credentials

Upon verification, we will create your account and send you the login credentials.
{% endstep %}

{% step %}

### Activate your account

You will receive an account activation invitation via the email you provided. This activation invitation is valid for **three days** — please activate your account within that period. If it expires, contact us to request a new activation invite.
{% endstep %}
{% endstepper %}

{% hint style="info" %}
Note: You will receive an account activation invitation via the email you provided. This activation invitation is valid for **three days** — please activate your account within that period. If it expires, contact us to request a new activation invite.
{% endhint %}

![LicenseSpring Sign-Up Page](/files/e3888dab12f557b0a4b9909a274cefa03fd50237)


# Creating & Configuring Products

### What Is a Product in LicenseSpring?

A `Product` is a basic building block within the LicenseSpring universe. It typically represents your software application, but it can be many other things, such as a controller, an operating system, a plugin, etc.

You can define products, configure features and custom fields for products, create license policies for products, archive products, or reactivate an archived product.

### How to Define a New Product

{% stepper %}
{% step %}

### Open the Configure Products page

Log in to the LicenseSpring Vendor Platform and click on `Configure Products`.

![Configure Products Button](/files/75afd3ce3b1ecd3448f63f032e2e69e21b1ebf88)
{% endstep %}

{% step %}

### Start adding a new product

This takes you to the section that lists all products defined in your account. Click on `Add a new Product` in the top right.

![Add New Product Button](/files/0cb8972a6d1196105e63ea863ad7fe8329e00d15)
{% endstep %}

{% step %}

### Fill in the basic product information

Provide the requested fields:

* Product Name: This must be a unique value.
* Product Code: This must be a unique value and cannot be changed once a product is defined.
* Authorization Method: Select whether you want to issue license keys, or assign users to licenses.
* Floating Timeout: Optional configuration for the default "heartbeat" when you issue floating licenses.
* Whether or not you allow Trial licenses to be issued.
* Whether you want binaries and versions associated to a product.

![Defining New Product](/files/f22d66e28729848ce36319a080c983b3a4e39635)
{% endstep %}

{% step %}

### Review and create the product

Once finished, click Next to review the product information you entered, then click "Create product".

![Click Create Product Button](/files/4a0e78d59a0f67deb410f118d8275d946c6431eb)
{% endstep %}

{% step %}

### Product detail page and next steps

After creation, you will be taken to the product detail page for the newly defined product. You can now start issuing licenses for this product.

![Finished Product Details Page](/files/4209b5196e715746850cd287d04d12ca00457e39)
{% endstep %}
{% endstepper %}

{% hint style="info" %}
If you like to explore further, you can configure product features, custom fields, product versions, and license policies from the product details page.
{% endhint %}


# Integrating SDK and Configuring License Fulfillment

### Integrating the SDK or API in Your Application

We offer SDKs for C++, C#, Java, Swift, Python, GO, Nodejs and Objective-C, allowing seamless integration with your application. For more detailed information on the SDKs and integrations, please refer to our [**SDK Documentation**](/sdks/introduction).

For all other projects, detailed instructions on utilizing our APIs can be found here:

* [**License API**](/license-api/introduction)
* [**Management API**](/management-api/introduction)

Furthermore, our SDKs are equipped with sample apps and header files to facilitate seamless integration.

Should you require assistance or have any questions, please don't hesitate to [**submit a customer support ticket**](https://licensespring.zendesk.com/hc/en-us/requests/new). **We are here to help!**

### Configuring License Fulfillment

LicenseSpring offers multiple methods to generate and dispatch license keys, catering to your specific needs. If you generate a low volume of licenses (e.g., a few keys per month), manual generation within the platform might be suitable for you.

Here are the implementation instructions for generating license keys manually or through various integrations:

{% stepper %}
{% step %}

### Generating keys manually through the Vendor Platform

[**manually through the Vendor Platform**](/vendor-platform/licenses/issuing-new-licenses)
{% endstep %}

{% step %}

### Utilizing the License API and Management API

[**License API**](/license-api/introduction) and [**Management API**](/management-api/introduction)
{% endstep %}

{% step %}

### Shopify Plugin (3rd party)

[**Shopify**](/integrations/shopify) **Plugin** (maintained by a 3rd party)
{% endstep %}

{% step %}

### FastSpring Classic Store integration

[**FastSpring Classic Store**](/integrations/fastspring/fastspring-classic-store)
{% endstep %}

{% step %}

### FastSpring Contextual Store Old integration

[**FastSpring Contextual Store Old**](/integrations/fastspring/fastspring-contextual-store-old)
{% endstep %}

{% step %}

### Salesforce integration

[**Salesforce**](/integrations/salesforce)
{% endstep %}

{% step %}

### Stripe integration

[**Stripe**](/integrations/stripe)
{% endstep %}
{% endstepper %}

Choose the method that best aligns with your requirements to efficiently manage and deliver license keys using LicenseSpring.


# Activate a Key Based License

In order to perform license activations or license checks, your software needs a way to interact with the LicenseSpring service.

For that you can either make calls directly to the LicenseAPI, or you can use one of the SDKs that we maintain.

{% hint style="info" %}
To keep things simple, we assume you selected license keys as your authorization method.
{% endhint %}

{% hint style="info" %}
There are many good reasons why you would call the LicenseAPI directly. However, the server response is in JSON which you would be responsible for parsing. We usually recommend that you use one of our SDKs, since give you a lot of additional functionality, such as performing local license checks, and working with the licenses as objects and calling functions on them, which could potentially save you a lot of time.
{% endhint %}

Regardless of whether you are using the LicenseAPI directly, or importing one of our SDKs in your codebase, you will need to enter some of the following information at a minimum.

### Connecting to the API

You will need your **api key** and your **shared key**. You can find these by logging in to the vendor account, and navigating to settings > keys. Click on the dropdown button beside the api key to reveal your shared key:

![Location of API Key and Shared Key](/files/ca54ebf793bc636d8c4a483663ffbcb8323a8a36)

{% hint style="info" %}
Alternative to api and shared key is [**OAuth authorization**](/vendor-platform/settings/oauth-configuration).
{% endhint %}

The minimum information required in order to run the `activate_license` webhook (or its respective SDK) for a key based license is:

* License key
* Product code
* HardwareID (a unique identifier for the device you are binding a license to)

If the activation is successful, the status of the license will say "Active", and the "Total activations" will have incremented by `1`.

![License Status](/files/03f6d1c77cfc1e4868c9847037f247100dd33576)


# Platform

LicenseSpring Vendor Platform is the **admin portal** for software vendors using LicenseSpring. It is also called the **vendor portal**.

Your internal team uses it to manage **software licensing**, from product setup to license operations.

Access is **permission-based**. Each user sees only what their role allows.

Common tasks include:

* Create and manage **products**, **product codes**, **features**, and **entitlements**
* Configure **license policies** (trial, subscription, consumption, node-locked, floating)
* Issue **license keys**, create **orders**, and provision licenses to customers
* Monitor **license activations**, **device usage**, and **license usage analytics**
* Manage licenses (reset devices, disable licenses, edit entitlements)
* Manage **customers**, **customer accounts**, and **license users**
* Handle **billing**, download **invoices**, and review account settings

{% hint style="info" %}
The vendor platform is meant for **internal users** (vendor admins and staff).

For external users, please review our [**End User Portal**](/license-entitlements/portal/end-user-portal).
{% endhint %}

### Vendor platform dashboard

![LicenseSpring vendor platform dashboard showing licensing stats and navigation](/files/9cabb2b3642cd2c13033efde5cd662b78a456319)

Use the dashboard for aggregate licensing metrics. Use it for quick navigation to key areas.

### Vendor platform sections

Jump to the main sections:

* [**Licenses**](/vendor-platform/licenses)
* [**Issuing New Licenses**](/vendor-platform/licenses/issuing-new-licenses)
* [**License Managers**](/vendor-platform/license-managers)
* [**License Users**](/vendor-platform/license-users)
* [**Devices**](/vendor-platform/devices)
* [**Orders**](/vendor-platform/orders)
* [**Customers**](/vendor-platform/customers)
* [**Customer Accounts**](/vendor-platform/customer-accounts)
* [**Products**](/vendor-platform/products)
* [**Metadata**](/vendor-platform/metadata)
* [**Analytics**](/vendor-platform/analytics)
* [**Settings**](/vendor-platform/settings)
* [**Importing/Exporting Data**](/vendor-platform/importing-exporting-data)


# Licenses

![](/files/574a5e18bc244872b8b4be27cd0a22c6bb9dc16a)

This view allows you to look up all licenses that have been issued in your account. Clicking on any of the licenses will allow you to view the details for that given license.

Main actions available on the license summary view:

* Search for and look up a license
* Import licenses via CSV
* Download licenses via CSV
* Create new licenses (orders)

You can click on "tab columns" to add or remove columns to the table:

![List of Optional Table Columns](/files/bdc5ee16c84880633779fee7e2e9e6043530c026)

The columns you can add/remove for the License Summary View are:

| Column                           | Description                                                                                                                                                   |
| -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `Created on`                     | Date on which a license was created                                                                                                                           |
| `License Key/User`               | The actual key or first email address assigned to a user-based license                                                                                        |
| `Product`                        | The product for which the license was issued on                                                                                                               |
| `License Type`                   | Allows you to see what [**type of a license**](/license-entitlements/license-types) was issued                                                                |
| `Expiration Date`                | Time-limited and subscription-based licenses may have a value on when they are set to expire (or renew), if that field has been set for that license          |
| `Is trial`                       | Will have an "x" symbol if the license is not of type trial, and a "checkmark" if the license is a trial license                                              |
| `Status`                         | The status of the license (active/inactive/disabled/Not Activated)                                                                                            |
| `Times activated`                | The number of devices that are currently node-locked to the license                                                                                           |
| `Max activations`                | The maximum number of devices that the license allows to bind itself to                                                                                       |
| `Prevent Virtual Machine`        | Boolean used to determine whether or not a license is allowed to run in a virtualized environment                                                             |
| `Customer email`                 | The email address specified for the customer associated to the order for which the license belongs to                                                         |
| `Customer name`                  | The name field of the customer associated with the order for which the license belongs to                                                                     |
| `Customer company`               | The company of the customer associated with the order for which the license belongs to                                                                        |
| `Customer reference`             | The reference (if specified) of a customer associated with the order for which a license belongs to                                                           |
| `Customer labels`                | Labels (if any) added to a customer                                                                                                                           |
| `Active`                         | Boolean displaying if there is at least one active device on the license                                                                                      |
| `Start Date`                     | Timestamp added to a license in case a start date was specified for the license                                                                               |
| `Is bundle`                      | Boolean that shows whether the license is for a product bundle (True) or for a single product (False)                                                         |
| `Is air-gapped`                  | A green checkmark appears if this license is meant to activate an air-gapped license policy                                                                   |
| `Is floating`                    | A green checkmark in this column if the license is meant to activate an on-prem floating license server                                                       |
| `Is floating cloud`              | Green checkmark in this column if the license is a cloud floating license                                                                                     |
| `Max simultaneous license users` | Field used by both floating license types to determine max concurrent usage of the license. The value will be N/A if the license is not of type floating      |
| `Can borrow`                     | Green checkmark if the license is a floating license, and the default heartbeat can be overridden by a larger value                                           |
| `Max borrow time`                | Maximum time (in hours) that a floating license can be borrowed. N/A if the license is not a floating license or is a floating license and cannot be borrowed |
| `Max license users`              | Maximum number of users that can be assigned to a license                                                                                                     |
| `Enable maintenance period`      | Boolean determining whether there is a maintenance window defined for the license                                                                             |
| `Maintenance duration`           | Duration of the maintenance period starting from the first activation of the license                                                                          |
| `Maintenance End Date`           | End date when the license maintenance window expires                                                                                                          |
| `Max consumptions`               | If the license is of type consumption, this is the maximum consumptions allowed for that license                                                              |
| `Total consumptions`             | Total consumptions counted on the license to date. N/A if this is not a license of type consumption                                                           |
| `Product features`               | Lists all feature codes that have are added to this license                                                                                                   |
| `Disabled on`                    | If the license is disabled, this column will show the date on which the license has been disabled                                                             |
| `Activated on`                   | The date that the license was last activated on. It is updated for every license activation                                                                   |
| `Last updated`                   | Time stamp with the last modification to the license's entitlements                                                                                           |
| `Last checked`                   | Time stamp with the last online license check                                                                                                                 |
| `Valid duration`                 | If the license is time-limited, the license's expiry date is counted on the time stamp of the first activation and this duration                              |
| `Order`                          | Order ID                                                                                                                                                      |
| `Active Devices Up to Dates`     | Number of active devices using the latest product version                                                                                                     |
| `Note`                           | Any note that is added to the license will appear in this column                                                                                              |


# Issuing New Licenses

There are a few ways you can issue licenses:

* Manually through the Vendor platform
* Through the Management API
* With one of our integrations

## Our Data Model

Before issuing a license, it might help to know how our data model is organized:

![](/files/5558564d81ab19a66799169d95f5e2477a4ccf37)

* **A License** can belong to an order. An order can have zero to many licenses.
* **An Order** can belong to a customer. A customer can have zero to many orders.
* **A Customer** can belong to an account. An account can have zero to many customers.

{% hint style="info" %}
A License will belong to an order. If you do not specify an orderID, a random orderID will be generated.
{% endhint %}

Orders do not need to belong to customers. However, it can be useful to have this association for analytics or customer support.

## Issuing New Licenses Manually Through the Vendor Platform

{% hint style="info" %}
We are assuming you have already created a product within your vendor account.
{% endhint %}

To issue a new license for your product, follow these steps:

{% stepper %}
{% step %}

### Open the Licenses tab and create a new order

Click on the "Licenses" tab on the left, then click "create new order" on the right of the screen.

![](/files/e612020a1b077420ca5ce922ee43eed77ae870c1)
{% endstep %}

{% step %}

### Add order identifiers and (optional) customer information

The next screen allows you to add order identifiers and customer information; these fields are optional. Click Next.

![](/files/17b7ad88288ccb994d61be9f943367ef5d9eb17d)
{% endstep %}

{% step %}

### Configure the licenses to issue

Click "Add Product" and use the configuration screen to set the entitlements for the license.

![](/files/4467cc186e6c6c315d2bdebf6eaabe6dccee1ec0)

Select the product and configure the licenses. You can choose a license policy (if previously created) or select "use custom values" to fully customize the license entitlements.

You can specify options such as:

* License type: Perpetual, Time-limited, Subscription, or Consumption-based
* Which features are included in the license
* Maximum activations (how many devices can node-lock to the license)
* Unique values for license custom fields
* Etc.
  {% endstep %}

{% step %}

### Confirm and create the order

When finished configuring entitlements, click "Confirm". Review the configuration on the "Confirm Order" screen and press "Create order". The Order detail page will appear where you can retrieve your new licenses.

![](/files/865dea504f0419f64c54d6215c2617aa7ef8bd3d)

![](/files/c407749c150bcb0dbc5c76734c2dbad782dac832)

![](/files/31c81f2d31acbaae2491280e9cf06ef0932ef1b7)
{% endstep %}
{% endstepper %}


# License Managers

**License Managers**: This is a user role responsible for managing licenses for a group or organization. They have access to the [**End User Portal**](/license-entitlements/portal/end-user-portal), where they can perform actions based on the **permissions that have been enabled** for them.

This view gives you an overview over license managers you have created for your account.

![License Managers View](/files/fc04e5b9873a949c0c453c867880cab10a05a2d3)

You can set up a default permission set for any license manager created in the future and you can inspect license manager details (e.g., their permission set, orders, licenses, user groups and some reporting).

Resources

* **License Manager Guide**\
  A comprehensive walkthrough of the end-user portal. It covers all key roles, core concepts, and actions, including managing licenses, devices, orders, users, and more. You'll also find sections on permissions, floating licenses, API integrations, and detailed workflows.\
  [License Manager Guide](/license-entitlements/portal/end-user-portal/license-manager-guide)
* **The License Manager**\
  This page details the role of a License Manager within the platform. It explains how to assign a user as a License Manager, the permissions they can have (such as issuing licenses, managing users, and resetting devices), and how to add them to existing orders.\
  [The License Manager](/license-entitlements/portal/end-user-portal/the-license-manager)


# License Users

This page lists all license users that have been added to a LicenseSpring account. A license user typically is added to LicenseSpring at the moment they are assigned to a given license. A license user represents an individual who can access and use a product under a user-based licensing model.

![License Users Page](/files/03107cc58445afc2d2256916d17591d2fe9bd9af)

## Searching for License Users

You can search for a license user to find all the licenses they are assigned to. The following search criteria are supported:

* First Name
* Last Name
* Email
* Phone Number

![](/files/d9f4e92a3031cb90496ae6be03221cb200318140)

## Individual License User Information

After selecting a license user, you will see the User Details screen, where you can inspect the user’s information across three main sections: licenses, devices, and user groups.

### Licenses Tab

![](/files/4c2b861f3568292ebead023a7800a2c3e919e347)

This table displays all licenses currently assigned to the user. You can view key details including:

* Created On: Date and time the license was issued.
* User: The assigned user’s email.
* Product: Name of the licensed product.
* Product Features: Specific features included in the license.
* License Type: Type of license (e.g., perpetual, subscription).
* Expiration Date: When the license will expire (if applicable).
* Times Activated: Number of times the license has been activated.
* Is Trial: Indicates if the license is a trial version.
* Status: Current state of the license (e.g., active, inactive).

{% hint style="info" %}
**Initial Password:** This is the password automatically generated when a user is first added to LicenseSpring. Users are prompted to reset it upon first login.
{% endhint %}

![](/files/82c74de05f3a9be4fa2129572892c59086476021)

### Devices Tab

Here you can view detailed information about each device where the user has activated a license. The table contains:

* Details: General information about the device itself (IP address, host name, SDK version).
* Type: Type of device.
* Hardware ID: Unique identifier for the device.
* Last Check: The most recent time the license was verified on this device.
* Status: Current status of the license on the device.
* Device Variables: Any custom variables stored for this device.
* Floating in Use: Indicates if the license is currently active in a floating license pool.
* Borrowed: Shows if the license has been borrowed from a floating pool.
* Floating Last Check: Timestamp of the last floating license verification.

![](/files/5348408057df3c8a9b2c4c848f4bdf613aa2f0fd)

### User Groups Tab

* Lists any user groups the user belongs to.
* User groups help simplify license management. On the User Group page, you can easily assign users, allocate licenses, and designate license managers.

![](/files/d4a00738fe681bef99f0dcf5b3acae511032c5d0)

## Add a new License User

Most users are automatically added to LicenseSpring when assigned a license. If a user does not yet exist in the system and you plan to assign them to a license later, you can also add them manually:

{% stepper %}
{% step %}

### Click Add License User

Open the Add License User dialog.
{% endstep %}

{% step %}

### Enter user details

Provide name, email, phone number and Metadata JSON.
{% endstep %}

{% step %}

### Confirm

After clicking the Confirm button, the user will receive an email containing their initial password.
{% endstep %}
{% endstepper %}

![](/files/e92cd0735f0cdc03a8cd1ad07cd7865e58b6690f)

{% hint style="info" %}
To learn more about user groups, including how to create them and manage licenses, users, and license managers, see the [License Manager Guide](/license-entitlements/portal/end-user-portal/license-manager-guide).
{% endhint %}

{% hint style="info" %}
User-Based Licensing — Learn how user-based licenses work and review best practices in [User Based Licensing](/sdks/tutorials/licensing-scenarios/user-based-licensing).
{% endhint %}


# Devices

This summary page displays a list of all license devices that have been used to activate your account licenses.

![License Devices View](/files/542d632040b9c615134adfb39073200afffa8db3)

For every device you can inspect for which license was this device used and which license user (if user based authorization).

{% hint style="info" %}
The same device, with the same hardware\_id can be used to activate multiple licenses, and it will be displayed multiple times in the devices list since for every new license activated, LicenseSpring creates a new license device.
{% endhint %}

## Device Details

**Devices Tab:** Shows all machines where a license has been activated. You can view the device’s hardware ID, activation type, OS, SDK version, network information, status, and other variables. Expanding a device provides detailed info including first activation date, hostname, IP addresses, MAC address, and whether the device is a virtual machine.

![](/files/3e09043c9c83877276bfa5af33e05755c82c7b93)

### Expanded Device Details

* **First Activated** – The date when the license was first activated on this device.
* **Activation Type** – Indicates whether the license was activated online or offline.
* **OS** – The operating system running on the device.
* **SDK Build** – The version of the LicenseSpring SDK used by the application.
* **App Version** – The version of the application that is using the license.
* **Hostname** – The name of the device or computer.
* **External IP Address** – The device’s public network IP address.
* **IP Address** – The local network IP address of the device.
* **MAC Address** – The unique identifier of the device’s network interface.
* **Is Virtual Machine** – Specifies whether the device is a virtual machine.
* **VM Info** – Additional information about the virtual machine, if applicable.


# Orders

This is a summary view that displays a table with order information. Clicking on one of the orders will open the orders summary view.

Actions you can take on the orders summary view include:

* Search for an order
* Download all orders and licenses for a given date range
* Start the order creator process

![](/files/c7b646cdfe54d1d3cf9beca7041d2262a84e17c0)

The columns you can specify for the orders table are:

* `Created on` — Date/time at which an order was created
* `Customer email` — If a customer is specified for this order, then the customer's email address will appear in this column
* `Customer name` — Customer name if the order has a customer specified
* `Customer company` — Company of the customer if the `Order` belongs to a customer with a company field
* `Customer account` — Account of the customer if they belong to one, and if the order has a customer specified
* `Customer reference` — Customer reference number if one is specified
* `Customer Labels` — Customer label if one was specified
* `Order ID` — Orders will always have this value
* `Order reference` — Optionally specified reference number of the order
* `Last updated` — Last time the order information was updated (order reference, campaign parameters, downloadID, is test, or is trial)
* `Type` — If created using the [**Trial Key**](/license-api/trial-key) License API endpoint it will be set as `api_trial` and in rest of the cases it will be set as `normal`. It can be overwritten using the Management API when using the [**Create Order**](/management-api/order/create-order) endpoint
* `Campaign parameters` — Optional field, currently not used
* `Is trial` — If the order was created when generating a trial license via the License API, there will be a green check mark in this field


# Order Detail View

The order detail view allows you to lookup and modify information on a given order.

Actions you can take on the order view include:

* Exporting license/customer information for a given order
* Modifying order parameters (such as the order reference number or campaign parameters)
* Add more licenses to the order
* Add/edit customer to an order
* Add license managers to an order
* Delete the order

### Export CSV of Order Licenses

Click "export order data" and a CSV will be generated with information on the order, licenses, and customers.

![](/files/e2ec3b0f823dbe2ea4a025c89d33cac27c1c12fc)

### Edit Order Details

The fields you can modify include:

* Order reference number
* Campaign parameters
* Download ID
* Is this a test order
* Is this order a trial order

![](/files/0e9c505989c0a7ee6896f2629878df8f96eaaf69)

![](/files/abf7e9ccb2389b8395a437b6c38e9b0bc6e8cd54)

From this view you can also:

* Add more licenses to the order
* Add/edit customer to an order
* Add license managers to an order
* Delete the order

### Add or Edit the Customer for the Order

Click on the customer tab. If the order does not have a customer associated with it, you can add one from this screen. Otherwise, you can edit the existing customer.

![](/files/f8454ca4133438079a6996a95460802191b1eab6)

### Add More Licenses to an Existing Order

Click on "Licenses" > "Issue new Licenses" to start the process of adding a new set of licenses. The rest of the license generation process should be very similar to the regular order creation process.

![](/files/21e0c4c5373bb63923beb3abfa2275b661d2c606)

### Add a License Manager to an Order

{% hint style="info" %}
License managers can log in to the user portal and manage all licenses within an order, according to permissions they had been granted.
{% endhint %}

Click on the "License manager" tab and press "Add license manager".

![](/files/7f3a255f2b0ae17474799bb0b89fae7c76de70e9)

{% hint style="info" %}
Be sure to set up a notification to send the new license manager an email invite to the user portal, so that they can receive their initial email/password.
{% endhint %}

Available permissions for license manager are:

| Manage license users            | Can add, edit or delete license users      |
| ------------------------------- | ------------------------------------------ |
| Manage license user credentials | Can change license users passwords         |
| Manage managers                 | Can add new license managers to some order |
| Reset device                    | Can reset device                           |
| Reset license                   | Can reset license                          |
| Borrow license                  | Can manage floating license borrowing      |
| Create bundle order             | Can create new product bundle orders       |
| Create order                    | Can create new orders                      |
| Issue license                   | Can issue new licenses on existing order   |

This permission can be edited only in enterprise pricing plan where they can be also extended with distribution portal permissions (creating orders and issuing licenses).

### Delete the Order

{% hint style="danger" %}
Deleting an order will also delete all of the licenses within it and the process is not reversible.
{% endhint %}

![](/files/a642c5b8126ef93aaa66d19805c39a239fd72a13)


# License Detail View

## License Status

A license can display one of the following statuses:

| Status            | Description                                                            |
| ----------------- | ---------------------------------------------------------------------- |
| **Active**        | The license is enabled and has at least one active device              |
| **Inactive**      | The license is enabled but currently has no active devices             |
| **Not Activated** | The license has been issued but has never been activated by any device |
| **Disabled**      | The license has been manually disabled from the vendor platform        |

All licenses that have been issued within LicenseSpring will have their own license detail view.

**To access the license editing page via the Orders tab, follow these steps:**

{% stepper %}
{% step %}

### Go to the Orders tab

{% endstep %}

{% step %}

### Click on Licenses

{% endstep %}

{% step %}

### Select the license to edit

From the list, locate and select the license you want to edit. (You can click anywhere on the license row to open the edit license page.)
{% endstep %}
{% endstepper %}

In this view, you can see all information associated with the license, ability to edit the license's entitlements including disabling or resetting a license, review the usage of the license in table format, along with analytics.

![](/files/60372f228bf175d930202846b5269538a6174ff9)

### Editing a License

{% hint style="info" %}
You cannot edit everything within a license. For example, you cannot edit a license type. You can however, add/remove features, change the expiry date (for time-limited licenses) or max activations
{% endhint %}

![](/files/3027be3a14bc8b97b0fa6a79d640cc4f9cbe9a92)

Depending on the type of license that it is, you will be able make different types of changes.

Some of the common changes for all licenses include:

* Changing the number of max activations allowed for that license
* Setting the number of device transfers allowed or preventing license transfers altogether
* Preventing virtual machines
* Changing the license start date
* Modifying whether or not the license is a floating license
* Modifying the maintenance period for that license

![](/files/0087fc3ee5034ee8efdd8e4c143dc5d925464ccd)

### Reset a License

Resetting a license will reset license entitlements, including setting total activations to 0, deactivating license devices, resetting total consumptions, etc.

![](/files/e714d9073ad28331ad2aad248b5791d0ac8c329d)

When selecting **Reset License**, you will be presented with a set of options that allow you to reset specific license parameters individually. This provides flexibility in managing license states without needing to recreate them entirely.

You can choose to reset the following:

* **Validity period**
* **Consumptions**
* **Maintenance period**
* **Metadata**
* **Product features**
* **Custom fields**

> **Note:** Core license data will always be reset as part of this action.

<figure><img src="/files/nTTqsRlwzUhUIvqyetd6" alt=""><figcaption></figcaption></figure>

### Disable/Enable a License

Disabling a license will reject any attempts to activate, or perform a check on that license. Disabling a license will not change any of the entitlements to the license. The status of the license will be set to `Disabled`

![](/files/789e1d6996a647addd5e1d2ccdfcbc934089b937)

A disabled license can be re-enabled at a later date. The status will revert to whatever the status was if it was not disabled: `Not Activated`/`Inactive`/`Active`"

![](/files/019982448a36022785c2cb979e2db09886f2e0e1)

### Delete a License

Deleting a license will remove it from the LicenseSpring database. This is a destructive process, and cannot be reverted through the vendor platform.

![](/files/dbe35917f4e094a9de2f40d7e07a38795b9b433e)

### Devices Associated With a License

Any device that has been activated a license will appear in this tab:

![](/files/69d031ce2c14163f3f1a78c160d66776dc355974)

You can retrieve more information on the computer that activated the license by expanding the "details" arrow:

![](/files/85b39831ec9bf1577d8b9dd02735493fad2ded96)

{% hint style="info" %}
Many of the fields are optional and do not need to be sent when activating a license. The only field that needs to be sent when activating a license is the Hardware ID.
{% endhint %}

#### Retrieve Current Device Variables

Devices can send key value pairs back to LicenseSpring. You can retrieve the most current values sent by clicking on the "Device Variables" button.

![](/files/ef639b1ae00b47f92fa2221560a7112502912b9f)

![Device Variables Display](/files/948ed6dd620f435712500013d88c46e8c1325fd4)

#### Reset Device

Resetting a device from the license details view will set the status of the device to `Inactive` and decrement the `Total activations` field by 1. The next time that device attempts any action using the License API, the server will return a 4XX error.

![](/files/b14ac1f4252d427a3f3d818e016cca20aa5bd8d5)

![](/files/1582824d40218ab822023f40ccf963ba375b790c)

![](/files/9441e3b2f41148983610fdfddc0b90aff11b4b7d)

#### Blacklist/Whitelist Device

Blacklisting a device will prevent that device from using it with this license. Blacklisted devices appear at the bottom of the view.

You can always unblock a device by whitelisting it.

![](/files/cb82e5e2481c67b00dd5c89df2b7b087a1c7f694)

![](/files/949766463f614e60471d811dba802c6cc9f69459)

#### Get License Refresh File

When a license has been node-locked to a given device, a device-specific license file is created which can be reached through the use of our SDKs to perform local license checks. This file is updated each time entitlements for the license change. You can download this file and send to the end user:

![](/files/eb7b465e4a1424397a885bab7b3188903de909bb)

After clicking **Download License Refresh File**, a dialog will appear prompting you to select a user and an authorization method.

<figure><img src="/files/zbUoYLxyWyBhDTnxRySV" alt=""><figcaption></figcaption></figure>

When **OAuth** is selected as the authorization method, you will be prompted to select a Client ID and Client Secret.

The available OAuth clients are sourced from the following:

* License
* Customer
* Customer Account
* Order
* Company

{% hint style="info" %}
Only valid OAuth clients are displayed  clients with expired secrets will not appear in the list. Make sure the Client ID and Secret you select match the ones being used in the application running on the device.
{% endhint %}

### Edit License Custom Field

If the product has any custom fields defined, then the fields will always be available to all licenses issued for that product. You can override the value of the custom field for any given license.

![](/files/d67e8816d088fdb933c58c5adefabff7cd891eb6)

### Edit Product Features of the License

You can add/remove or modify features for any given license provided that those features have been defined for the license.

![](/files/65d8899e50149417015bc8d2a4f2baaf9af426b6)

#### Add/Remove a Feature

If a feature has not already been added to the license, you can select which feature to add.

![](/files/4e5e1132a43aedc951c8c146305453bc5c50426a)

{% hint style="info" %}
You can add an expiry date for an individual feature if you would like to limit the use of a module with time. Additionally, you can define a valid\_duration ( same like for a time limited license, eq- 1m, 1y, etc. ) which will be recalculated as expiry\_date at the time of the first license activation.
{% endhint %}

![Toggling Product Features](/files/740848d16dcc4fd04567ef5f3ff48e97a871b23d)

#### Change Consumption Feature Allowances

You can modify the allowances for consumption features:

![](/files/578a7b8f66f43ad50323e69169e5baba6cbd53bd)

![](/files/d03d5a1af576d621a6da564ec7c9bf6f716929ab)

### License Usage Report

The license usage report includes tabular data on events that occurred on the license, such as license checks.

![](/files/2c643daaeed3b0ee9acec15345dc13ef7467d2d4)

#### List of license usage events

* License activated
* Offline license activated
* License check
* Consumption added
* Max consumptions reached
* Feature consumption added
* Max feature consumptions reached
* License created
* License deactivated
* Offline license deactivated
* Floating release
* Floating borrow
* Trial license created
* Get device variables
* Track device variables
* Get versions
* Get installation file

#### Export License Usage Report in a CSV

You may download the CSV of the license usage report by specifying a date range, and clicking the download button.

![](/files/e65450b62c97afa465bbe9748ae4072974e4f12a)

### Analytics

The [**Analytics**](/vendor-platform/analytics) tab aggregates the license usage report data and plots it for better understanding.

![](/files/32d2ae412bd29559448cda32ec2079793ca00224)

#### Analytics for All Licenses

Using the license detail view, you are able to see license-specific statistics, including:

* License checks
* Get device variables
* Track device variables

#### Analytics for Consumption Licenses

Consumption licenses have unique statistics displayed, including:

* Daily consumptions
* Total consumptions
* Overages

Consumption events are also tracked, such as:

* Reset consumption
* Add consumption

{% hint style="info" %}
**Note:** License analytics also tracks license feature consumption
{% endhint %}


# Customers

This view will present a table with all of the customers that have been specified within LicenseSpring.

Common tasks you can accomplish on the customers summary view include:

* Looking up customers
* Defining customer labels
* Add a new customer to LicenseSpring
* Download a CSV file with all customers

![List of All Customers](/files/5887b906baa187398ad77801fabca2fe6ef2167f)


# Customer Detail View

The customer detail view is where you look up information on a specific customer, see any orders they have, and any licenses they are assigned to, along with logs.

![](/files/b5db2c7fefb04721ae9024ba26b3ecd1b7eb9307)

### Actions you can take on a customer

* Edit customer details
* Add/remove customer labels
* Change customer accounts
* Delete the customer

### Edit customer details

A customer has the following fields that can be modified or left blank. The email address, however, is required.

{% hint style="info" %}
Email is required.
{% endhint %}

* `Email` **(Required)**
* `First Name`
* `Last Name`
* `Address`
* `City`
* `State/Province`
* `Country`
* `Zipcode/Postcode`
* `Phone Number`
* `Company Name`
* `Reference` (Customer reference number)

### Add customer label

Customers can be filtered by their labels, which is useful when searching for certain types of customers.

Click "Add label" and select an existing unapplied label for the customer, or create a new one and apply it.

![](/files/43c8aba9bd5288ac4ea12294f6e45513a2516c6d)

![](/files/0820d24e215e10ebf5434ceadab25d6d9fe085cd)


# Customer Accounts

LicenseSpring allows customers to be grouped by account. This is useful if you license software to an organization with different departments, or multiple locations, and can be found by expanding the "Customers" tab in the left-hand navigation.

![List of Customer Accounts](/files/d0ec9e84357c552f5c5285cf0347365a7da90ceb)

The columns in the accounts table include:

* `Name` — Account name
* `Code` — Account code
* `Number of customers` — Number of customers that belong to a specific account
* `Email` — Email address associated with the account
* `Reference` — Optional value that can be useful when searching for an account

To retrieve information on the account without moving from the summary page, click on the dropdown arrow next to the account `Name`:

![Customer Accounts Detail View](/files/ff7691c993ab6600785ffbd3df399898b3f17638)


# User Groups

User groups allow you to organize license users into named groups for easier management across your account.

User groups can be managed at two levels:

* **License Users: N**avigate to **License Users → User groups** to manage groups across all license users globally

<figure><img src="/files/YBXajCHDk8ewe1PpUSfi" alt=""><figcaption></figcaption></figure>

* **Customer Account: O**pen a specific Customer Account and navigate to the **User groups** tab to manage groups within that account

<figure><img src="/files/wsyWJZiiEizgeZZFbuHF" alt=""><figcaption></figcaption></figure>

### Creating a User Group

Click **Add user group** and fill in the following fields:

<figure><img src="/files/qthy44YBt4g25zipNhSt" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/KKA1CUfkKEyun2ko7fjf" alt=""><figcaption></figcaption></figure>

* **Code: U**nique identifier for the group
* **Name:** Display name of the group
* **Description:** Optional description
* **Metadata JSON:** Optional metadata in JSON format

Click **Confirm** to save the group.

### Managing a User Group

Once a group is created, open it to view its details and manage its members. Each group displays the following information:

<figure><img src="/files/R27kHV3OERLkh2SwPwbE" alt=""><figcaption></figcaption></figure>

* **Name:** Display name of the group
* **Code:** Unique identifier
* **Description**: Optional description
* **Customer accoun:** The customer account this group is associated with
* **Metadata:** Any metadata attached to the group

You can edit the group details at any time using the **Edit user group** button, or remove the group using the **Delete** button.

<figure><img src="/files/667OQG9SqWOvLnYgXHLw" alt=""><figcaption></figcaption></figure>

### Adding Users

Navigate to the **Users** tab within the group and click **Add user** to assign license users to the group. Users are listed with their email, first name, last name, and phone number.

<figure><img src="/files/eRXA0sVBeZnVYxrldale" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/ZN9fiOX027wA4UTRS51u" alt=""><figcaption></figcaption></figure>

### Adding Licenses

Navigate to the **Licenses** tab within the group and click **Add license** to assign licenses to the group. You can search and filter licenses by standard fields and metadata filters.

<figure><img src="/files/pDc1umgGhA1VTZQFJfAs" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/GYpuXLRB3G090EbzPq5G" alt=""><figcaption></figcaption></figure>

### Adding License Managers

Navigate to the **License managers** tab within the group and click **Add manager** to assign a license manager to the group.<br>

<figure><img src="/files/uhbhlfXe8ph39CEWolLf" alt=""><figcaption></figcaption></figure>

&#x20;Fill in the following fields:

<figure><img src="/files/iA9AAuOWu5qz0CkNc41V" alt=""><figcaption></figcaption></figure>

* **Email**
* **First Name**
* **Last Name**
* **Phone Number**
* **Metadata JSON**

Click **Confirm** to save.


# Single Sign On (SSO) - Customer Accounts

Set up a dedicated SSO provider for a specific Customer Account, enabling users within that account to authenticate through their own identity provider.

Each Customer Account can be configured with its own SSO provider, enabling users within that account to authenticate through their own identity infrastructure rather than using LicenseSpring's default authentication.

{% hint style="info" %}
This page covers SSO configuration at the Customer Account level, which applies only to users within that specific account. For company-wide SSO setup, see [Single Sign-On (SSO)](/common-scenarios/single-sign-on-sso).
{% endhint %}

### Adding an SSO Provider

To configure SSO for a Customer Account, open the account and navigate to the **Single Sign On** tab, then click **Add provider**.

<figure><img src="/files/ejPqlTEIhvsBMLE1u8ql" alt=""><figcaption></figcaption></figure>

The following provider types are supported:

* **SAML:** Upload the provider metadata `.xml` file and set a default redirect URI
* **Google:** Requires a Name, Client ID, and Client Secret
* **Apple:** Requires a Service ID, Key ID, Team ID, and `.p8` key file
* **OpenID Connect (OIDC)**: Requires a Client ID, Client Secret, and well-known configuration URL

<figure><img src="/files/NItKpMNintGv81twEfYR" alt=""><figcaption></figcaption></figure>

Fill in the required fields based on your selected provider type and click **Confirm** to save.

### Additional Options

**Require email verification on first SSO login**  when enabled, users logging in via SSO for the first time will be required to verify their email address before gaining access.

**Multiple Redirect URIs** : if you need to support multiple redirect URIs, ensure you are on the latest SDK version. See the SDK[ documentation](/sdks/introduction) for details.


# Products

The **Product List** page is your main dashboard for managing all products in your system. It helps you easily find, review, and organize products, as well as create new ones. Here’s what you can do on this screen:

![Product List View](/files/91ffaa208b22a15fdfd0c5a6895ae14391dc2ebf)

{% stepper %}
{% step %}

### View and Organize Products

**Active & Archived Tabs**\
Switch between viewing active and archived products using the tabs at the top.

**Product Table Overview**\
Each row in the table displays key product details:

* Product Name
* Product Code
* License Policy (e.g., Default or custom policy)
* Authorization Method (License Key or User-Based)
* Trial Availability
* Bundle Status (if it's part of a bundle)
* Product Status (Active or Archived)
  {% endstep %}

{% step %}

### Search and Filter

**Search by Name**\
Quickly find a product by typing its name into the search bar.

**Advanced Filters**\
Click **“Search filters”** to filter the list by:

* **Authorization Method**: License Key or User
* **Trial Availability**
* **Status**: Active or Archived

**Clear Filters**\
Use the “Clear filters” button to reset all filters and view the complete list.
{% endstep %}

{% step %}

### Add New Products or Bundles

* **Add New Product**: Create a new product entry.

![](/files/e56baa503705f08a8cd105a246dc2dc3fceeb5f1)

* **Add New Bundle**: Group multiple products into a bundle.

Both actions can be done directly from this page for quick and seamless product management.
{% endstep %}

{% step %}

### Navigate Large Product Lists

Use the **“Previous”** and **“Next”** buttons at the bottom of the page to move between multiple pages of products.
{% endstep %}

{% step %}

### At-a-Glance Product Insights

Without needing to open individual product pages, you can instantly see:

* Which products allow trials
* Which are part of a bundle
* Current product status
  {% endstep %}
  {% endstepper %}

{% hint style="info" %}
Summary

The **Product List** page is designed for fast, efficient product management. Whether you're updating product details, organizing licenses, or expanding your catalog, this page provides a clear, centralized interface to help you stay in control.
{% endhint %}


# Metadata

Metadata is a powerful feature introduced in LicenseSpring that allows you to attach custom information to various levels within your licensing infrastructure.

It serves as a flexible and extensible mechanism for storing additional data related to licenses, features, products, and customers.

The metadata JSON is stored as a string, providing a wide range of possibilities for customization.

### Levels of Metadata

Metadata can be applied at four different levels within LicenseSpring, providing flexibility and granularity in managing your licensing data.

#### Order Metadata

Order metadata enables you to store custom information related to your orders. This level of metadata is useful for storing order-specific details.

![](/files/a0b800e066c1c4e6a87210f6dc28979ba57dc7f7)

#### License Metadata

License metadata allows you to attach custom information to individual licenses. This level of metadata is particularly useful for storing license-specific details that are unique to each instance.

For example, you can add metadata to indicate the activation date, license type, or any other relevant information specific to a particular license.

![](/files/83a2fa2e3b8bfec89d67d36f32f62d1856fc43db)

#### Feature Metadata

Feature metadata allows you to associate custom information with specific features within your licensing structure.

This level of metadata can be used to store additional details about individual features, such as version numbers, release dates, or any other feature-specific data that you wish to track.

![](/files/5b8e28ba13e819ccc7c284c62bdedf8047aa69c8)

#### Product Metadata

Product metadata enables you to store custom information related to your products. This level of metadata is useful for storing product-specific details that are applicable to all licenses associate.

Product metadata is returned from license check inside the `product details` object.

![](/files/80266d1d8f5396a2ef060f09d600174b604ec2ac)

#### Customer Metadata

Customer metadata allows you to attach custom information to individual customers. This level of metadata is particularly helpful for storing customer-specific details that may be important for managing your licensing relationships.

For instance, you can add metadata to store customer preferences, contact information, or any other relevant data that pertains to a specific customer.

![](/files/d604ef78a1518eb778b685f6cd07c6baa08b983d)

### Customer Accounts

Customer Accounts metadata allows you to attach custom information to group of customers. This level of metadata is particularly helpful for storing details about group of customer accounts.

For instance, you can add metadata to store customer accounts preferences, contact information, or any other relevant data that pertains to a specific customer accounts.

![](/files/d349626c4c9aaa2db3e6707edb0a7a9afe75ffba)

### Working with Metadata

Managing metadata in LicenseSpring involves the adding and retrieving of metadata at the desired levels. Here's a brief overview of the basic operations for working with metadata.

#### Adding Metadata

To add metadata to a license, feature, product, or customer, you can use the appropriate API method provided by LicenseSpring.

The `metadata` field is initially established on the product feature and, along with other fields, acts as a default value. When a license is created with specific product features, we utilize those features to generate license\_product\_feature items, inheriting default values from the product\_features, and assign them to the license.

Then, the metadata on the license product features can remain unchanged or be modified on a per-license basis, depending on your preferences. If no modifications are made, the metadata will persist in its original state.

#### Retrieving Metadata

To retrieve metadata associated with a license, feature, product, or customer, you can utilize the method `metadata()` on the `License`, `LicenseFeature`, `ProductDetails`, and `Customer` classes. By providing the relevant identifier, the API will return the metadata string associated with the specified entity.

{% hint style="info" %}
Note: It's important to remember that metadata will only affect the specific entity to which it is attached and will not impact other licenses, features, products, or customers.
{% endhint %}


# Analytics

LicenseSpring offers powerful analytics tools that provide valuable insights into software license usage and customer behavior.

### What LicenseSpring Currently Offers

#### Comprehensive License API Call Tracking

LicenseSpring's analytics tracks various license-related API calls, including license checks, activations, deactivations, trial keys, orders, licenses issued, and license key generations.

This real-time data provides vendors with an in-depth understanding of license usage patterns.

![](/files/fad3b9f00186f1ce6b5a0720796933df8dc2fb2d)

#### Offline Activations and Deactivations

LicenseSpring's analytics also captures data on offline license activations and deactivations, allowing vendors to account for licenses used in environments without internet connectivity.

#### License and Feature Consumption Tracking

LicenseSpring's analytics monitors license and feature consumptions, helping vendors identify the popularity of specific features and optimize their product offerings.

The analytics feature also highlights licenses or products that have reached their maximum feature usage, aiding vendors in making informed decisions about feature allocation and pricing.

#### Product Details and Versions

Vendors can access detailed information about their products, including different versions and installation files.

This insight aids in understanding product adoption and version distribution.

#### Custom Date Range Selection

Vendors can customize the date range for viewing analytics data, enabling them to analyze trends, seasonal variations, and performance over specific periods.

#### Success/Error Rate of API Calls

LicenseSpring's analytics provides success and error rate metrics for API calls, helping vendors monitor system performance and identify potential issues.

#### Detailed Reports for Licenses, Orders, Products, and Customers

LicenseSpring allows vendors to generate detailed reports for each specific license, order, product, or customer.

This facilitates granular analysis of API calls associated with individual entities.

![](/files/27be40d1857a824e08b5f21b6c2016bd148e6b17)

#### Usage Reports

LicenseSpring provides comprehensive usage reports for individual licenses, giving users valuable insights into how their software is being utilized by end-users.

These usage reports offer a detailed overview of license activations, deactivations, and usage patterns, helping software vendors monitor the popularity and distribution of their products.

By analyzing individual license data, developers can make informed decisions to optimize their licensing models, identify potential licensing misuse or abuse, and tailor their software offerings to meet customer demands effectively.

### Comprehensive List of Tracked Events

<details>

<summary>View tracked events</summary>

LicenseSpring tracks many events, including:

* License Checks
* License Activation
* License Deactivation
* License Offline Activation
* License Offline Deactivation
* License Trial Key
* Order Created
* License Issued
* License Key Generation
* License Consumption
* License Feature Consumption
* Installation File
* Versions
* Floating Release
* Floating Borrow
* Product Details
* Track Device Variables
* Get Device Variables
* Change Password
* Customer License Users
* Ping
* Single Sign On (SSO)

</details>

### How to Use LicenseSpring Analytics

{% stepper %}
{% step %}

### Access the analytics dashboard

Log in to your LicenseSpring vendor account and navigate to the analytics dashboard.
{% endstep %}

{% step %}

### Select data parameters

Choose the desired data parameters for analysis, such as license activations, usage frequency, customer segments, etc.
{% endstep %}

{% step %}

### Generate reports

Generate customized reports and view visual representations of the analytics data on the dashboard.
{% endstep %}

{% step %}

### Interpret insights

Interpret the insights from the analytics data to make informed business decisions, optimize licensing strategies, and improve customer experience.
{% endstep %}

{% step %}

### Take action

Based on the analytics findings, take proactive actions to address licensing compliance issues, engage with customers, and enhance software offerings.
{% endstep %}
{% endstepper %}

![](/files/8e129e58dbeb85d97d449511b24fe83ae5da09e2)

By leveraging LicenseSpring's analytics capabilities, vendors can gain valuable intelligence to drive business growth, strengthen customer relationships, and maximize the value of their software licenses.


# Settings

Use Settings to manage account security, access, notifications, billing, and integrations.

### Sections

* [**Two Factor Authentication (2FA)**](/vendor-platform/settings/two-factor-authentication-_2fa_)
* [**Users**](/vendor-platform/settings/users)
* [**Email Notifications**](/vendor-platform/settings/email-notifications)
* [**Roles / Permissions / User labels**](/vendor-platform/settings/roles-permissions-user-labels)
* [**Audit Log**](/vendor-platform/settings/audit-log)
* [**Billing**](/vendor-platform/settings/billing)
* [**OAuth Configuration**](/vendor-platform/settings/oauth-configuration)


# Two Factor Authentication \_2FA\_

Enabling Two-Factor Authentication (2FA) enhances the security of your account by requiring a second layer of authentication. This page provides instructions on how to enable 2FA for all platform users on the vendor account and how to complete the 2FA setup upon logging in.

## Enable 2FA for platform users in the Vendor Platform

{% stepper %}
{% step %}

### Navigate to Settings

* Go to the `Settings` section in the sidebar and click on the `Preferences` tab.

![Preferences Tab](/files/516b21b81b9c34a0d31f7ed85df3048b9cf842f6)
{% endstep %}

{% step %}

### Enable 2FA Requirement

* Scroll down to the `Security` section.
* Click on the `Enable 2FA` button to make 2FA mandatory for all users within your company account.

![Enable 2FA](/files/b88b9fc6f4d8dbfae8684f64a8e7ef6ae18bcd00)
{% endstep %}

{% step %}

### Save Changes

Once 2FA is enabled, it will be required for all users during their next login.
{% endstep %}
{% endstepper %}

## Complete the 2FA Setup When Logging In

The next time you log in, you’ll be prompted to configure 2FA for your account. Follow these steps:

{% stepper %}
{% step %}

### Install a Mobile Authenticator App

* Download and install one of the supported authenticator apps on your mobile device:
  * Google Authenticator
  * Microsoft Authenticator
  * FreeOTP
    {% endstep %}

{% step %}

### Scan the QR Code

* Open the authenticator app and scan the QR code displayed on the screen.

![2FA QR Code Setup](/files/5f4eb00860d096268ea5b84445c5e7c768cd5c06)

* If you’re unable to scan the QR code, click the `Unable to scan?` link for alternative setup instructions.
  {% endstep %}

{% step %}

### Enter the One-Time Code

* In the `One-time code` field, enter the code generated by your authenticator app.
  {% endstep %}

{% step %}

### Provide a Device Name

* In the `Device Name` field, add a label to help identify the device you are setting up for 2FA (e.g., “Work Phone”).
  {% endstep %}

{% step %}

### Sign Out from Other Devices (Optional)

* By default, the option to `Sign out from other devices` is checked. You can uncheck this if you want to remain signed in elsewhere.
  {% endstep %}

{% step %}

### Submit and Complete Setup

* Click the `Submit` button to finalize the 2FA setup for your account.
* You will now be required to use your authenticator app for future logins.
  {% endstep %}
  {% endstepper %}


# Users

### Has Accepted Invitation

"Has accepted invitation" refers to the status of a user who has successfully responded to and confirmed their invitation to join an account. When a user is added to the account, they receive an email invitation that includes a link to activate their account.

If the user clicks on the activation link within the given time limit, they are considered to have accepted the invitation. As a visual indicator, a green checkmark is typically displayed to signify that the user has successfully accepted and confirmed their invitation.

However, if the user fails to accept the invite within the specified timeframe, the invitation expires. In such cases, a new invitation needs to be sent to the user for them to join the account. This time limit is in place to protect the security of the account and ensure that only authorized individuals gain access.

### User Password-Setup

Typically, initial passwords are system-generated and can be a weak link in security if not promptly updated.

#### a) API

Inspect the `is_initial_password` field to determine whether a user is still using the initial system-generated password. Use the following endpoint to retrieve the user:

* [Retrieve License User](/management-api/license/users/retrieve-license-user)

A value of `True` indicates that the initial password of the user is still in use. In that case, prompt the user (via your UI) to change their password. You can update the password with these endpoints:

* [Set Password for License User](/management-api/license/users/set-password-for-license-user)
* [Set Password for License User by Email](/management-api/license/users/set-password-for-license-user-by-email)

#### b) User-Interface

First, check if the user has an initial password inside the platform: <https://saas.licensespring.com/>

![](/files/04b07493b9a3788818c5f46aaa9a483046036568)

{% hint style="info" %}
A value of "N/A" for `is_initial_password` signifies that the initial password has already been replaced by the user.
{% endhint %}

To change the password, the user should go to <https://users.licensespring.com/>, log in with the initial password, then navigate to MyProfile → Change Password.

{% stepper %}
{% step %}

### Check initial password

Visit the platform: <https://saas.licensespring.com/> and inspect the `is_initial_password` field for the user.
{% endstep %}

{% step %}

### Log in

Go to <https://users.licensespring.com/> and log in with the initial password.
{% endstep %}

{% step %}

### Change password

After logging in, go to MyProfile → Change Password to set a new password.
{% endstep %}
{% endstepper %}

![](/files/8d04a6b79dbe41739484bc4c1f1a58f5f0b8264c)


# Email Notifications

### What Are Email Notifications in LicenseSpring?

LicenseSpring allows the software vendor to send email notifications triggered by specific events to designated recipients. The content of these emails can be customized, as well as the sender email.

You can find the notification configurator in the vendor platform, under "Settings" > "Notifications"

![Notifications Configurer Location](/files/578ce2259398463467cc3229c2baae2bc3521e84)

{% embed url="<https://youtu.be/8CikF9Mym0I>" %}

### What Email Notifications Are Available?

The email notifications that we currently offer are:

* **Order created (or appended)** - An order was created or a license was appended to an existing order. Order/license information is sent out by email.
* **License has expired** - Expiration day of license is no longer valid; email is sent at 00:10 UTC.
* **License will expire** - Email notification intended to inform recipients of a license that is about to expire. It can be configured to be sent at different time intervals; email is sent at 00:10 UTC.
* **Assigned license user** - A user was assigned to a license (user-based licenses only).
* **Assigned license manager** - A license manager was assigned to an order.
* **Payment method added** - A user of the vendor platform added a new payment method to the LicenseSpring account.
* **Payment method removed** - A user of the vendor platform removed a payment method from the LicenseSpring account.
* **Charge succeeded** - When the credit card on file in the vendor platform is charged, we can send out this notification to specified recipients.
* **Invoice finalized** - After a successful charge, an invoice notification is sent to specified recipients.
* **Trial created** - Triggered when a new trial is initiated.
* **Trial expired** - Triggered when the trial period has ended.
* **Trial will expire** - Triggered when the trial period is about to end (with advance warning).
* **Assign license user is close to limit** - Triggered when the system is about to assign the maximum number of users allowed under the license (indicating it's near the limit).
* **Assigned max license users** - Triggered when the maximum number of users allowed for the license is reached.
* **Maintenance expired** - Triggered when maintenance or support associated with the license has expired.
* **Maintenance will expire** - Triggered when maintenance or support is about to expire (usually with a set warning period).
* **Feature expired** - Triggered when a feature associated with the license has expired.
* **Feature will expire** - Triggered when a feature is about to expire soon (with a predefined warning period).
* **License activated** - Triggered when a license is successfully activated.

{% hint style="info" %}
[**Contact us**](https://licensespring.zendesk.com/hc/en-us/requests/new) if there is a specific notification you would like to see and we will try to add it!
{% endhint %}

### Customize the Notification Policy

Each email policy will have slightly different configuration options. To edit a policy, click the pencil on the policy row:

![Edit Notification Policy Button](/files/61df243e7c6f262cc602a792cc131c97fcbf55ac)

#### Customizing the Recipients

When editing the policy, you can select the following recipients (depending on the policy, some of these recipients may or may not be available):

* Customer, based on the email specified for that customer
* License user, for user-based licenses
* License manager, if they are assigned to a given order
* Company users, as specified in the vendor platform
* Custom recipients, if you would like a certain email address to receive an email notification

#### Customize When to Send Reminder Notifications

The "License will expire" notification allows you to send an email a certain number of days before the license expires to desired recipients. You can specify several days prior to expiry that you would like the email notification to be sent out:

![Setting Check Days to Remind Users of Expiring Maintenance License](/files/13893b85d2f49a9fba894c35b81fc769cfd92b1a)

The logo that appears on the emails can be set under "Settings" > "Portal Configuration".

For content changes, click on the "emails" tab, and then select the notification you wish to modify.

Content that can be altered in the template:

* Message subject
* Title
* Text
* Fields (different fields are available depending on the email notification you are looking to send)
* Accent color

![](/files/8d4e5dd78de9ca8ac0bccedbd04019ae4343c17a)

{% stepper %}
{% step %}

### Previewing and Editing Email Templates

* Go to **Emails Settings**.
* Select an event.
* On the right is **Preview** to view the email.
* Edit the content on the left side if needed, then click **Save**.
  {% endstep %}

{% step %}

### Sending Test Emails

* Select an event.
* If you're satisfied with the appearance and content of the email, enter an email address and click **Send** to receive a test notification.
  {% endstep %}
  {% endstepper %}

![Test email notification](/files/04526da7071c3b1192bca113a663c11ef8b74d50)


# Roles/Permissions/User Labels

You can set custom permissions for platform users.

{% hint style="info" %}
Admin users have all permissions by default.
{% endhint %}

The options available are:

* `Product` Read/Manage/None
* `Orders` Read/Manage/None
* `Licenses` Read/Manage/None
* `Customers` Read/Manage/None
* `Analytics` Read/None
* `Settings` Read/Manage/None
* `Billing` Read/Manage/None
* `Platform users` Read/Manage/None

{% hint style="info" %}

* **Read access** to Settings allows a user to **view API keys**.
* **Write access** to Settings allows a user to **create webhooks**.
  {% endhint %}

## Creating a Permissions Template

Creating a permissions template allows you to easily set permissions for a new or existing user or group of users.

{% stepper %}
{% step %}

### Start

Navigate to "Settings" > "Permissions".
{% endstep %}

{% step %}

### Add new template

Click "Add new permissions template".

![Add New Permissions Template Button](/files/0836fd868a2d645f0b44282a64c8a03d921714a3)
{% endstep %}

{% step %}

### Configure template

Give your new permissions template a name and set the access level you wish to grant to any user that you apply this template to, then click confirm when you're done.

![Setting New Permissions](/files/6f31f091a15984a7e4c1d0ed68d0f50b62345c2c)
{% endstep %}
{% endstepper %}

{% hint style="warning" %}
Modifying a permissions template will not affect permissions of any given user.
{% endhint %}

## Setting Permissions for a New User

When adding a new user to the vendor platform, you can either assign them "Admin" privileges, or you can set permissions from any template already created in the account.

{% stepper %}
{% step %}

### Open user creation

Navigate to "Settings" > "Users" > "Add new User".

![Add New Platform Users](/files/c6aa1255e925f86fca3c7fe4823a074538cd1907)
{% endstep %}

{% step %}

### Set permissions

In the add-user popup you will have the options to set permissions or assign a permissions template.

![Setting User Permissions](/files/6b8aa5c245304f71104fa9793250636670f29ff0)
{% endstep %}
{% endstepper %}

## Applying Permissions to an Existing Platform User

### For an Individual Platform User

Navigate to the platform user detail view. From this screen, you can:

* Toggle whether this user is an administrator for the account
* Edit individual permission settings (if they are not admin)
* Apply permissions from an existing template (if they are not admin)

![Editing Permissions for Existing User](/files/d7e6b87e52ac434412c06919707d0b08c0ece9b7)

### Apply Permissions Template to a Group of Platform Users

You can apply a permissions template to all platform users that have a certain label.

![Assign Permissions Temlate to User Label](/files/8621a82bb572e2749a405f6c1418b88e2792cc60)


# Audit Log

The audit log is a useful table to keep track of all essential actions performed within an account, and who performed them.

### Where Audit Logs Can Be Seen

#### In Aggregate

It can be found in aggregate by navigating to "Settings" > "Audit logs":

![Platform Audit Logs](/files/fa7878f10ff8663612ca8b733f2df2efa67b0e82)

#### Per Individual Item

A table under each license, customer, order or product will contain logs for any action carried out on a particular item. They currently appear at the bottom of the following views:

* License detail view
* Order detail view
* Customer detail view
* Product detail view

![Individual Item Logs](/files/8728c3cc51a09f6df2cb9c22a85786f9ff5694d5)

### Actions Recorded Within the Audit Log

Here is a list of actions that are recorded when taken by a platform user:

#### Platform Users

* `platform_user_added`
* `platform_user_updated`
* `platform_user_removed`

#### Permissions Template for Platform Users

* `user_permission_template_created`
* `user_permission_template_updated`
* `user_permission_template_permissions_set`
* `user_permission_template_deleted`

#### Email Notifications

* `notification_policy_created`
* `notification_policy_updated`
* `notification_policy_deleted`

#### Products

* `product_created`
* `product_updated`
* `product_activated`
* `product_deactivated`
* `product_archived`
* `product_unarchived`
* `product_deleted`

#### Customers

* `customer_created`
* `customer_updated`
* `customer_deleted`

#### Orders

* `order_created`
* `order_updated`
* `order_manager_added`
* `order_manager_removed`
* `order_deleted`

#### Licenses

* `license_created`
* `license_updated`
* `license_reset`
* `license_disabled`
* `license_enabled`
* `license_consumption_reset`
* `license_user_assigned`
* `license_user_unassigned`
* `license_features_updated`
* `license_deleted`
* `license_custom_field_created`
* `license_custom_field_updated`
* `license_custom_field_deleted`

#### Devices

* `device_reset`
* `device_blacklisted`
* `device_borrowed`
* `device_borrow_released`

{% hint style="info" %}
Data Retention Period

The audit log is currently indefinite.
{% endhint %}

{% hint style="info" %}
Exporting the Audit Log

The audit log can be exported on a case-by-case basis.
{% endhint %}


# Billing

The billing section allows a platform user to perform billing-related tasks such as:

* Add payment information if paying by credit card
* Specify billing details that should appear on the invoice
* Retrieve invoices
* Add tax information

{% hint style="info" %}
LicenseSpring accepts payment by credit card for Monthly and for Annual term payments, and accepts Wire Transfers for Annual payments. To pay by Wire transfer, please contact us.
{% endhint %}

{% hint style="info" %}
You can set permissions of platform users to restrict who can view and who can manage the billing section of your LicenseSpring account.
{% endhint %}

## Pay Your Bill by Credit Card

{% stepper %}
{% step %}

### Add a payment method

Go to Settings > Billing and click on "Add Payment Method".
{% endstep %}

{% step %}

### Enter card details

Enter your credit card information on the screen displayed.
{% endstep %}
{% endstepper %}

{% hint style="info" %}
If you do not see the "Add payment method" button, your account might have been configured for manual payment collection. If you wish to pay automatically by credit card, please contact LicenseSpring's customer support.
{% endhint %}

![Adding Payment Method for Billing](/files/c9ce6110c996d4e5fa6215243111f929075b5c67)

![Enter Payment Method Details](/files/c16f98abbac5fbac295e106a7982265b8f21da91)

## Change Billing Details

You can add your billing address and a point of contact by clicking on "Change Billing Details".

* This information will appear on the invoices created.
* The invoice will be sent to the email address specified.

![Changing Billing Details](/files/b148b56dc65e0ad7f019208a306cfaa9c41cb2b5)

## Add/Edit the Tax Identification Number

Scroll to the bottom of the billing detail view. You can specify the type of Tax ID you would like to add, and then enter the identification number itself.

![Changing Tax Details](/files/ea012a8b8103765c57f71f64fa3140bfa78c0701)

![Selecting Tax Types and Number](/files/30c176e528432995c81088dc6b2b57d73f63d979)

The Tax ID will be added to the invoices we issue.

## Receive Invoice by Email

The email address you specify under billing will receive the invoices. You can also set up an email notification and specify additional email addresses to receive the invoice when it's been finalized. Email notifications are set up under Settings > Notifications.


# OAuth Configuration

OAuth authorization is an alternative authentication mechanism for the License API, available exclusively for **enterprise clients**.

## OAuth Client ID and Secret Structure

OAuth credentials (Client ID and Secret) can be associated with different levels of specificity within the system:

* **License**
* **Customer**
* **Customer Account**
* **Company**

### OAuth Token Validation Hierarchy

When validating OAuth tokens, the system follows a hierarchy of specificity:

{% stepper %}
{% step %}

### Most specific: License level

Check for an OAuth secret at the **License** level.
{% endstep %}

{% step %}

### Customer level

If not found at the License level, check at the **Customer** level.
{% endstep %}

{% step %}

### Customer Account level

If not found at the Customer level, check at the **Customer Account** level.
{% endstep %}

{% step %}

### Least specific: Company level

If not found at the Customer Account level, check at the **Company** level.
{% endstep %}
{% endstepper %}

### User Interface Access

* **License, Customer, and Customer Account Views**: OAuth settings are accessible via the **OAuth tab**.
* **Company Level**: OAuth settings can be found under **Settings → Keys**.

![OAuth Initialization Location on Platform](/files/eb30fdce56cfacf366ee7abb6d06653221000a56)

## Default Rotation Settings

* When creating new OAuth credentials, the system applies **default rotation settings**, unless overridden for specific credentials:
  * **Secret Expiration Period**: Defines the time before a newly generated secret expires.
  * **Secret Rotation Grace Period**: Specifies the period before the current secret expires, allowing for a smooth transition.

![Setting the Default Rotation Settings](/files/c48a93d5f274296c6d5d267caa1baffb02d2941d)

## Enforcing OAuth Authentication

A company account may have an optional flag, `is_oauth_required`, when enabled:

* OAuth authentication is **mandatory** for all users under that company account.
* API key and shared key authentication methods are **disabled**.

This setting is available under **Settings → Preferences**.

### API Error Response

If a request is sent using an API/shared key while OAuth is required, the License API will return the following error:

```json
{
    status: 400,
    code: 'oauth_required',
    message: 'This account requires OAuth authentication'
  }
```

## Authorization Process

To authorize with OAuth:

{% stepper %}
{% step %}

### Obtain short-lived access token

Users obtain a **short-lived access token** using their **Client ID** and **Secret**.
{% endstep %}

{% step %}

### Include token in requests

The access token must be included in the **License API request headers**, replacing the traditional API key or shared key.
{% endstep %}
{% endstepper %}

For more details, refer to [**OAuth Authorization**](/license-api/license-api-authorization/oauth-authorization).

## SDK Integration

OAuth authentication will be implemented in the SDKs to simplify integration. See your SDK specific documentation for more information.

## OAuth Key Management

### License Manager Permissions

License managers can:

* View a list of client IDs and secrets for the licenses and customers they manage.
* Revoke OAuth secrets.
* Manage expiration dates of OAuth secrets.

### Explicit Permission Requirement

License managers can **only** revoke or modify expiration dates of OAuth keys if they have been granted explicit permission to do so.

![](/files/d66796368b5550071278adb70149833974c19e47)

## OAuth Expiration Notifications

Platform users can define **notification policies** for OAuth key expiration. These policies trigger email notifications when:

* A secret is about to expire.
* A secret has already expired.

![](/files/60ccaffd6bdcd97987076a199684408bd83d7539)


# Importing/Exporting Data


# Customers Import

Importing customers from CSV allows you to quickly upload customer data in bulk, saving time and ensuring consistency when managing customer information. This page provides step-by-step instructions for using both the platform interface and the Management API to import customer data.

## Importing Customers via Platform Interface

{% stepper %}
{% step %}

### Navigate to the Customers Import Section

* Open the `Customers` menu in the sidebar and click the `Customers Imports` tab

![Customers Imports Tab](/files/5542f0296434dc825723db70b3965faa946f7bb3)
{% endstep %}

{% step %}

### Prepare Your CSV File

* Populate your CSV file with your license data.
* There are other **optional headers** that can be included in this CSV file for more detailed configurations, such as `first_name` and `last_name`. See: [Import Customers from CSV](/management-api/customer/import-customers-from-csv).
* Optionally, you can download a pre-formatted CSV template to ensure your data conforms to the required structure:
  * [customers\_import\_template.95c819f1.csv](https://api.archbee.com/api/presign/1qQvw4Dswzp513JIpU4f9-fbfYPoMmkPPqxNby5YDHt-20241204-022545.csv)
    {% endstep %}

{% step %}

### Upload Your CSV File

* Drag and drop your prepared file into the upload area or click to browse and select your file. The platform accepts `.csv` files only.

![CSV Upload Area](/files/b8cdfc6dfd6b1c614f43097ac637f50d6bf585d5)
{% endstep %}

{% step %}

### Monitor the Upload Status

* Once uploaded, the platform will validate your data and display the status in the `Imports List` section.
* If successful, the status will show as `success`. Otherwise, review the error messages, correct the data, and re-upload.

![Imports List and Status](/files/f2c273a4f5d2ebd1203606569e63e9eeab65c644)
{% endstep %}
{% endstepper %}

## Importing Customers via Management API

For users looking to programmatically import customer data, the Management API provides this functionality.

More information regarding this style of importing can be found at [**Import Customers from CSV**](/management-api/customer/import-customers-from-csv), our page outlining the Management API endpoint for performing the equivalent action.


# License Import

Importing licenses refers to the ability to bulk upload license data efficiently using a pre-defined CSV file. This page walks you through the entire process, including where to find templates, how to structure your CSV file, and how to upload it through the platform interface or Management API.

{% stepper %}
{% step %}

### Navigate to the Licenses Import Section

* Go to the `Licenses` menu on the sidebar and click the `Licenses Import` tab, as shown below.

![License Import Tab](/files/b681ad14666c2264d796a462dca03b0098c2b2ad)
{% endstep %}

{% step %}

### Prepare Your CSV File

* Populate your CSV file with your license data. Ensure you include the **required headers**, such as `license__license_key` and `product__short_code`.
* There are other **optional headers** that can be included in this CSV file for more detailed configurations, such as `license__max_activations` and `license__validity_period`. See: [Import Licenses From CSV](/management-api/license/import-licenses-from-csv).
* Optionally, you can click the `Download import template file` button to download a pre-formatted CSV file. This template ensures your data conforms to the required structure:
  * [license\_import\_template.31dc5f35.csv](https://api.archbee.com/api/presign/1qQvw4Dswzp513JIpU4f9-5LIH-vNz5kVTNoHEf1dBY-20241204-011307.csv)
    {% endstep %}

{% step %}

### Upload Your CSV File

* Drag and drop your prepared file into the upload area or click to browse and select your file. The platform accepts `.csv` files only.

![CSV Upload Area](/files/9d998fca2ce708fdc1964d19623001bb8bd71097)
{% endstep %}

{% step %}

### Monitor the Upload Status

* Once uploaded, the platform will validate your data and display the status in the `Imports List` section.
* If successful, the status will show as `success`. Otherwise, review the error messages, correct the data, and re-upload.

![Imports List and Status](/files/64ee55fb072af63bc98c2fa222f3c01ab49313d0)
{% endstep %}
{% endstepper %}

### Importing Licenses via Management API

For advanced users, the platform also supports license imports programmatically through the Management API.

More information regarding this style of importing can be found at [**Import Licenses From CSV**](/management-api/license/import-licenses-from-csv), our page outlining the Management API endpoint for performing the equivalent action.


# License Export

The license export feature enables you to extract license data into a CSV file for reporting, analysis, or backup purposes. Using filters, you can customize the export to include only the data you need. This page provides step-by-step instructions for exporting licenses through the platform interface.

### How to Export Licenses from the Platform

{% stepper %}
{% step %}

### Navigate to the Licenses Section

Open the `Licenses` menu from the sidebar and select `All Licenses`, as shown below:

![All licenses tab](/files/88796df0bf713da9eab58450d88d20b94f2b394f)
{% endstep %}

{% step %}

### Apply Filters (Optional)

* Use the `Search Filters` option to narrow down the licenses to be exported.
* Available filters include:
  * **Customer Labels**: Filter licenses by associated customer labels.
  * **Is Disabled**: Include or exclude disabled licenses.
  * **Expiration Date**: Filter licenses based on their expiration date.
  * **Product**: Export licenses associated with a specific product.

![Search filters button](/files/945d1740e8aa686710de3fcf854312e97fa50464)
{% endstep %}

{% step %}

### Initiate the Export

Click the `Export Licenses` button located in the top-right corner of the licenses page.

![Export licenses button](/files/cd5faf9144bdb697ce4fa0c56372e9ca0e68a922)
{% endstep %}

{% step %}

### Set Export Options

In the **Export Licenses** dialog:

* Choose a **date range** for the licenses you want to export.
* Review and confirm any applied filters.
* Clear filters directly in this dialog if necessary.

![Export Options Pop-Up](/files/32118d505daea88061ed2049ef6bfead46b903ef)
{% endstep %}

{% step %}

### Confirm and Export

* Click the `Confirm` button to start the export process.
* The platform will generate a CSV file containing the license data that matches your filters.
  {% endstep %}
  {% endstepper %}

### Exporting a License via Management API

Exporting licenses via the Management API is handled with the same endpoints as exporting orders.

* For a single export, see [Export Order](/management-api/order/export-order).
* For bulk export, see [Export Orders](/management-api/order/export-orders).


# License Features Import

Importing license features allows you to upload multiple features in bulk, expediting the process of managing and associating features with licenses. This page outlines the steps for importing license features using both the platform interface and the Management API.

### Importing License Features via Platform Interface

{% stepper %}
{% step %}

### Navigate to the License Features Import Section

* Open the `Licenses` menu in the sidebar and select the `License Features Import` tab.

![License Features Import Tab](/files/998e77edd0eebd30fad2d088b460d66977f61206)
{% endstep %}

{% step %}

### Prepare Your CSV File

* Populate your CSV file with your license data. Ensure you include the **required headers**, such as `license_key` and `code`.
* There are other **optional headers** that can be included in this CSV file for more detailed configurations, such as `max_consumption` and `expiry_date`. See: [Import License Features From CSV](/management-api/license/import-license-features-from-csv).
* Optionally, click the `Download import template file` button to download a pre-formatted CSV file. This template ensures your data conforms to the required structure:
  * [lpf\_example.66d33fd0.csv](https://api.archbee.com/api/presign/1qQvw4Dswzp513JIpU4f9-rGr6MwzZl96SU_grO6qn4-20241204-021000.csv)
    {% endstep %}

{% step %}

### Upload Your CSV File

* Drag and drop your prepared file into the upload area or click to browse and select your file. The platform accepts `.csv` files only.

![CSV Upload Area](/files/fc73c30b6e9f410f83e600ebf691b2f781847618)
{% endstep %}

{% step %}

### Monitor the Upload Status

* Once uploaded, the platform will validate your data and display the status in the `Imports List` section.
* If successful, the status will show as `success`. Otherwise, review the error messages, correct the data, and re-upload.

![Imports List and Status](/files/b0d59cfaf72ac1961083ea92030a67f9cfcf174b)
{% endstep %}
{% endstepper %}

### Importing License Features via Management API

For users who prefer programmatic imports, the Management API provides a simple endpoint to import license features from CSV files.

More information regarding this style of importing can be found at [**Import License Features From CSV**](/management-api/license/import-license-features-from-csv), our page outlining the Management API endpoint for performing the equivalent action.


# Orders Export

The export orders functionality allows you to extract detailed order data in CSV format for reporting, auditing, or analysis. You can export orders through the platform interface or programmatically using the Management API.

### Exporting Orders via Platform Interface

{% stepper %}
{% step %}

### Navigate to the Orders Section

Open the `Orders` menu from the sidebar and select `All Orders`, as shown below:

![All Orders Tab](/files/734e168dec73ca727003e632a277f4270b3826e8)
{% endstep %}

{% step %}

### Apply Filters (Optional)

Use the `Search Filters` option to narrow down the licenses to be exported.

Available filters include:

* **Customer Labels**: Filter licenses by associated customer labels.

![Search filters button](/files/a6de33ae939da4ab92a9890a3ea51f4429091843)
{% endstep %}

{% step %}

### Initiate the Export

Click the `Export Orders` button located in the top-right corner of the orders page.

![Export Orders Button](/files/493e3124a3d1c3e9131e0a6ca8be57815920da75)
{% endstep %}

{% step %}

### Set Export Options

In the `Export Orders` pop-up:

* Choose a **date range** for the licenses you want to export.
* Review and confirm any applied filters.
* Clear filters directly in this dialog if necessary.

![Export Orders Pop-up](/files/414d827ff70ba6c2f589f0e2986fa55c11035c66)
{% endstep %}

{% step %}

### Confirm and Export

Click the `Confirm` button to start the export process.

The platform will generate a CSV file containing the license data that matches your filters.
{% endstep %}
{% endstepper %}

### Exporting Orders via Management API

#### Exporting a Single Order

If you need to export a single order programmatically, the Management API provides an endpoint for this purpose.

For more information on how to use this endpoint, see [**Export Order**](/management-api/order/export-order).

#### Exporting Multiple Orders via Management API

The API also allows for exporting multiple orders in bulk.

For more information on exporting multiple orders, see [**Export Orders**](/management-api/order/export-orders).


# Configuration

![Products within your LicenseSpring account](/files/39a42cb322d21a7e854aba4809d8283426eee13e)

### What Is a Product?

In LicenseSpring, a product is what you can issue licenses for. This is usually a software application, but it could be an operating system, or even a hardware device.

In order to issue and manage licenses, you will need to have a product to issue licenses for.

### Initializing a Product

{% stepper %}
{% step %}

### Start product creation

* Click "Configure Products" in the left-hand navigation.
* On the Product summary view, click "Add a new product".

![Creating New Product](/files/bc71c07af8498bc1cac30e629ce968f7f97bc60c)
{% endstep %}

{% step %}

### Enter product identity

* Enter a `Product name` and a `Product code`.
  * `Product name` is used only in the vendor platform (can change later).
  * `Product code` is used in places like API calls and when issuing licenses; it cannot be changed once set.

{% hint style="info" %}
The product code must be unique and comprised of only lower-cased alpha-numeric characters. No spaces or special characters are permitted.
{% endhint %}
{% endstep %}

{% step %}

### Select authorization and options

* Select the `Authorization method` for your product: `key-based` or `user-based`.

{% hint style="warning" %}
A product can be either key-based or user-based. You cannot change the authorization method once the product has been initialized.
{% endhint %}

* Optionally enable `Product has a trial period` and set the default validity period for trial licenses.
* Set `Floating timeout` to define the default heartbeat for any floating licenses issued (this can be overridden per license).
* Optionally enable `Product has file` to specify an initial release version of the product.

![Inserting Product Information](/files/3da1c7b02d215ee3e2b965de679ac82cda1febb4)
{% endstep %}

{% step %}

### Review and create

* Click `Next`.
* Review the details you entered.
* Click `Create Product`.

Your new product will appear in the product summary screen.

![Create New Product](/files/908f9f037f653d5c78736d897d1a79578edfd892) ![Product List](/files/043fefd02a12dd8ca20aaa9df96beb22afdc9a9b)
{% endstep %}

{% step %}

### Access product details

* Click anywhere in the row containing your product name to open the product detail page.

![Product Detail Page](/files/62e6b118c1c62c3cac3884e6480a7e2ee47b1bf5)
{% endstep %}
{% endstepper %}

### Disable a Product

Disabling a product prevents issuing licenses for that product. The Status on the Product List View will change from `Active` to `Disabled`.

A disabled product can be re-enabled at any time and will still appear in the Product List. You can still edit the product (for example, add features, custom fields, and License Policies).

To disable a product:

* Go to the product detail screen and click "disable product".

![Disabling a Product](/files/2fae903f6219c71a1e87cbc82e20dc39a65c4c7c)

It will then appear as disabled on the Product List View.

### Archive a Product

Archiving a Product removes it from the product list and will disable it if it is enabled.

* You cannot modify archived products.
* You can `Reactivate` an archived product.

Find archived products in their own tab on the Product List. Click the "Reactivate" button on the respective row to reactivate a product.

![Archieved Product List](/files/fe930915cb3bda9634113cdced5dc8b3dd722816)


# Product Features

### What Is a Product Feature?

In LicenseSpring, a Product feature is an attribute that is defined for a given product, and can be added to any license issued for that product. In LicenseSpring, we define two product feature types:

#### Activation Feature

![Adding Product Feature](/files/ff920cb1c4317a6f3712b34ef7548b29bea64e5e)

Activation Features are meant to be used to turn on or off specific functionality within your licensed product, such as a component, module, or set of features.

Once defined for a product, an activation feature can be added or removed from a license, without additional functionality.

#### Consumption Feature

Consumption Features are meant to meter the usage of a component or resource, such as an API, the number of times a Report is generated, or amount of time (in seconds or minutes) that a resource is used.

Consumption features, like Activation features, can be added or removed from a given license. However Consumption features also have the attributes `Max consumption`, `allow overages` (Boolean), a `Max overages` value, and a means to reset the consumption allowances periodically.

![Consumption Period Selection](/files/7b71977e586ada4da6a1bc3df6b1e5c36a9b7ecd)

#### Time limited features

Optionally you can define an expiration for the features. You can either set expiration to a specific date (`Expiry date`) or you can set a Valid duration to a feature which is then used to calculate a specific expiry date at the time of the first license activation.

Features that do not have expiry\_date defined are never expired.

License API will also return `is_expired` Bool for every feature. To include expired features in a license check and license activation response, use the `include_expired_features` parameter. See: [Check License](/license-api/license-check/check-license).

Feature expiration policy can be defined on a product features, license feature and license policy level.

![Feature Expiration Settings](/files/6f56b5b1b05453aab40b1dbf092be08180000db5)

#### Floating features

Features can also be configured to support concurrent usage, same as on a license level.

More details about this usage can be found in [Floating Features](/license-entitlements/floating-features).

{% hint style="info" %}
Depending on your plan you will have access to a certain number of Features and there may be limitations on the type of feature you are allowed to use.
{% endhint %}

Define a New Product Feature

Required Fields

* Feature Name
* Feature Code

Fields for Consumption Features

* Max Consumptions
  * Allow Unlimited Activations
* Allow Overages
  * Max Overages
* Reset Consumption
  * Consumption Period

{% hint style="info" %}
The upper limit for all consumption-related fields (`Max Consumptions`, `Max Overages`) is `Number.MAX_SAFE_INTEGER`. For the exact value, see the [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MAX_SAFE_INTEGER#value).
{% endhint %}

Once you defined a product, click on the "Features" Tab, and click "Add product feature"

![Adding New Product Feature](/files/ffc4cd49f8fb848c654f9be4218a302d3942bc7e)

Give the Feature a Name, a Code, and select its type.

![Selecting Product Feature Type](/files/bf23051678c6f130fbd99dbce8e741807ce8b4e4)

If the feature is of type consumption, set the default value for Max Consumption, the Value for Max Overages, as well the time interval for which the allowances should reset

![Adjusting Consumption Period](/files/d2fba9eb315f1a962f90b2c5545f16c4fb5ab39c)

{% hint style="info" %}
Adding default allowances for a product feature can always be changed when issuing a new license, or by modifying the entitlements of an existing license. For more information, please visit the "Features" article in the "License Entitlements" section.
{% endhint %}

#### Editing/Deleting Product Features

{% hint style="info" %}
You can rename a product feature, and the default values for consumption features can be modified. However, you cannot change a product feature code.
{% endhint %}

{% hint style="danger" %}
If you delete a product feature, it will be removed from any licenses that are currently using it. Adding the feature back to the product will not add the feature back to the licenses. LicenseSpring does not have any concept of archiving features!
{% endhint %}

To edit a product feature, go to the features list and click on the Pencil icon on the row for that product feature.

To delete a product feature, click on the red garbage can icon on the row for that product feature.

![Features List Actions](/files/a24ff09ff5e1a4840657564272abc9e324e97895)

#### Feature groups

On a product level, you can group features to groups for easier feature licensing. When issuing licenses you do not need to list all of the features you want for this licenses - you just define a group and attach a group code during order creation.

![Feature Groups Tab](/files/746947ffc1890693d1e19dc0a06b1282cdaca012)

Groups behave similar to license policies in a way that you can also define time limited settings on a group level that would be applied to all license features when license is created.

![Adding Feature Group Form](/files/edb549f1da743474535c0c02232dcbb05cc6485a)

Feature groups can be applied to a license policy or used directly when creating order.

![Edit License Policy View](/files/7d561f9f4b3cf9c91ee2cbde5bbbbcada1a49080)

{% hint style="info" %}
If you at the same time apply a feature both as a standalone feature and in a group, a standalone feature settings will apply.
{% endhint %}


# Global Features

### Global Feature

In LicenseSpring, a **Global Feature** is an attribute that is not tied to any specific product, and can be added to licenses issued for any product. Global features provide flexibility when you need to define attributes that apply across multiple products rather than being limited to a single one.

![](/files/44f0d7d8359a8f619d0e65e107c922977d93904e)

### Activation Global Feature

![](/files/334980359195869c5165d339778ee028210e9ff5)

Activation Features are used to enable or disable specific functionality within a licensed product, such as a component, module, or set of features.

Once defined, an Activation Feature can be added to or removed from a license as needed. It does not include additional attributes beyond its activation state.

### Consumption Global Feature

Consumption Features are used to meter the usage of a component or resource, such as an API, the number of times a report is generated, or the amount of time (in seconds or minutes) that a resource is used.

Consumption Features, like Activation Features, can be added to or removed from a license. However, Consumption Features also include the attributes Max Consumption, Allow Overages (Boolean), Max Overages value, and an option to reset consumption allowances periodically.

![](/files/d4d8b8c0c18be1ff0ae6d2730138639dd3de218e)

### Time limited Global features

Optionally you can define an expiration for the features. You can either set expiration to a specific date (`Expiry date`) or you can set a Valid duration to a feature which is then used to calculate a specific expiry date at the time of the first license activation.

Features that do not have expiry\_date defined, are never expired.

License API will also return `is_expired` Bool for every feature. To include expired features in a license check and license activation response, use the `include_expired_features` parameter. See: [Check License](/license-api/license-check/check-license).

Feature expiration policy can be defined on a global feature, product features, license feature and license policy level.

![](/files/3db0144b55030d1e8af30bcc41d56d4fcec1399b)

### Define a New Global Feature

#### Required Fields

* Feature Name
* Feature Code
* Feature Type

#### Fields for Consumption Global Features

* Max Consumptions
  * Allow Unlimited Activations
* Allow Overages
  * Max Overages
* Reset Consumption
  * Consumption Period

To add a new **Global Feature**, use the left sidebar, click on **Products,** and then select **Global Features.**

![](/files/98c68df1b738e2f259420a1b1a3cd9c7913ad9c0)

Give the Feature a Name, a Code, and select its type.

![](/files/52cd200b6c617c1b072f08dd5a1ce66527d3510f)

If the feature is of type consumption, set the default value for Max Consumption, the Value for Max Overages, as well the time interval for which the allowances should reset

![](/files/0f25e70c0183a40693c8342e524aaff1c1e57930)

### Editing/Deleting Global Features

{% hint style="info" %}
You can rename a global feature, and the default values for consumption features can be modified. However, you cannot change a global feature code.
{% endhint %}

{% hint style="danger" %}
If you delete a **Global Feature**, it can be removed from licenses, but you can **add it back to any license at any time**. Unlike Product Features, Global Features are not permanently tied to licenses, so there is no restriction on re-adding them.
{% endhint %}

* To edit a global feature, go to the features list and click on the Pencil icon on the row for that global feature.
* To delete a global feature, click on the red garbage can icon on the row for that global feature.

![](/files/09b53281d85cfb71a8d7171fbf1bd032d79045c3)

### Global Feature groups

Features can be organized into feature groups to simplify license management. When creating a license, instead of adding each feature individually, a group code can be attached, and all features within that group will be included automatically.

![](/files/d35a5a8b3ee291f398e19a550ab1117473136140)

Groups behave similar to license policies in a way that you can also define time limited settings on a group level that would be applied to all license features when license is created.

![](/files/c2bd36e1557491a3bbc20db8ee33fbbddb74a59d)

![](/files/b535baeddcc3379b72c26feca49c2ffdd005612d)

Feature groups can be applied to a license policy or used directly when creating order.

![](/files/b269c1f291b33bbbca04d11c94f4258592fb6760)


# Product Custom Fields

{% hint style="info" %}
Starting with Platform Version **v3.222.0** Custom Fields support an allowed values List.
{% endhint %}

Product custom fields allow you to define key:value pairs for any product. For a given license, you can override the value of the product custom field with a license custom field value.

{% hint style="info" %}
When you add a custom field to a product, all licenses for that given product will have that field added to them, and will show up on license checks and activations. This field will appear on the server responses if you do not override the value for that given license.
{% endhint %}

### Example Use-Cases for Custom Fields

There are many ways you can use custom fields.

Here are a few example use-cases:

* Creating a product tier. The key might be something like "edition" and the default value could be the lowest tier, such a "lite". For a given license you may change the default with "standard" or "pro"
* Setting the number of CPU cores that a machine is allowed to run for your licensed application
* Setting a grace period for requiring online license checks

{% hint style="info" %}
We call a "Product Custom Field" the default key:value pair that you set for a given product, and a "License Custom Field" the key:value pair if you override the default value for a given license.
{% endhint %}

### Add/Remove/Edit Product Custom Fields

Open the product detail page, and click on the "custom fields" tab

![](/files/3b1b83984b0c34dae8d45edf7c734c73976c0d78)

#### Adding a New Product Custom Field

To add a new product custom field, Click "add custom field", and specify a name (key) and value for your new custom field:

![](/files/c35199b83bd570e395f43cd3ba3a35f5e40a27ad)

#### Modify/Delete Product Custom Field

Click on the pencil to edit an existing product custom field, and it will allow you to modify the name and/or the value.

Click on the red trash can to delete a product custom field.

![](/files/d528578ea3c3ddbe77085fc053bf7961f15ae9c0)

{% hint style="info" %}
Once you issue a license, you can override the product custom field with a license custom field. You can edit the value of the custom field for any license that was issued.
{% endhint %}

#### Allowed custom field values

To enhance data consistency and prevent accidental input errors, LicenseSpring supports optional enumerated (enum) allowed values for custom fields defined at the product level. When allowed values are specified, only predefined values can be assigned to the corresponding license custom field.

This introduces an additional validation layer while preserving backward compatibility and flexibility.

#### Behavior and Logic

| Condition                 | Allowed license custom field values                          |
| ------------------------- | ------------------------------------------------------------ |
| No allowed values defined | Any string                                                   |
| Allowed values defined    | Values in the list **OR** default product custom field value |

{% stepper %}
{% step %}

### Default behaviour (No allowed values defined)

If a custom field on a product does not have an allowed values list defined:

* The associated license custom field can accept any string as its value. This allows full flexibility, including the ability to update or correct values without restriction.

![](/files/0bd82519b8e4647be264a1a0242cfcc924ac8766)
{% endstep %}

{% step %}

### Allowed values defined on a product custom field

If a custom field on a product has an allowed values list defined:

* The associated license custom field can only accept:
  * One of the list values, OR
  * The product custom field’s default value.

This restriction helps ensure data integrity by limiting inputs to known, validated values.

Notes:

* If a **default value** is set, it is automatically added to the list of **Allowed Values**.
* Users can **manually remove any allowed value**, including the current default (prompting selection of a new default).
* The **default value must always be one of the allowed values**.

![](/files/4494e7774d58063fd5e9b779b462319262a3e0f2)
{% endstep %}
{% endstepper %}

#### Deletion of allowed values items

If a value (e.g., `val3`) is removed from the allowed values list on the product custom field:

* Existing license custom fields that already had `val3` set will retain this value.
* These values are not retroactively invalidated or overwritten.
* However, `val3` can no longer be set as a value in new license custom fields or updates.

This ensures stability and backward compatibility across issued licenses.

### Configuration via Vendor Platform

Allowed values for custom fields can be configured directly within the Vendor Platform or using the Management API.

When creating or editing a product custom field, vendors can:

* Leave the allowed values list empty to allow free-form values.
* Define a list of allowed values to enforce validation at the license level.
* Set a default value which remains valid even if not in the current allowed vlaues list.

![Product custom field allowed values](/files/2ac16c65c0e9e853b043ab377deb2adb4c86d947)

* **“Reset to allow any value”** clears all allowed values with a confirmation prompt.

![](/files/412c238032b93a49fed73978f07e2930700197c7)

**“Reset All License Custom Fields”** This action will reset all License Custom Fields for every license issued under this product.

![](/files/f24ee1845421d1f6aed1b1ab649310ae0fc353ee)


# Product Versioning

The LicenseSpring service can be configured to return product version information on Activation, license checks, and other webhooks through the License API.

{% hint style="info" %}
LicenseSpring does not host binaries to product versions. We simply provide a mechanism to handle product versions for a license
{% endhint %}

### Add a Version to a Product

To add a new version of your product, open the product detail page, click the Versions tab, then click "Add new version".

![Adding New Product Version](/files/e6f6d98b900c5af9d8e9acc985cff345ec2e18e0)

### Product Version Fields

#### Required Fields

* `Environment`
* `Version Number`
* `Release Date`

#### Optional Fields

* `Requires Version` — Specify the minimum version required for someone to be able to update to the version being added. For example, if you are adding a new version 3.0.0 and you set "Requires version" to 2.0.0, then anyone with a version less than 2.0.0 will not be able to upgrade to 3.0.0.
* `File hash` — MD5 which you can use to help verify the integrity of the binaries being installed.
* `File size` — Defined in bytes, communicated so that you can display this information when offering a new version update.
* `Link to file` — Link to download the binaries of the new version.
* `Link to release notes` — Intended as a web page or text file to display release notes on the version.
* `Link to EULA` — Intended to show a web page or text file with the License Agreement for the app or unique to the app version.
* `Channel` — Could refer to a specific distribution or release channel for your software product versions. Different channels may serve different purposes (stability, testing, early access, etc.).

![Product Versions Fields](/files/f882bcab6eb428cde3317b71518a24c45a01701d)

### Using Product Versions

The License API has a [**Get Product Versions**](/license-api/get-product-versions) webhook that allows you to retrieve all product versions for a given channel and environment. You can compare the release date of a product version with another timestamp, such as a license expiry date or maintenance window, to determine whether the version is allowed for this license.

Similarly, you can check the latest product version against the version currently installed to conditionally prompt the user to download an update to their software using the [**Get Installation File**](/license-api/get-installation-file) endpoint.

#### Link License to max product version

LicenseSpring supports linking licenses to specific product versions, allowing fine-grained control over license applicability. This enables scenarios where a license should only be valid up to a designated version of a product.

![Product version on a license](/files/cc2690179d7b6572e46b446230bba6c70a4ef839)

If a license is linked to a version, any license check or activation request must respect this version limit.

When a license is linked to a product version:

* The client may send the `app_ver` parameter to specify the version of the product they’re using.
* If the `app_ver` is newer than the version linked to the license, the API will return `product_version_not_supported` error.

{% hint style="warning" %}
This can be verified only if the `app_ver` parameter is sent with license activation (online and offline) and check requests. If a license is not linked to any product version, then no version validation is enforced. Version validation is possible only if versions follow [**semantic versioning**](https://semver.org/) format.
{% endhint %}

Product version can also be applied to a license policy. If a license is created via a license policy that has a product version assigned to it, the license inherits that product version.

{% hint style="danger" %}
License bundles are not supported with product version linking.
{% endhint %}


# License Policies

### What Is a License Policy?

{% embed url="<https://www.youtube.com/watch?v=MqpjJy_2LXw>" %}

A license policy is a templating feature within LicenseSpring to easily issue a new license according to a preset of entitlements. There are a few benefits to using license policies:

* **Less error-prone**: If you defined dozens of features and custom fields, and primarily sell your product in a few combinations of these, then license policies help you avoid configuration mistakes.
* **Easier to integrate with other systems**: For example, if you sell licenses through Stripe that are time-limited and include a specific subset of features and activation limits, instead of listing each entitlement in your Stripe integration, you can define a license policy and select it in Stripe.

{% hint style="info" %}
Every product has at least 1 license policy, and there must be at least 1 default license policy.
{% endhint %}

{% hint style="warning" %}
A License Policy is only a template. Once a license is issued, you can modify certain parts of the entitlements (e.g., extend the validity period, change the max activations, etc.).
{% endhint %}

### How to Create a License Policy?

Navigate to the license detail page and click on "License Policies" and then "Add license policy"

![](/files/4f8c7a724bff689a7058023e7e2b067dcbc52715)

On the License Policy editor, the required fields are that you give the license policy a name and a code. All other parameters are optional.

![Setting Values for License Policy](/files/fa779eb3723f7f80bcc32bbcde1c7a068e0e5ede)

### What Fields Can Be Set for a License Policy?

#### Policy Values

* `Name` — Required
* `Code` — Required, immutable
* `License type` — Required, can be Perpetual, Time-limited, Subscription, or Consumption
* `Is trial` — Optional, Boolean determining whether the license issued is a trial license
* `Max activations` — Must be a positive integer
* `Allow unlimited activations` — Boolean
* `Has device transfer limit` — Whether you wish to control the number of times a license can be moved from one device to another
* `Prevent virtual machine` — Boolean value sent to the SDKs to detect and block apps running in virtual environments
* `Offline Floating license` — Boolean, setting entitlements for a floating server that is deployed
* `is floating cloud` — Boolean setting concurrency over HTTP
* `Enable Maintenance period` — Set a maintenance window which starts counting after the initial activation of the license

#### Product Features

You can add or remove product features to the license policy. For consumption features, you can set different values for `Max consumptions`, `max overages`, and for `periodic resets`.

#### Custom Fields

You can select the values of the product custom fields you wish to override in the dropdown.

{% hint style="info" %}
The Policy name and the policy code are required fields. You cannot change the policy code once the license policy is defined.
{% endhint %}

### How to Use License Policies?

#### Using License Policies to Issue Licenses Manually Through the Vendor Platform

Through the vendor platform: navigate to Licenses > Issue Licenses. Add order and customer information and hit next. Click "Add product" and you will see the product selector screen. From here, you can choose which license policy to use when issuing a license for that given product:

![](/files/2e028531a82c39c6cd58db7c449a4dd67396eec3)

If you wish to use the License policy as a starting point when configuring the license you are issuing, then enable `Use custom license values`. The editor will start with all of the options configured in the license policy:

![](/files/2955b1022529921383a2c4e807023d6fe67d21eb)

#### Using License Policies to Issue Licenses through Stripe integration

License policy code can be added as a custom attribute to the Stripe product price so that any subscription created using this price will create licenses using that specific license policy.

More details can be found in [Stripe Integration](/integrations/stripe/stripe-integration-legacy).

#### Link License Policy to the License

LicenseSpring supports tracking and exposing the license policy code associated with a license. This enhancement provides better traceability and improves visibility into which policy was used when a license was created.

When a license is created using a specific license policy, the policy identifier is stored and made available via both API responses and the user interface.

License Creation

If a license is created by specifying a license policy the system will:

* Persist the `license_template_id` (policyID) at the license level.
* Ensure this value remains associated with the license throughout its lifecycle.

This can be achieved when creating a license using the Management API and sending a specific `license_template_id` parameter as a license entitlement field.

Alternatively, it is also assigned to a license by default when created using the platform.

If the custom values toggle is not used, the selected default policy will be assigned to a license.

![Link License Policy to a License using platform](/files/ee8f576df1be8c4357deb733b3624b93ffcb552e)

In the Vendor platform, the License details view will include a display of the linked License policy.

![](/files/1a2b5d0b7a0cce28c19b8c3f42e47f6de2d3a67c)

This ensures transparency regarding how license terms were determined, supports auditability by maintaining a clear link between licenses and their policy definitions, and enhances usability for vendor teams managing complex policy configurations.

#### License Policy as a SKU

To improve product structuring, searchability, and traceability, LicenseSpring introduces a mechanism to treat License Policies as **SKUs**. A new boolean configuration enables policies to behave like immutable identifiers tied to specific licensing terms.

This enhancement supports workflows where license policies represent pre-defined commercial SKUs and must remain consistent once assigned to licenses.

SKU Behavior Toggle

* Field: `is_sku`
* Purpose: When enabled, this marks the policy as a **SKU-like policy**.
* Effect: Licenses issued with such policies behave as if associated with a commercial SKU.

When a license is created directly using a policy marked as `is_sku = true`:

* The policy’s identifier and SKU flag are stored at the license level.
* This association ensures consistent behavior and enables future queries or logic based on SKU grouping.

To ensure SKU consistency:

* Policies marked as SKU become immutable after they are created.
* Attempts to change policy settings post-issuance will be rejected or restricted to prevent SKU drift.

![](/files/267aad9ed35897173517670eaa567720ec9b32c3)

License search functionality includes an option `Search by license policy code` and results include all licenses issued using specific policies, enabling behavior similar to product SKU filtering in e-commerce or inventory systems.

![](/files/b1b75ca36eac5b064fd2cade854e52c3236150ec)

Using SKU license policies enables product-based license tracking and reporting. It also prevents accidental modifications to SKU-critical policies, streamlines license discovery through SKU-style search filters, and ensures operational consistency in SKU-driven sales and deployment processes.


# Air Gapped License Policy

Air-gapped licenses allow ISVs to node-lock licenses on computers in air-gapped environments, where files are not allowed to leave the network once the software application has been deployed.

Instead, the software is deployed alongside license policy files, which can then be selected, and node-locked through a series of code exchanges (instead of http requests or file exchanges).

### Creating Air-Gap License Policies

On the product page ( saas.licensespring.com/{companyID}/products/{productID} ) go to `license policies` tab and click on a `add license policy` button.

To mark policy as air-gapped switch the `set as air-gapped` switcher under the title to true.

![Setting Policy as Air-Gapped](/files/361022b319e9a1fa0f6817d3edff33e5e158c4cf)

{% hint style="info" %}
&#x20;Air-gap license policies cannot be edited after creation. Additionally, standard license policies cannot be converted to air-gap policies. If you require an air-gap policy, a new one must be created from the outset.
{% endhint %}

### Creating Air-Gapped License Files

You can either download the license file from the product license policy, or, if a license was set as "air-gapped", you can download the license policy file from the license detail page.

![Locating Air-Gap License Policy](/files/bd3b0b3d4b5894c1c5f7cf968379be5167f3fab0)

![](/files/b8072d6db88d04417d61d625054dfee9e0dffbee)

### Activating an Air-Gapped License

The air-gap portal for license activation is available on this public URL: [**airgap.licensespring.com**](https://airgap.licensespring.com/)

Process of activation is done in 4 steps including actions on server (portal) and SDK:

{% stepper %}
{% step %}

### Generate an Initialization Code

To start the license activation process, the user needs to enter the license key on the portal to initialize the air-gap license activation. This will create a one-time `initialization code`.

![Initialize Air-Gap Activation](/files/3b72b187a9a2daee6fc1cde4d1e1075291bc940d)

![Initialization Code](/files/631259c25203471368013fa1c28ef0b58b159c60)

The `initialization code` needs to be imported to the SDK alongside with the license key. The SDK will verify the initialization code and return the `activation code` and `hardware_id`.

{% hint style="info" %}
The initialization will generate a QR code containing the JSON with a `license_key` and an `initialization_code` included. This can help and remove the need for copy pasting or manually entering the codes.
{% endhint %}
{% endstep %}

{% step %}

### Generate Confirmation Code

When attempting to activate an air-gapped license, the SDK should be configured to activate an Air-gapped license. It will accept the `license_key` as well as the `initialization_code` and return the `hardware_id` as well as an `activation code` along with the license key. Go back to the air-gapped portal and click on "Enter air-gap activation code" and enter them in order to receive a confirmation code.

![Entering Air-Gap Activation Code](/files/7b4a104f349ad94d21c6667f19c93c8e015e774a)

![Entering Air-Gap Activation Code Pop-up](/files/4cd6a7407f12727df20ef253b1e56343d2a2d55a)

![License Has Been Activated Pop-up](/files/c13793f92651a97e60eb24da2df94bb9e536c2e4)

The license key, hardware ID, policy ID and this new signature `confirmation code` need to be input into the SDK. The SDK validates the code using company account public air-gap key which is also previously embedded in to the SDK. If successful, the SDK will use the policy ID to retrieve the license entitlements and use this as a local license file.

Generated QR code contains the JSON with `license_key`, `hardware_id`, `policy_id` and `confirmation_code` included which you can scan and then send or save. This can help and remove the need for manually copy pasting the codes.
{% endstep %}
{% endstepper %}


# Product Bundles

A product bundle is a specific type of product which groups several products together so that they can be activated and used with a single license.

### General Process: Working With Product Bundles:

Generally, creating product bundles uses the following workflow:

{% stepper %}
{% step %}

### Initialize a product bundle

Initialize a product bundle.
{% endstep %}

{% step %}

### Issue licenses for the product bundle

Issue licenses for the product bundle.
{% endstep %}

{% step %}

### Activate each product individually using the same license

Activate each product in the bundle separately using the same license.
{% endstep %}
{% endstepper %}

### Initialize a Product Bundle

Product bundles can be created on the Products page in the platform by clicking the button Add new bundle.

![](/files/400b8116fd50978745afd21bf9f0c7b064831446)

Enter the bundle name, code, authorization method, and select all the products which are going to be included in the bundle.

![Setting Bundle Values](/files/d210b196d2d2ae134e5f5c06ae4a87e6a83527b7)

{% hint style="info" %}
The bundle code must be unique from other bundle and other product codes.
{% endhint %}

{% hint style="info" %}
You can only bundle products using the same authorization method.
{% endhint %}

{% hint style="info" %}
The Bundle code, authorization method and selected products cannot be edited after the bundle is created. You can edit their name, and delete or disable / re-enable them at a later date.
{% endhint %}

### Issuing Bundle Licenses in the Vendor Platform:

{% hint style="info" %}
An order for bundles can only contain product licenses or bundle licenses, but not both.
{% endhint %}

Start as you normally would with any other license:

{% stepper %}
{% step %}

### Open the Order Creator

Open the Order Creator, and optionally add Customer and order information. Press "Next".
{% endstep %}

{% step %}

### Toggle bundle order and configure

Toggle "Create order for product bundle".

![](/files/36e75827a7d0f85cae1e700628511020c39e78cd)

In the product bundle selector:

* Select the bundle you wish to issue licenses for.
* Specify the license policy to be associated for each product individually.
* Click "Confirm", "Next" and "Create order".
  {% endstep %}
  {% endstepper %}

![](/files/d355bf151ab422b252ecd7522467d625efd9ee4a)

![](/files/92ff1bc577ec9d803d5a468380c071f152e5fd2d)

The License Key can be found in the order:

![](/files/9ef9824c28ab67d5f7bdb471d7157413c9bfb5fd)

### Activating Licenses for Bundled Products

The License created for the bundle can be used to activate the products within the bundle. The products need to be activated separately. For example, you cannot use the bundle code to activate the license for all of the products in that bundle.

### Assigning Users to User-Based Product Bundles

Users are assigned to the user-based bundle license and can then log in to the individual products using the same credentials separately.

{% hint style="warning" %}
There is no way to assign users to individual products of a bundle. In other words, a user will be assigned to all products within the bundle. The `Max users` will therefore be the smallest value of the `Max users` of all the individual license policies used.
{% endhint %}

### Activating Licenses of Bundles through the LicenseAPI

The API interfaces remain consistent, as we continue to operate with standard licenses for processes such as activation and verification.

For instance, consider a license bundle named "Bundle PRO" with the code "bunpro," which is associated with a key-based license having the serial number 1111-2222-3333-4444. This bundle encompasses two products: "Product One" (code: prodone) and "Product Two" (code: prodtwo).

To initiate the license activation for "Product One" within the bundle, the procedure involves employing the bundle license key, the code for "Product One," and the hardware ID of the relevant device. It's important to note that the bundle license functions as a mere placeholder, serving to manage the license key and LicenseUsers information.

{% code title="activate\_bundle.js" %}

```javascript
const axios = require('axios');
const data = JSON.stringify({
  "product": "prodone",
  "license_key": "1111-2222-3333-4444",
  "hardware_id": "test-hardware-id-1",
});

const config = {
  method: 'post',
  url: 'https://api.licensespring.com/api/v4/activate_license/',
  headers: { 
    '\'Content-Type\'': '\'application/json\'', 
    'Date': 'Thu, 28 Jan 2021 09:07:27 GMT', 
    'Authorization': 'algorithm="hmac-sha256",headers="date",signature="9Km8AoACsFfRf9+JW4gcnHMmvWoPaZIwlaZ3HeZxdgQ=",apikey="5225d654-f6e5-41f0-af90-4237a597134b"', 
    'Content-Type': 'application/json'
  },
  data : data
};

axios(config)
.then(function (response) {
  console.log(JSON.stringify(response.data));
})
.catch(function (error) {
  console.log(error);
});
```

{% endcode %}

### Working with Product Bundles with the Management API

There are two primary, product bundle-specific API calls:

* [**List Products in a Bundle**](/management-api/product/list-products-in-a-bundle) shows all products in a bundle the requesting manager has access permissions to.
* [**List Bundles for a Product**](/management-api/product/list-bundles-for-a-product) shows all bundles for a product the requesting manager has access permissions to.

### bundle\_code

{% hint style="warning" %}
When using user based bundle licensing, it can happen that the same license user has multiple licenses for the same product - one license being standalone, and another as a part of the bundle. To make sure that License API recognizes which license needs to be used, you need to send the **bundle\_code** param with every call to License API.
{% endhint %}

![Bundle code](/files/ecb6b9d676fd52fc1505fdc7a8941c17f7557a72)


# Entitlements

A "license entitlement" is typically used as terminology in the licensing agreement between the licensor (software vendor) and the licensee (user/customer).

In LicenseSpring, license entitlements can be configured into the licenses that you issue to your customer. The configuration options available to a software vendor include a rich set of capabilities, such as start and end dates, the number of devices that can use a given license, whether a license can be transferred from one machine or customer to another, and more.


# License Types

One of the fundamental building blocks of a license within our model is the license type. Every license issued will always have a type. In the vendor platform, this appears in many places, such as the license summary page as well as on the license detail page.

![License Type Appearing on the License Detail Screen](/files/bb28eef0d840195a933bdda8c6e02ff54bf28b2a)

{% hint style="danger" %}
You cannot change the **license type** once it has been created.
{% endhint %}

LicenseSpring defines 4 types of licenses:

* Perpetual
* Time-Limited
* Subscription
* Consumption

Let's dive into the different license types:

### Perpetual License

A perpetual license does not expire. The `is_expired` field on a license check will always return `False`.

{% hint style="warning" %}
At the time of this writing, we distinguish a `trial license` through a Boolean value `isTrial`. If a license is of type trial, then the date stored in the validity period refers to the validity period of the trial. For example, if the license is of type `perpetual`, and `isTrial` is true, you would see a perpetual license with an expiry date in the vendor platform.
{% endhint %}

#### Statuses of a Perpetual License

* `Inactive` — The `times_activated` for this given license is `0`, meaning that this license has not been bound to a device.
* `Active` — The `times_activated` for this field is greater than `0`, meaning that the license has been bound to at least one device.
* `Disabled` — The license has been disabled by the vendor; this will override other rules for this status.

### Time-Limited License

Time-limited licenses always have a calendar date as a value in the `Expiry date` row that you can see in the vendor platform.

On a license check, the field `is_expired` will be `True` if the expiry date is earlier than the server's current date, and `False` if the expiry date of the license is at a future date than the server's timestamp.

#### Status of a Time-Limited License

* `Inactive` — The `Total Activations` for this given license is `0`, meaning that this license has not been bound to a device.
* `Active` — The `Total Activations` for this field is greater than `0`, meaning that the license has been bound to at least one device.
* `Disabled` — The license has been disabled by the vendor; this will override other rules for this status.

{% hint style="info" %}
There are other timestamps you can work with, instead of just at the license type level. For instance, you can set an expiry date for a specific feature, or define a maintenance period validity.
{% endhint %}

#### Setting a Pre-Determined Expiration Date

Users can set a pre-determined expiration date for a license that does not depend on the time of first activation by doing the following at time of issuance:

![Pre-determined expiration date example](/files/5a7d573ca96a20f3d64eec49f317d0722fca46f7)

### Subscription License

A subscription license synchronizes the status of the subscription with the state of the license.

{% hint style="info" %}
For a subscription license, LicenseSpring requires an integration to an external source of truth (like a recurring billing system). LicenseSpring updates the status of the license according to the status of the subscription, which is handled by a 3rd party.
{% endhint %}

{% hint style="warning" %}
Unlike the other license types you can encounter in LicenseSpring, the status is not dependent on the value of the `times_activated` field. Instead, a license of type subscription will be `active` once created, and will remain active until the system handling the subscription tells LicenseSpring that the subscription is no longer active (cancelled, refunded, etc). In this case, the license status will be set to `inactive`.
{% endhint %}

#### Subscription Grace Period

Grace periods offer you the flexibility to determine a specific timeframe, measured in hours, subsequent to the expiry of the designated validity period.

During this defined interval, the license will retain its validity (`isExpired()` status will remain false), ensuring a seamless transition even after the official expiration.

#### Status of a Subscription License

* `Inactive` — LicenseSpring received information from the external source of truth stating that the subscription is no longer active.
* `Active` — This is the default state of a subscription license, when it is created.
* `Disabled` — The license has been disabled by the vendor; this will override other rules for this status.

### Consumption License

A consumption license, also known as a metered license, permits usage metering. Consumption licenses work by setting a value for the resource the vendor wishes to meter, and then recording the times that resource has been used. The resource can be anything that has a discrete value, for example:

* Times the software was run
* The number of API requests made
* Number in minutes that a pooled resource was used
* Credit

Consumption licenses support overages, total consumptions can be incremented or decremented (by adding a negative value), and the total consumptions can be periodically reset.

![Options available when issuing a consumption-based license](/files/ccad456aa75fcc6935853079af182a8752e9a1db)

#### Unique Fields in a Consumption License

![Unique fields for a consumption license](/files/46e728b85d43f0c6e8e451de70425378ffa2c233)

* **Max Consumptions**: Number of "consumptions" granted to the end user.
* **Total Consumptions**: Number of "consumptions" the end-user has currently consumed. This number must be less than or equal to the max consumptions + max overages.
* **Overages**: If overages are allowed, then the software vendor can specify a value that the license can exceed the value for max consumptions by. Otherwise, the max overages is 0.
* **Consumption Period**: If this field is set, then the value for total consumptions is reset to 0. The consumption period can be `Daily`, `Weekly`, `Monthly` or `Annually`
  * `Daily` is reset at midnight UTC
  * `Weekly` is reset on Monday at midnight UTC
  * `Monthly` is reset on the first day of the month at midnight UTC
  * `Annually` is reset on the first day of the year at midnight UTC

#### Status of a Consumption License

* `Not Activated` — The Total Activations for the given license is `0`, meaning the license has never been activated or bound to a device. Newly created and never-activated licenses will appear in this state.
* `Inactive` — The `Total Activations` for this given license is `0`, meaning that this license has no active devices.
* `Active` — the `Total Activations` for this field is greater than `0`, meaning that the license has been bound to at least one device.
* `Disabled` — The license has been disabled by the vendor; this will override other rules for this status.

{% hint style="info" %}
LicenseSpring also allows an ISV to meter the usage of an individual module or component by defining features consumptions, and adding them as part of the license entitlements.
{% endhint %}


# Time Limited vs Subscription Licenses

There are important distinctions between a time-limited license and a subscription license. Although they both hold a validity period, they don't work the same way. One license type might be more appropriate to use than another depending on how you wish to enforce your licensing.

## Distinctions Between Time-Limited and Subscription Licenses

There are a few key distinctions:

| Distinction         | Time-Limited                                                                                                                                                              | Subscription                                                                                                                                                                                                |
| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| License Status      | Assuming the license is enabled, the license status will be `inactive` if the total activations of the license is 0, and `active` if total activations is greater than 0. | The status does not depend on total activations. It will be `active` when the license is created, and will only be `disabled` if the source of truth communicates that the subscription has been cancelled. |
| Offline Activations | Possible by sending a license refresh file                                                                                                                                | Not possible.                                                                                                                                                                                               |
| Grace Period        | Not implemented at the moment                                                                                                                                             | You can define number of hours after which the license will be still valid (`is_expired` will be `false`) even if `validity_period` is expired                                                              |

Time-limited licenses need to be extended manually or using management api and subscriptions should rely on some 3rd party service as a source of truth.

## When to Use Time-Limited Licenses

* If you were issuing a recurring license that is for use on machines that do not have access to the internet. In this case, you can activate licenses offline, and simply resend a refreshed license file.
* You want to ensure a license expires unless its validity period is specifically renewed.

## When to Use Subscription Licenses

* Use this only if you are using an external source of truth to maintain subscription status. This external source of truth is typically a recurring billing system or a CRM.


# Trial Licenses

All licenses have a Boolean field denoting whether it is a trial or not. This can be seen on the license summary as well as the license detail page:

![](/files/e803d7ff060fc22260bbff40d86e00ac66db9fd2)

{% hint style="info" %}
By using the `is_trial` Boolean designating a license is of type trial, you have a flag that allows you to configure your software in demo mode.
{% endhint %}

### Particularities of a Trial License

* Trial licenses are the only type of license that can be issued through the License API. In other words, your application can request and activate a trial license without the software vendor's involvement.
* By changing the Boolean value of the `is_trial` field from `True` to `False`, you can upgrade a license.
* Since any license type can also be a trial license, you might notice that a perpetual or a consumption license can also have an expiry date.

{% hint style="info" %}
When a license is a trial license, the expiry date for that license always refers to the trial expiry date.
{% endhint %}

### Setting a Trial Expiration Date Through the Vendor Platform

You can either set a fixed end date, or a set number of days from the first use of the trial license:

![](/files/7aeafc00b3021d9400a0218f63eab99577851dec)

Before activation you have two options - you can either set `trial_days` OR `validity_period` on a trial license. If both are defined on a trial license, when this license is activated, `validity_period` will take precedence and License API will use this field and ignore `trial_days`. If only `trial_days` are defined, then `validity_period` will be calculated and used to determine license validity.

Use only `trial_days` if you want the `validity_period` to be calculated at the time of activation based on this number.

Use `validity_period` if you already know, even before activation, until when this license should be valid.

### “Upgrading” a Trial License

Any trial license can be edited to modify the license so that it is no longer a trial license, but a full license for your product. To do so:

{% stepper %}
{% step %}

### Open the license detail page

Open the license detail page for that given trial license and click "Edit License".
{% endstep %}

{% step %}

### Disable trial and save

Uncheck the "Is Trial" box, and click "Save".
{% endstep %}
{% endstepper %}

![](/files/687a222cff0d1476d9e757544953ab8a16b8d1c6)

### Issuing New Trial Licenses

There are a few ways that a trial license can be issued:

* Through the vendor platform on [**Issuing New Licenses**](/vendor-platform/licenses/issuing-new-licenses) view
* Through the License API using [**Trial Key**](/license-api/trial-key) endpoint
* Through the Management API using [**Create Order**](/management-api/order/create-order) endpoint


# License Activation Types

We define a license activation as the process of binding a license to a device.

### Ways to Activate a License

In LicenseSpring, there are 3 different activation strategies for licenses:

* Online
* Offline
* Air-gapped

### Which Activation Process to Choose?

#### When to Use the Online Activations

Online activations are pretty straightforward and are usually the least cumbersome for the end user performing the activation. We make available a license API, and maintain a fairly comprehensive set of wrappers. In most cases, we would suggest to use online activations.

The general process for activating licenses online is that the end user would enter their license key (or email/password) into the app and initiate the activation process. This will send a request to LicenseSpring via http, which will include the license, product code, and device identifier (typically generated by the SDK). If successful, the server will return the license entitlements in a JSON, which are then parsed by the SDK (if the app is using it), allowing the software to be configured according to the license agreement.

{% hint style="info" %}
Even if a license is activated online, you can still perform offline license checks. The SDKs can be configured to update a local license file upon every successful license check.
{% endhint %}

#### When to Use the Offline Activations

Activating licenses sometimes cannot be done over the internet. For this situation, we maintain an offline license portal, where the SDK generates a request file. The user uploads that file on our offline portal, which will return a license file. This license file can only be used to activate by the device that specifically generated the request.

The portal can be found at [**offline.licensespring.com**](https://offline.licensespring.com/).

If the client machine activating the software license will not have access to the internet, then you can activate the license using this process.

{% hint style="info" %}
License files can be updated and sent to the end user after activation, without the end user needing to generate a new request file.
{% endhint %}

#### When to Use Air-Gapped Activations

Some customers have an additional requirement that once their applications have been deployed, files cannot leave the network, so they cannot use the offline activation mechanism described above.

The air-gapped license activation process entails that license policy files are deployed at the same time as software application binaries. Through a series of code exchanges generated by the LicenseSpring server and the client machine (initialization code, activation code, confirmation codes), one of the license policies can be activated for that device.

This is somewhat of a cumbersome process, so we only recommend using it to deploy software for customers that have the most stringent security requirements and where offline activations are not acceptable.


# Online License Activation

{% hint style="info" %}
Online license activations are done via the license API, and can be initialized through one of the SDKs that we maintain. For more information on license activation via one of the SDKs or via the license API, please review their respective articles in this documentation.
{% endhint %}

### General Process

{% stepper %}
{% step %}

### Issue and dispatch the license

A license must first be issued, and dispatched to the end user.
{% endstep %}

{% step %}

### End-user provides authorization

The end-user will enter the license authorization mechanism (usually a license key or an email/password, but we also support activating licenses using single sign on) into their software application.
{% endstep %}

{% step %}

### Software communicates with LicenseSpring

The software will attempt to communicate with the LicenseSpring server. If this license is authorized and valid, the server will return the License information.
{% endstep %}
{% endstepper %}

### Required Fields to Make a Valid Activation Request

Generally speaking, the client application will need to send at a minimum the following information to the LicenseSpring server in order to activate a license:

* `hardware_id`: unique device, session, or environment identifier, usually computed by the SDK.
* `product_code`: This is a field that is set when initializing the product.
* license authorization: Either a license key, or user credentials, depending on the authorization set on the product.


# Offline License Activation

Licenses can be activated and deactivated without the client device having access to the internet. Additionally, device variables and consumptions will be synced with the cloud platform at the moment of deactivating licenses offline. Offline activation supports both key-based and user-based licenses.

{% embed url="<https://youtu.be/mN4KiwQbNrI>" %}

{% hint style="info" %}
Subscription licenses cannot be managed using the offline portal (since subscription licenses rely on an external system for determining the validity of the subscription), but time-limited licenses can be refreshed.
{% endhint %}

{% hint style="info" %}
The SDKs can be configured to run offline license checks. Please see their documentation for more information.
{% endhint %}

### Activating a License Offline

In order to implement offline licensing in your application, follow these steps:

{% stepper %}
{% step %}

### Issue the license

Issue a license for the end user. The end user will enter the license in the application (either a key, or user credentials), and the SDK needs to be set to activate offline. For more information on how to activate offline, check the extended options in your respective SDK.
{% endstep %}

{% step %}

### Enter the license into the app

The end user enters the license file into the app, which will unlock the application and complete the binding of the license to their machine.
{% endstep %}
{% endstepper %}

### Retrieving the License File From the Vendor Platform

Regardless of how a license has been activated, you can retrieve the license file from the vendor platform. Entitlements for a license can be modified and the license file will be updated. You can then retrieve the license file and send it to the customer.

Follow these steps:

{% stepper %}
{% step %}
Go to vendor platform and find the license.
{% endstep %}

{% step %}
On the devices tab find the device where the offline license is located.
{% endstep %}

{% step %}
Click on the `download license refresh file`.
{% endstep %}

{% step %}
Save the `license_refresh.lic` and send it to the user.
{% endstep %}

{% step %}
The end user enters the file into the app, which will refresh the local license file with new entitlements and changes.
{% endstep %}
{% endstepper %}

![Downlading License Refresh File](/files/974ecc8d09297a996e54a9bd5b5595991364a525)

### Deactivating a License Offline

The end user can uncouple the computer from the license, which generates a deactivation file. If they then upload this file to the vendor portal, the license will be released and can be used on another machine.

### Updating Consumption Values Offline

To update the values of total consumptions and total feature consumptions, run the procedure of deactivating the license offline. The vendor can always send a license refresh file to the user with new entitlements periodically.

{% hint style="info" %}
If you are working with a license that is of type consumption, or with consumption features, then the deactivation file will contain the cached consumptions which will be added to the usage information on the license on our server.
{% endhint %}

### White Labeling the Offline Portal

Contact us if you would like to white label the offline portal on your own subdomain.

### Allow offline activation

By default every license can be activated using online, offline, or airgap activation processes.

If you want to limit your licenses to be activated only online, you can toggle the Allow offline activation checkbox on a license. When set to `False`, the License API will not allow the license to be activated offline or airgap (airgap being a variation of offline licensing).

![Allow offline activation on a license](/files/2dfbbe907183dfe96f146940a5e8b1324d53da33)

The same can also be set on a specific license policy, so when a license is created using that policy code the offline activation setting will be applied accordingly.

![](/files/3963eee7da31d4f360555eb0d2b27a71fae51cc0)


# Air Gapped License Activation

Our concept for air gap license activations is similar to the offline activation process, with the added constraint that files cannot leave the private network after the licensable software has been installed/deployed.

{% hint style="info" %}
Only key-based licenses can be activated as air-gapped licenses for now.
{% endhint %}

### General Workflow for Issuing & Activating Air-Gapped Licenses

{% stepper %}
{% step %}
Add the air-gapped API key to the client SDK
{% endstep %}

{% step %}
Create an air-gapped license (either from a license policy or overriding an existing one)
{% endstep %}

{% step %}
Generate an initialization code with the license key from the air-gapped portal
{% endstep %}

{% step %}
Enter the license key and initialization code in the client application
{% endstep %}

{% step %}
Generate an activation code using the SDK
{% endstep %}

{% step %}
Generate a confirmation code from the air-gapped portal
{% endstep %}

{% step %}
Complete the activation process by entering the confirmation code in the SDK.
{% endstep %}
{% endstepper %}

The air-gapped activation portal can be found here: [**airgap.licensespring.com**](https://airgap.licensespring.com/)

{% hint style="info" %}
[**Contact us**](https://licensespring.zendesk.com/hc/en-us/requests/new) to white label the air-gapped portal for your own subdomain!
{% endhint %}

### Creating Air-Gapped Private/Public Keys

The key pairs can be regenerated, however the new public key would need to be re-entered into the SDK. The air gapped public key should be obfuscated inside of the SDK.

The key algorithm used for air gap company account keys and air gap license keys is [**EcDSA**](https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm). You can find the air-gapped private and public keys under "account settings" > "keys".

{% hint style="warning" %}
This step is **essential** for **Air-Gapped** licenses
{% endhint %}

![generating new key pair](/files/04b9cccd56a1c68e1a75fc71a0c0bf57672adbab)

![Generated air-gapped keys](/files/07714406917b964c1f5800ad3070322f47f6b49f)

### Create a License Policy for Air-Gapped Licenses

On the product detail page, go to the `License policies` tab and click on the "add license policy" button.

To mark policy as air-gapped switch the "set as air-gapped" toggle.

{% hint style="info" %}
Air gap license policies cannot be edited or deleted after they are created. The same is true for any air gapped license that has been issued.
{% endhint %}

![Setting License Policy as Air-Gapped](/files/ed55cc0b42819fd8cf8e905b9b8cdba98a0cb12f)

### Issuing Air-Gapped License Keys Through the Vendor Platform

Start the process as you would with any license: Click on the licenses tab in the left navigation > "Issue Licenses". When adding a product to the order, toggle "Set as air-gapped". All of the policies selectable from the drop-down will only be the policies defined as air-gapped:

![](/files/7197d3e028fc736262714fa06f98fd18266f4bf2)

The air gapped license is now created

![](/files/905b5c26d8b2d6cf08ba89e686e6050cd0ede34d)

### Activating an Air-Gapped License

The air gap portal for license activation can be found at: [**airgap.licensespring.com**](https://airgap.licensespring.com/)

The process of activation is done in 3 steps including actions on server (portal) and SDK.

{% stepper %}
{% step %}

### Generate an Initialization Code From the Air-Gap Portal

Navigate to the air-gap portal, and click "Initialize air-gapped activation", enter the license key:

![Initializing Air-Gap Activation](/files/59e26ac501c828ca0f9ffeee933cdfdb90a82df4)

The portal will return an initialization code which, along with the license key needs to be entered in the SDK.

There is a QR code available to make the process of copying this information easier:

![Initialization Code](/files/0bde58a043cb5df58a5971e1dbe46fd3417694f0)

```json
{
	license_key: "GQXR-94AC-QKAK-B4GP",
  	initialization_code: "+48Wk6FQIwAmFctO6Ru8YHIX849wCKilMBH149sn9yo="
}
```

{% endstep %}

{% step %}

### Generate Activation Code From the SDK

This `initialization_code` must be imported to the SDK alongside with the license key. The SDK will verify the `initialization_code` and if successful will return the `activation_code`, along with the `hardware_id` values to the user so that user can take those strings back to the server.
{% endstep %}

{% step %}

### Generate Confirmation Code From the Air-Gap Portal

On the air-gap portal, click "Enter Air-Gapped Activation Code"

Enter the license key, `hardware_id`, and `activation_code`

![Entering Air-Gap Activation Code](/files/02f9073b0eb851b8cadd21c1b19d7fbe3dce880d)

![Pop-Up To Enter Activation Code](/files/cf6c5da0ae6d5424237aa99603228abd17466d78)

The portal will return a policy ID and a confirmation code, as well as a QR code to make it easy to copy/paste this information.

{% hint style="info" %}
At this point, LicenseSpring will consider this license has been fully activated, even if the confirmation code and policy ID were not entered into the client application
{% endhint %}

{% hint style="danger" %}
**Important:** If incorrect data (e.g., an invalid policyFilePath) is entered into the client application during the confirmation step, the LicenseSpring SDK will throw an error, and no local license file will be created on the device. However, the license will still appear as "active" in the LicenseSpring portal because the server cannot verify the device's state in an air-gapped environment.

To prevent losing a license due to such errors, we recommend:

* Saving the **QR code** or JSON data provided during the confirmation step. This allows users to re-enter the correct data in the client application if the first attempt fails.
* Double-checking all inputs (license key, policy file path, activation code) before proceeding with the activation process in the SDK.
  {% endhint %}

![Final Pop-Up Confirming Activation](/files/7382be4956d06ae663d13f4b6915e09fcaddc7b3)

Enter this information into the client application, and the activation process should be complete!
{% endstep %}
{% endstepper %}

{% hint style="info" %}
Airgap deactivation follows the same flow, you just need to toggle the initial view to Deactivation.
{% endhint %}

![Airgap Deactivation](/files/f0f7f22016d1273cec73c9f3a820bad74805f452)

### Frequently Asked Questions

<details>

<summary>What happens if the activation process fails on the end device?</summary>

If an air-gapped license activation fails on the device (e.g., due to an incorrect policyFilePath or confirmation code), the SDK will throw an error, and no local license file will be created. However, the license will still appear as "active" in the LicenseSpring portal. This behavior is due to the offline nature of the device and the inability of the server to validate the device's state. Ensure all inputs are correct before submitting the activation request.

</details>

<details>

<summary>Can I reset or reuse a license that failed to activate locally?</summary>

Currently, licenses marked as "active" in the LicenseSpring portal cannot be reactivated unless they are deactivated first. For this reason, it is critical to safeguard the JSON data or QR code provided during the activation process so you can re-enter it into the client application if an error occurs.

</details>


# Hardware Key Licensing

In the LicenseSpring platform, users can generate and customize licenses to require a hardware key. LicenseSpring supports using [**YubiKey**](https://www.yubico.com/) hardware keys.

{% hint style="warning" %}
Hardware Key Licensing is an add-on functionality in Licensespring. Contact sales to enable it on your account.
{% endhint %}

These licenses can only be activated and used only when a hardware key is plugged in. To start using hardware key licensing you need to:

{% stepper %}
{% step %}

### Create hardware key required licenses

* Issue licenses from LicenseSpring with the desired entitlements (max concurrent users, features, validity period, etc.)
* Ensure `Require Hardware Key` is ticked
  {% endstep %}

{% step %}

### Provision hardware keys so that they can be used with LicenseSpring service

{% endstep %}
{% endstepper %}

### Create hardware key required licenses

#### Issue License from LicenseSpring

* Configure license entitlements (max concurrent users, features, validity period etc.)
* Ensure `Require Hardware Key` is Ticked

![Configure license entitlements](/files/53cd50b6eb5e8a5488f44349dbb85ec36ed03502)

Click Next and Create Order.

![Create order](/files/4252c6c4f9f3ccd9490557abc595715bf01a95cb)

### Provision hardware keys

#### Desktop application installer

Download and install the **LicenseSpring Provisioning Application for Desktop** from [**here**](https://ls-provisioner.s3.eu-west-1.amazonaws.com/index.html).

{% hint style="warning" %}
We currently support x64 Windows, but we will expand the support for the rest of the OS environments.
{% endhint %}

This application will handle all the provisioning logic. It will create certificate and private and public keys for Yubikey dongle. Private key will be saved in the Yubikey, and public key will be kept in LicenseSpring and used for signature verification.

Install the [**MSVC redistributable**](https://aka.ms/vs/17/release/vc_redist.x64.exe) if it is not available already.

#### Key certificate provisioning

Visit the `Hardware Keys` page in LicenseSpring vendor platform ( Licenses -> Hardware Keys ). Here you can see the list of all provisioned hardware keys for your account and informations like expiration date of the certificate, is the certificate revoked or active and if it is already used to activate a license. If it is, button `View license` will be displayed in table row.

![Hardware Keys](/files/b5515f0bd56b55eaee0c3ff1e171306020444ea5)

To provision a new key certificate, click on `Provision the key certificate` button and confirm the action inside the popup. This will redirect you to the desktop application which you installed in previous step. This application will start the provisioning process in the background.

If the process is successful, you should get a message window stating `Succeeded writing cert` and new certificate will be saved in PIV slot 9a. Otherwise, you will get a message window with some kind of error and writing the certificate will fail.

{% hint style="warning" %}
Provisioning will overwrite the RSA2048 keypair in PIV slot 9a.
{% endhint %}

Once this is done, you can safely activate the license using the hardware key. The hardware key must remain plugged in at all times while the software is running, otherwise the license will not function.


# Offline Request Schema

### Overview

As part of our ongoing efforts to strengthen security, we have introduced **Schema Version 2 (v2)** for offline requests. This update enhances request integrity by ensuring the entire payload is signed and verifiable.

### Schema Version 2 (v2) – Changes

Schema v2 introduces full payload signing and request versioning.

#### Key Changes

* **Schema Version Field**
  * Every request now includes a schema\_version field.
  * Allows License API to detect and handle the request format.
* **HMAC-SHA256 Signature**
  * The entire request JSON is signed using HMAC-SHA256 with the client secret or shared key.
  * Ensures all fields are protected from tampering.
* **Nested Request Object**
  * The signed request is wrapped in an outer JSON object.
  * The request field must remain a proper JSON object (not a string) to allow correct parsing and signature verification on the License API side.

#### Example (v2)

{% code title="offline-request-v2.json" lineNumbers="true" fullWidth="false" expandable="true" %}

```json
{
  "request": {
    "schema_version": 2,
    "api_key": "apikey",
    "date": "Fri, 29 Aug 2025 07:22:37 GMT",
    "hardware_id": "8b06c6053b7a2040f07c60b62f17f378ccc5030e21676f8a100a97ab36303ad0",
    "ip_local": "192.168.1.226",
    "license_key": "BSK8-CLCY-PB5E-BXFO",
    "mac_address": "5A-8A-90-08-13-84",
    "os_hostname": "Mac",
    "os_ver": "Darwin 24.6.0",
    "product": "fooapp",
    "request": "activation",
    "request_id": "D010714E-72DD-E4CF-BEBB-4105EC1B2A0F",
    "sdk_lang": "cpp",
    "sdk_ver": "7.41.1",
    "signature": "A7Tg3dBCc3OsegkPdPc6I1/sRqHj1uSuBR3ad8uaEnk="
  },
  "signature": "aad1693bf8b394719b695e916e07aee29d4ddf2afb24cc43febd4d43a660b703"
}
```

{% endcode %}

### Benefits of v2

* Full payload integrity: all fields are signed, including previously optional fields.
* Backward-compatible: older SDKs using v1 requests will still work while v2 enables enhanced security.
* Simplifies License API parsing and signature verification.

### Enabling Schema Version 2

{% stepper %}
{% step %}

### Step

Log in to the **Vendor Platform**.
{% endstep %}

{% step %}

### Step

In the left sidebar, go to **Settings** → **Preferences**.
{% endstep %}

{% step %}

### Step

Locate the **Minimum Offline Request Schema Version** section.
{% endstep %}

{% step %}

### Step

Select **V2** from the dropdown list.

By default, this setting is **Version 1**, so you must switch it to **Version 2** to start generating v2 offline requests.
{% endstep %}
{% endstepper %}

![Schema Version Selection](/files/139fc690d7486bde2cab3ba317cff65a10f9b0e8)

{% hint style="info" %}
Offline schema v2 requires an SDK version that supports v2. Check your SDK documentation (and changelogs) under [SDKs](/sdks/introduction) before enabling.
{% endhint %}

![](/files/c215111956e87f79475c2851af4c040aab94126d)


# TPM Licensing

Configure TPM-backed licenses that enroll device keys and sign LicenseSpring requests.

In the LicenseSpring platform, you can generate and customize licenses that require a **TPM** (Trusted Platform Module). A TPM-required license can only be activated and used on a device with a hardware-protected key store. Every subsequent request must be signed by that device's key.

{% hint style="warning" %}
TPM licensing is an add-on feature in LicenseSpring. Contact sales to enable it on your account.
{% endhint %}

Unlike [Hardware Key Licensing](/license-entitlements/license-activation-types/hardware-key-licensing), TPM licensing needs no external dongle and no provisioning step. The key is created inside the hardware the end user already has:

| Platform    | Key store                                                                       |
| ----------- | ------------------------------------------------------------------------------- |
| Windows     | TPM 2.0, via the Platform Crypto Provider (per-user key, no elevation required) |
| Linux       | TPM 2.0, via `/dev/tpmrm0`                                                      |
| macOS / iOS | Secure Enclave                                                                  |

The key is a P-256 (ECDSA) key pair generated inside the security module that **never leaves it**. LicenseSpring only ever receives the public half and uses it to verify every request signature from that device.

To start using TPM licensing, you need to:

{% stepper %}
{% step %}

#### Create TPM-required licenses

* Issue licenses from LicenseSpring with the desired entitlements (max activations, features, validity period, etc.)
* Ensure `Require TPM` is ticked
  {% endstep %}

{% step %}

#### Enable TPM licensing in your application

Turn on TPM licensing in the SDK configuration and ship the hardware ID generator library alongside your application.
{% endstep %}
{% endstepper %}

### Create TPM-required licenses

`Require TPM` can be set in three places in the vendor platform:

* **License Policy** (Products → your product → License Policies) - every license issued from that policy inherits the setting
* **Create Order** - in the `License usage` section of the product configuration step
* **Edit License** - on an existing license

Click Next and Create Order.

{% hint style="info" %}
The setting can be changed on an already-issued license. Turning `Require TPM` on for an activated license means existing devices have no enrolled key. They must reactivate before checking in again.
{% endhint %}

{% hint style="warning" %}
Do not enable `Require TPM` and `Require Hardware Key` on the same license. Both are checked at activation, so the end user needs a TPM **and** a plugged-in hardware key. After activation, only the TPM signature is verified - the hardware key is no longer enforced. Pick one.
{% endhint %}

You can also read, filter and set the flag through the Management API:

* `is_tpm_auth` on License and License Policy (readable, patchable, and filterable with `?is_tpm_auth=true`)
* `tpm_public_key` on Device - the enrolled public key, empty when the device has not enrolled one
* `is_tpm_auth` is included in the license CSV export

### Enable TPM licensing in your application

TPM licensing is currently supported in the **C++ SDK**. It is disabled by default and has to be enabled explicitly in the configuration:

```cpp
auto options = LicenseSpring::ExtendedOptions();
options.enableTpmLicensing( true );

auto config = LicenseSpring::Configuration::Create(
    "your_api_key", "your_shared_key", "your_product_code", "1.0.0",
    options );
```

Requirements:

* **LicenseSpring C++ SDK** - version `8.5.0` or later
* **LicenseSpring Hardware ID Generator** - version `1.6.0` or later, deployed next to your application binary (`hardware_id_generator.dll` / `libhardware_id_generator.so` / `libhardware_id_generator.dylib`). The SDK loads it at runtime to communicate with the TPM. Older versions do not export the key store functions, and the SDK raises a `TpmException` that tells you to upgrade.

{% hint style="warning" %}
On macOS and iOS, persisting a Secure Enclave key requires the **host application process** to be code-signed with a `keychain-access-groups` entitlement backed by an embedded provisioning profile. Applications that are not signed and provisioned this way cannot use TPM licensing on Apple platforms.
{% endhint %}

### How it works

#### Enrollment happens at activation

Activation is the only point at which a TPM key is enrolled. When TPM licensing is enabled, the SDK asks the hardware ID generator for the device's public key (creating the key pair on first use) and sends it in the activation request as `tpm_enrollment.signing_public_key`, together with a `licensespring-tpm-signature` header proving the device holds the matching private key.

* If the license requires TPM and the request contains no `tpm_enrollment`, activation is rejected.
* If the request contains `tpm_enrollment` but the license does not require TPM, activation is rejected.
* The enrolled key **replaces** whatever was stored for that device on every activation. Reactivating on the same device after a TPM reset or OS reinstall simply enrolls the new key.

#### Every request afterwards is signed

Once enrolled, the SDK signs every request to LicenseSpring with the device's TPM key and sends the signature in a `licensespring-tpm-signature` header (`algorithm="ES256"`). The platform verifies it against the key enrolled for that license and device. This covers:

* License and bundle checks
* Consumption and feature consumption
* Floating license and floating feature register / release / borrow
* Device variables (send and get)
* Deactivation

A request that arrives without a valid signature is rejected, so a copied license file alone is not enough to keep a license alive on a different machine.

#### Offline activation

Offline activation is supported. Because an offline request file carries no HTTP headers, the same two values are written into the request file itself:

* `tpm_enrollment.signing_public_key` - the device's public key
* `licensespring-tpm-signature` - the signature over the request's `signature` field

Offline deactivation works the same way. No extra steps are required from the end user — the SDK writes these fields into the `.req` file automatically when TPM licensing is enabled.

{% hint style="warning" %}
**Air-gapped licenses do not support TPM.** Air-gapped activation uses its own confirmation-code exchange in the vendor platform and never carries a TPM enrollment or signature, so a license cannot be both air-gapped and TPM-required. Use regular offline activation, described above, if you need TPM enforcement without a network connection on the end user's machine.
{% endhint %}

#### Bundles

For bundle activations, sublicenses do not have to agree on TPM. Enrollment is applied to licenses that require TPM and skipped for those that do not. A mixed bundle remains activatable.

### Checking enrollment in the platform

* The **Licenses** list has an optional `Is TPM` column showing which licenses require TPM.
* On a TPM-required license, the **Devices** tab shows a `TPM enrolled` column, ticked for every device that has a key enrolled.

### Error reference

| Error code                | Meaning                                                                            |
| ------------------------- | ---------------------------------------------------------------------------------- |
| `tpm_enrollment_required` | The license requires TPM but the activation request contained no `tpm_enrollment`. |
| `tpm_not_enabled`         | The request contained `tpm_enrollment` but the license does not require TPM.       |
| `tpm_signature_required`  | The `licensespring-tpm-signature` header is missing on a TPM-required license.     |
| `tpm_signature_mismatch`  | The signature could not be verified against the enrolled public key.               |
| `tpm_invalid_public_key`  | The enrolled `signing_public_key` is not a valid P-256 public key.                 |

In the C++ SDK, all of these surface as a `TpmException`. A device with no usable TPM or Secure Enclave - or a process not entitled to use it - raises a `TpmException` with error code `eTpmKeyStoreUnavailable` before any request is sent.


# Floating Licenses

Setting up your application to accommodate floating licenses is quite simple. Due to LicenseSpring being a cloud-centric software licensing API, there's no need to set up a license server for license issuance and revocation.

This concise guide will walk you through the process of establishing a basic floating license for your application.

While this method offers a rapid implementation of a floating/concurrent license model, it does come with certain constraints.

### What is a Floating License?

The primary feature of a floating license is its capacity to constrain the quantity of machines concurrently utilizing the same license.

More intricate iterations of a floating license could further confine deployment to a designated network, IP address range, or a predefined roster of machines.

An approach to concurrent licensing, offered by LicenseSpring, involves incorporating a server-side watchdog mechanism. This watchdog periodically assesses the connection status with an application that has secured a license. In cases where the application terminates abruptly, the watchdog ensures the license is released.

LicenseSpring has two types of floating licenses:

* [**Floating Cloud**](/license-entitlements/floating-licenses/cloud-concurrent-licensing) where LicenseSpring is the server that manages the checking in and out of licenses.
* [**Floating License Server**](/license-entitlements/floating-licenses/floating-license-server) where licenses are issued for servers that run on-premise, and the client devices do not require an internet connection to request / release licenses from the server.

### What is License Borrowing?

A highly effective approach for extending the duration of a license beyond the default floating timeout involves license borrowing.

With standard floating licenses, adherence to the floating timeout period is required for license validation or renewal. However, license borrowing introduces a solution wherein a license can be generated to appear as "utilized" on the floating server.

This empowers the designated user to employ the license offline throughout the borrowing period, which can be significantly extended compared to the license's typical floating timeout. This functionality proves particularly advantageous for scenarios involving offline floating licensing.

For instance, consider a scenario where an employee must engage with a project necessitating a floating license while working remotely. Due to security constraints, accessing the license server isn't feasible. By borrowing a license for, let's say, 24 hours, the employee gains the ability to utilize the license without requiring a connection to the server during that entire 24-hour timeframe.

For information on how to setup license borrowing, see [Floating Licensing](/sdks/tutorials/licensing-scenarios/floating-licensing).

### What does it mean to Revoke a License?

Revoking a license refers to the forcible release (de-registration) of a floating license or floating feature from a user by an administrator with permissions to do so. This is a permission that can be granted to License Managers in the [End User Portal](/license-entitlements/portal/end-user-portal), so that better control over license can be maintained by end-users. By revoking a license, you free up a license, allowing another user to register for that product.

If you want to revoke a license and not have the same user/device re-register it, then you are looking to **blacklist** the device. See: [Blacklist Device](/management-api/device/blacklist-device).


# Cloud Concurrent Licensing

With LicenseSpring, application developers can allocate licenses for simultaneous usage, wherein LicenseSpring functions as the floating license server.

![LicenseSpring Floating Cloud Visualization](/files/57cdf8aa8917d9f3df63f637ddc725720e03c2ec)

### Configuring Floating Cloud Licenses

Configuring a cloud-based floating license involves adjusting several parameters:

#### Timeout Period

Within the process of setting up a new product, you'll encounter a parameter named "floating timeout."

This value, measured **in minutes**, dictates how frequently the license application must communicate with the LicenseSpring server to maintain the license's active state.

Should the application fail to establish contact with the server before the designated timeout duration elapses, the license will be automatically freed from that particular application.

If you wish to modify the Floating timeout duration for an already-configured product, follow these steps:

{% stepper %}
{% step %}

### Modify the floating timeout

* Access the product.
* Choose "Edit Product".
* Navigate to "License Configuration".
  {% endstep %}
  {% endstepper %}

{% hint style="info" %}
**Note:** The floating timeout period is defined at the product level, but you can override it per license policy or on the license itself.
{% endhint %}

![Initial product configuration allows you to set the floating timeout.](/files/c276244ab3b1a7c44bf004db4eaad507cbc547c5)

![Set floating timeout on license policy](/files/c36c998a716abfb6074bb64981fb1e5446dd7e5a)

![](/files/2379b2a02125d98c0f351ebe38edf1b8155573c3)

#### Max Simultaneous License Users

During the setup of a new product license policy, if the chosen license type is a floating cloud license, you will specify the quantity of devices that can simultaneously utilize the license.

Additionally, it's possible to designate the number of concurrent users at the time of license issuance, and this allocation can be modified subsequently, even after the license has already been granted.

{% hint style="info" %}
**Note:** Max simultaneous users is **not the same** as max activations. Simultaneous users are the total number of machines that can concurrently use a license, while max activations are the number of machines that are node locked to the license.

A license must have been activated on a device before the device can check it out.

Simultaneous users should therefore be less than or equal to max activations, if max activations are not set to unlimited.
{% endhint %}

![Setting the default max concurrent usage.](/files/83d336c5abdb558f19437503635ef6ad192b3010)

![Changing the default max simultaneous license users value.](/files/a8d25baee5aa67e2f9ce98c318d2bb168a00e27c)

The usage of these licenses is similar to that of normal licenses. You utilize `activate_license`, `check_license` and other endpoints as usual. The SDK handles the other aspects such as checking for concurrent usage.

After a license is activated, it needs to be checked to occupy a floating slot.

#### Release

Floating licenses can be released using: API, Platform and License Manager.

**API**

A call should be made to `/api/v4/floating/release` to gracefully release the license floating slot. This call will avoid waiting for the timeout to release the license.

See [Release Floating License](/license-api/floating/license/release-floating-license) for more information on how to release a floating license via our API.

**Platform**

To release a license inside the platform go to: License --> Devices --> Choose device --> Actions --> Revoke

![](/files/465ffe66d1f6a1b92bd3863442130828b20ca3e7)


# Floating License Server

{% hint style="info" %}
**Note:** Visit [Floating Server Usage](/sdks/tutorials/advanced-usage/floating-server-usage) for SDK integration and usage patterns.
{% endhint %}

In an enterprise setting, configuring all computers with a particular software license can often be inconvenient or unfeasible, particularly when the environment encompasses machines lacking internet connectivity.

To address this challenge, we present floating licensing tailored for private networks. This system hinges on a distinct floating license server that can be readily established.

Licenses are dispensed per server, and end-user computers exclusively interact with these floating servers.

### Advantages of Opting for a Floating Server Setup

* **Centralized License Management**: Floating servers hold the licenses instead of individual machines, simplifying upkeep and management.
* **Redundancy and Scalability**: Floating servers are redundant and can be configured in any quantity on the network. They simply need the capability to connect with each other.
* **Offline Capability**: In the event of a loss of internet access within the client facility network, the floating licensing functionality remains unaffected.
* **Localized Performance and Reliability**: Floating servers are situated locally to end-user computers, ensuring minimal latency and enhanced reliability.

### Additional Considerations and Prerequisites

* Multiple redundant floating servers can operate within the private network, necessitating their ability to intercommunicate.
* Floating server(s) necessitate initial internet access (for license activation) and periodic connectivity (to synchronize with the central server).

![LicenseSpring Floating Server Visualization](/files/e1cfdd1f4fd51dceda823cbefad58bcf5836af20)

The operational sequence for utilizing such a license unfolds as follows:

{% stepper %}
{% step %}

### Purchase and Specifications

The customer acquires a time-limited (for instance, one-year) floating license encompassing 3 activations and catering to 5000 floating users. This configuration implies that the license can be concurrently employed on 3 distinct floating servers, facilitating a cumulative 5000 simultaneous users.
{% endstep %}

{% step %}

### Floating Server Setup

The customer establishes the floating server on up to 3 servers and embeds the license key within the server's configuration file.
{% endstep %}

{% step %}

### End-User Application Configuration

On all end-user computers, the application necessitates configuration with just 2 parameters: the application's short-code and one or more IPs corresponding to the floating servers. There is no requirement for individual licenses. Accomplishing this is commonly facilitated through provisioning or Mobile Device Management (MDM) software.
{% endstep %}

{% step %}

### SDK Integration

The application incorporates 2 SDK methods: FloatingActivateLicense (during application startup) and FloatingDeactivateLicense (upon application closure). These functions mirror standard Activate and Deactivate methods but diverge in their approach. Instead of connecting to the central server, they engage in license checkouts from the floating server.
{% endstep %}

{% step %}

### Automatic Ping Mechanism

The SDK features an automated and periodic pinging system to one of the floating servers. This signaling informs the server that the current instance persists and actively utilizes the provisioned license. Should the SDK fail to ping the server within a designated interval, a solitary floating license is released and can be claimed by another computer.
{% endstep %}
{% endstepper %}

![Floating Server Workflow](/files/8250c1bcc36c4dcb8e9413e3908e365a954c0efc)


# Comparison of Floating Cloud and Floating Server Licenses

### Activation & Registration

**Floating Cloud:** Activation is managed via LicenseSpring’s cloud platform, meaning the client communicates with the cloud for license issuance and revocation. This requires internet access. Unlike a floating server setup, a floating cloud license demands individual activation on every device aiming to utilize the license since the license is bound to the client device. In-depth guide: [Floating Licensing](/sdks/tutorials/licensing-scenarios/floating-licensing)

{% hint style="info" %}
With a floating cloud license, you must first activate any client that will be allowed to register a license.
{% endhint %}

**Floating Server:** License activation happens locally, on-premise, using a self-hosted floating server. Internet connection is not required for clients to check in/out licenses since all interactions between the floating server and client application happen over the local network. The license is bound to the floating server for floating server licenses. Licenses can be activated online, offline, air-gapped or by using a hardware key on the floating server. See: [License Options](/floating-server/floating-server-v1/license-options).

{% hint style="info" %}
With a floating server, you first activate the floating server, then any clients on the private network may register to it.
{% endhint %}

### Online/Offline Access

* **Floating Cloud:** Requires constant internet access to check out licenses and perform license checks.
* **Floating Server:** Clients can operate completely offline as long as they can communicate with the on-premise server. Internet connection is required only for online license check-outs and syncs. The floating server can handle offline floating features, allowing operations in air-gapped or restricted environments. Offline licensing docs: [Offline License Activation](/license-entitlements/license-activation-types/offline-license-activation)

### Floating Features

* **Floating Cloud:** Provides concurrent licensing features, allowing users to check in/out licenses dynamically, with cloud-based management of license usage.
* **Floating Server:** Supports similar features locally, including offline floating features that allow the licenses to be borrowed or used without an active internet connection. More info: [Floating Features](/license-entitlements/floating-features)

General floating features guide: [Floating Features](/license-entitlements/floating-features)

### License Borrowing

* **Floating Cloud:** Users can borrow licenses for a specified time to work offline. Initial borrowing requires an internet connection. Borrowing API: [Borrow Floating License](/license-api/floating/license/borrow-floating-license)
* **Floating Server:** Offers more flexibility for offline environments; licenses can be borrowed locally without internet as long as the borrowing happens on the server.

### Hardware Keys

* **Floating Cloud:** Not typically used for cloud licenses.
* **Floating Server:** Hardware keys can be used to secure the floating server setup, preventing unauthorized copying or misuse of the server installation. More info: [Hardware Key Licensing](/license-entitlements/license-activation-types/hardware-key-licensing)

### Setting Up the SDK

* **Floating Cloud:** Easier setup as there is no need for local server configuration; the client SDK interacts directly with the cloud.
* **Floating Server:** More involved, requiring setup and configuration of the local server path and network settings. Setup docs: [Floating Server](/floating-server/floating-server)

### Analytics and Reporting

* **Floating Cloud:** Reports can be generated via the LicenseSpring platform, providing real-time insights into license usage and status.
* **Floating Server:** Reporting capabilities are currently limited compared to the cloud:
  * Floating usage and consumption data are stored locally on the server.
  * When an internet connection is available, this data is sent to the cloud and used to generate reporting graphs.
  * If the server is offline, only CSV reports are available (no graphical reports).
  * For metrics such as API calls and activation/deactivation events, users must rely on cloud-based analytics and reporting.

This setup is expected to change in version 2 to better align the floating server's reporting capabilities with the cloud. General analytics docs: [Analytics](/vendor-platform/analytics)

### User Portal and License Manager Permissions

* **Floating Cloud:** Permissions for users and administrators can be managed via LicenseSpring’s web portal, providing easy access to manage licenses remotely.
* **Floating Server:** User management is handled locally, and roles/permissions can be customized similarly. Some advanced cloud-based features (like real-time updates) might be limited.

End-user portal docs: [End User Portal](/license-entitlements/portal/end-user-portal)

### Consumption Syncing / Caching Mechanisms

* **Floating Cloud:** Data on license consumption is locally cached; it is necessary to sync consumptions (as with non-floating licenses) to accurately reflect consumption data on the LicenseSpring platform.
* **Floating Server:** Consumption data is tracked locally, with periodic syncs to LicenseSpring’s platform (every hour) when a connection is available. Syncing does not occur when running in offline mode.


# Features

{% hint style="info" %}
Please visit our article on [**Product Features**](/product-configuration/product-features) for more information.
{% endhint %}

Features allow you to turn on and off individual modules or components of your software product, as well as to meter their usage.

![Editing features on an existing license](/files/0769bcd2549956ced7de6cd0c53d99e8fda119ff)

### Examples of Features Licensing

* Giving access to additional functionality within your software
* metering API usage for different types of requests
* Setting the number of reports that can be pulled from a database

### Types of Features Available on LicenseSpring

We offer two type of license features:

* **Activation Features** are added or removed from a license, and can have an expiry date to them, but have no further functionality.
* **Consumption Features** can be added to or removed from a license and also can have an expiry date set. However, they have fields to keep track of additional values:
  * `Total consumptions`: times this feature was used
  * `Max consumptions`: the number of times the feature is allowed to be used
  * `Max overages`: the number of times the feature is allowed to be used beyond the value set for `Max consumptions`

{% hint style="info" %}
The upper limit for all consumption-related fields (`Total Consumptions`, `Max Consumptions`, `Max Overages`) is `Number.MAX_SAFE_INTEGER`. For the exact value, see the [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MAX_SAFE_INTEGER#value).
{% endhint %}

### Adding/Removing Features to a License

You can select which features to add to a license at the moment of issuing them, or by editing an existing license. To change which features should be available for an existing license, navigate to the license details page, and click on "product features".

![](/files/4d1f5ce261be1438da3203f48ddad1ed258bedeb)

{% stepper %}
{% step %}

### Adding a Feature to a License

{% hint style="info" %}
Make sure you first defined the product feature. Otherwise you won't have anything to add to the license.
{% endhint %}

* Click "Add product feature", then select from the "remaining product features" dropdown which features to add to the license.
* If the feature is a consumption feature, you can set values for `Max consumption`, `Max overages`, and `Consumption period` that differ from the default.
* You can also add an expiry date when adding the feature to the license.

![Adding New Product Feature](/files/aba2f07e6a2898e0c507f0150a044c4977a4e2c1)

![Product feature selection UI](/files/273bd30a71ec990f953df903a92bbb53d275d0f8)
{% endstep %}

{% step %}

### Adding an Expiry Date to an Existing Feature

* Click the pencil icon on the right side of the row with the feature to set or remove an expiry date.
* The feature will be automatically removed from the license on the specified date at 00:00 UTC.

![Edit feature expiry](/files/046709c4677bdf66a1c9b40d750473ef1f63308f)

![Expiry date UI](/files/be494cfc5613c9e673a5113fb48c62b3c300a23e)
{% endstep %}

{% step %}

### Removing a Feature From a License

* Click the red trash icon on the right side of the screen to remove a feature from a license.

![Remove feature](/files/47f7a81eec374e850c2c3bf814afed9076d4ed5c)
{% endstep %}
{% endstepper %}


# Floating Features

### Floating Product Feature Setup

Before creating a floating product feature, set up the product first: [Creating & Configuring Products](/getting-started/creating-and-configuring-products).

To create floating feature go to **Product** -> **Features** -> **Add product feature** \[button]

![Create floating feature](/files/2aef67ad2653a96f3570bcb47b33fe87be8a1741)

* Check `Is floating cloud` checkbox
* Enter default value for `Max simultaneous feature users` (in the data model this is the `floating_users` field which determines how many users can use the feature simultaneously)
* Enter default value for `Floating timeout` (after how many minutes of inactivity the license feature device will be released)

{% hint style="info" %}
`Max simultaneous feature users` and `Floating timeout` fields are default values for Product features and they can be overwritten when creating License product feature.
{% endhint %}

### Create License Product Feature

License product feature is created when issuing a license and attaching a feature to it.

This can be done programmatically when creating an order or editing license features using the Management API. See: [Create Product Feature](/management-api/product/features/create-product-feature). Floating features will have an orange colored label `floating`.

Here you can also overwrite default values for `Max simultaneous license users` and `Floating timeout`.

![](/files/dbca3cbd0691ec53dd7500ed9f54412a93cac478)

### License Features View

To see the License product features details go to the **License** -> **Product features** \[tab].

![](/files/84d673aa32cd2b19c4691e2291a3fb663f598d8d)

If a license feature is a floating feature, under the **Floating slots** column you should see information about how many floating slots are available and how many are used in the form `used_slots/max_slots` (e.g. `1/5` = one used slot of five available). Additional floating feature information can be observed when expanding the row by clicking on the `Details` icon on that row. Fields displayed there will be:

* `Is floating cloud`
* `Max simultaneous users`
* `Floating timeout`

The **Devices** column will display all the devices which were used to check the specific feature using the License API endpoint `/api/v4/check_license_feature`. See: [License Feature Check](/license-api/license-feature-check).

{% hint style="info" %}
Any license device can check any license feature; it does not have to be a floating feature. This allows displaying a clear connection between license devices and the features they use. It is not required for non-floating features.
{% endhint %}

Clicking on a Devices cell for some feature will open the **License feature devices pop-up** with a list of all feature devices.

![License feature devices popup](/files/b3695ef0c3f765d004288d2857c4cf54a2fdb56d)

Floating feature devices will have additional columns:

* `In use` - Boolean representing if this device is using a floating feature slot
* `Revoke` - Action button which opens a confirmation window. On confirm that device will be released and the floating slot will be opened for another device. Only devices which are in use can be revoked using the UI.

![Confirmation popup](/files/59a43880a59dccc3e3b553c195e6df9e0bf76558)

{% hint style="warning" %}
Borrowing for license floating features is not implemented at the moment.
{% endhint %}


# Custom Fields

{% embed url="<https://www.youtube.com/embed/qvsAqdnxvmo>" %}

A custom field is a `key:value` pair that is defined for a given product, which you can override for any license you issue or edit.

### Set a License Custom Field for a License Policy

License policies are templates you can use when issuing a license, and you can specify a license custom field for any license policy. Any license issued with the policy will use the license custom field.

Open the license policy editor by navigating to the license detail page > license policies, and either "add a new license policy" or click the pencil of an existing license policy.

![](/files/c80278c5e82aabf2e5116a827ba2580b2fac6ce8)

In the license policy editor, click on "custom fields" and "select the custom fields" from the dropdown. Tick the box of any product custom field you wish to override the values for. Their values (not their names) for those selected fields can then be edited in the table. Click confirm when you are done.

{% hint style="info" %}
A notice is now displayed above the custom fields: *Setting a value here will override the default value set in the Product custom fields configuration.*
{% endhint %}

![](/files/ec830bdf44028786c5aa2a8376f9b3cf8c678d59)

![Selecting Custom Fields](/files/42c2176255cbf58c039b2d105350d942d9fec6c3)

![Editing Values for Policy](/files/68dbc3139b69820e1cd679d93d8fcf6d2a62f1d3)

### Edit a License Custom Field for an Existing License

Open the license detail page, and click on custom fields. You will see a table with all of the custom fields for that product, regardless whether or not they have been overridden. Click the pencil on the row for the value of the license custom field you would like to override. Click "confirm" to save the changes:

![](/files/e4cd442416e5fa39409eda42bc49d38de0d5db61)

Custom fields display labels indicating whether the value comes from the **Product Default** or is a **License-specific custom value**. Changing a field updates its type automatically if it was previously a product default.

![](/files/8ac07e045807a459aedb58f02a7d727fc243a05e)

#### Resetting License Custom Field Values

{% stepper %}
{% step %}

### Individual License Reset

* For any license with **“License custom value”**, a **“Reset to product default”** button is available.
* Clicking it **reverts the value to the product default**, updates the label to **“Product default value”**, and removes the reset button for that field.
* Works consistently across **multiple custom fields**, and **bundle licenses**.

![](/files/b2e0947d02ea95ce261e75e1bb452388f50af12f)

![Editing Custom Field Value](/files/62ffe222f200b6ca71d8fae506f98f0b7a9ed4e4)
{% endstep %}
{% endstepper %}

### During Order Creation

* When using **custom license values** in an order, any edits to custom fields will **override the product default values**. A notification is shown to alert the user about this override.

![](/files/d1a45965b23cb280ee8b4f210853f9edfa43e1ad)

### Product vs License Custom Fields

**Product custom fields** are defined on the product with default values. When a license is created and those default values are overridden, **license-specific custom fields are** generated in our database. These fields are returned with the license data, allowing you to track license-specific overrides.

{% hint style="info" %}
If you add a new **custom field** to a product after licenses have already been created, those existing licenses **will not automatically** receive the new field.
{% endhint %}

To handle this, when fetching licenses via the **Management API**, LicenseSpring includes **product custom fields** in the license response. This enables you to see the differences between the product defaults and license-specific values.

For new product custom fields added after a license was created, you can retrieve them directly using the following endpoint:

```
GET /api/v1/product-custom-fields/?company={{platform_company_id}}&product={product_id}
```

{% hint style="info" %}
If defined, the key and the value of a custom field is always returned on License Checks and License Activations. You may modify the value for any custom field for each license.
{% endhint %}

For more information on defining product custom fields, please read [**Product Custom Fields**](broken://pages/0984a22f8f6f75cce17cf1afc29c994faab6f94c).


# License Start Date

When issuing a license, you can set a start date for it. Adding a start date to a license will prevent license activations until that date. This can be useful to fine-tune usage of a rented or time-limited license, or to make software available only for an academic term, for example.

### Issuing Licenses With a Start Date

Navigate to the product selection screen during the process of issuing licenses. Toggle the "Use custom license values". Under license usage, there is a field called "License Start Date" where you can specify the start date.

![](/files/1ae8dc884ceb83ffad38629e2500e77939826fff)

### Add/Modify/Remove the Start Date of an Existing License

{% hint style="info" %}
If you set a start date on an existing license that had already been activated, it will not affect the license that has already activated it. Instead, it will simply prevent additional license activations until the start date.
{% endhint %}

Navigate to the license detail screen and press "Edit License". From there you can set or delete the "License Start Date".

![](/files/c1eb38965ef47500bcc450cc6c6ad0163777f397)

![](/files/2ae120d573d080279f6d22fe48cb966925c62bb4)


# License Note

License notes are additional information or comments that can be included by license managers in the back-end system for individual licenses.

These notes serve as internal annotations or reminders associated with a specific license and are typically not visible to the end-users or license holders.

License managers have the option to add license notes to provide supplementary details, instructions, or special considerations related to a particular license.

These notes can include important information such as license activation instructions, specific usage guidelines, license expiration dates, customer-related details, or any other relevant notes that assist in the management and administration of the licenses.

![License Note Field](/files/28ce66f48114c668717eeae1740c91f2b2ae08df)

License notes serve as a communication tool within the license management system, allowing license administrators or support personnel to include additional context or instructions for a license. They are primarily intended for internal use and help facilitate effective license management, tracking, and support.

Through license notes, license managers can enhance their ability to manage and maintain licenses effectively, ensuring that important information and instructions are readily available within the back-end system.


# Maintenance Period

Adding a maintenance period for a license gives you an additional timestamp to work with beyond the validity period/expiry date of the license.

For example, if you wish to issue a license that never expires, but you wanted to charge for updates, you would set the license type to `perpetual` and just set a maintenance period expiry date.

### Add a Maintenance Period to a New or Existing License

Go to the license detail page (or "add product page" if you are issuing a new license), and check the box "enable maintenance period".

You can select "maintenance duration" if you would like to set an end date once the license has been activated, or you can specify the end date with a calendar day.

{% hint style="info" %}
If you set a "maintenance duration" on an already activated license, the count-down will begin using the current date. If the license is not yet activated, the maintenance will only start counting once the license has been activated.
{% endhint %}

![Enabling Maintenance Period](/files/ceff4c29b4c090e8bfcfdcd875e588fd3f52f0c1)

{% hint style="info" %}
You can also add a maintenance window on a license policy, but given that the license is not being issued, LicenseSpring would not know when to set an end date, and therefore only a "maintenance duration" can be set.
{% endhint %}

![Enabling Maintenance Period on License Policy](/files/947417879b4bd20112d0b594b3eb5d34c2cef1dc)


# Activations & Device Transfers

Activations denote the process of binding a device to a license. There are a few fields that allow you to control the node-locking properties of your software license: `Max Activations`, `Times activated`, `Transfer count`, and `Allow offline activation`.

![activation diagram](/files/98f5ce1b10cf5ae086b5c9bb4135b260897d4ee3)

## Max Activations

`Max Activations` refers to the maximum number of distinct devices that can be activated against a license.

## Allow Offline Activation

By default every license can be activated using online, offline or airgap activation process.

If you want to limit your licenses to be activated only online, you can toggle the Allow offline activation checkbox on a license.

When set to `False`, the License API will not allow the license to be activated offline or airgap (airgap being a variation of offline licensing).

## Times Activated

When issued, a license always starts with a `Times activated` of `0`. Each time a license is activated (bound to a device), the value for total activations increments by `1`. When a device is deactivated from a license, the `Times activated` field will decrement by `1`. Total activations must always be less than or equal to `Max Activations`.

## Transfer Count

When a license is deactivated on a device and then activated on a different device, the `Transfer count` will increment by `1` , but only when the active device count is already at or above the `max_activations` limit.

{% hint style="info" %}
You can set rules around transferring licenses across devices. For example, you can **prevent device transfers**, or only allow a **license transfer a limited number of times.**
{% endhint %}

![transfer rules](/files/85b3e990d137c0b21948b9391d745c69d8327340)

{% hint style="info" %}
**Prevent Device Transfers**\
When this option is checked, the license becomes permanently bound to the first machine where it is activated. This is also known as a **Node-Locked license**. It means the license cannot be moved or re-activated on any other device, even if it is later deactivated. Checking this box enforces a permanent binding of the license to a single machine.

**Has Device Transfer Limit**\
When this option is enabled, you can define the maximum number of times a license may be transferred between machines. A transfer occurs when the license is deactivated on one device and then reactivated on another. For example, if the limit is set to **3**, the license can be moved up to three times in total. If this option is left unchecked, transfers are unlimited (as long as the license is properly deactivated before reactivation).
{% endhint %}

![Device Transfer Limit](/files/7d2afbb9cb93c995d2fdb5979852968422e0103e)


# Consumption Period in License\_Features

* **Consumption Period**: If this field is set, the value for total consumptions is reset to 0. The consumption period can be `Daily`, `Weekly`, `Monthly` or `Annually`.
  * `Daily` is reset at midnight UTC.
  * `Weekly` is reset on Monday at midnight UTC.
  * `Monthly` is reset on the first day of the month at midnight UTC.
  * `Annually` is reset on the first day of the year at midnight UTC.


# Portal


# Introduction

For parties external to the software vendor, we make available a range of interfaces, including a set of portals.

LicenseSpring's portals can be configured to allow license management by an external third party.

Some common tasks that can be done via the portals include the following:

* Assign/unassign users to a license
* Transfer a license from one device to another
* Look up licenses from an order and dispatch them across the organization
* Issue new licenses for an existing order without the intervention of the software vendor
* Activate or deactivate licenses for computers that do not have access to the internet
* Reset licenses

### Available Portals

LicenseSpring currently maintains 3 portals (not including the Vendor platform):

{% hint style="info" %}
All of our portals, including the API, can be white-labeled to a vendor's subdomain. For more details, please contact us directly.
{% endhint %}

#### User Portal

URL: [**users.licensespring.com**](https://users.licensespring.com/)

License managers can log in to this portal and manage licenses for all of the orders they have been assigned to, according to the permissions they have been given.

#### Offline Activation Portal

URL: [**offline.licensespring.com**](https://offline.licensespring.com/)

End users can activate and deactivate their licenses using the offline activation method via this portal.

#### Air-Gapped Activation Portal

URL: [**airgap.licensespring.com**](https://airgap.licensespring.com/)

End users can activate and deactivate their licenses that are air-gapped using this portal.


# End User Portal

{% embed url="<https://www.youtube.com/watch?v=Q-rP0zq-9WQ>" %}

{% hint style="info" %}
License users and/or license managers cannot update the customer details.

Customer details can be updated only via the Management API. See: [Update Customer](/management-api/customer/update-customer).
{% endhint %}

### The intermediary user portal

The intermediary user portal is accessible to those situated between the software vendor and the end user, enabling them to be identified and carry out their designated roles.

### Languages Available in the User Portal

At the time of this writing, the user portal has been translated into English, Spanish, French and German. It can be translated into other languages upon request.

### Actions That Can Be Performed Through the User Portal

By default, it is possible to perform the following actions through the user portal:

* Assign/un-assign users to a user-based license
* Issue licenses and create orders (used like a reseller/distributor portal)
* Reset licenses and devices
* Reset end-users' credentials

It is also possible to customize the permissions of users to remove any of the above and add any of the following:

* Borrow licenses
* Create bundle orders
* Create orders
* Issue licenses
* Revoke devices

It is also possible to configure this portal as a [**Reseller or a Distributor Portal**](/license-entitlements/portal/end-user-portal/reseller-_-distributor-portals).

![User Portal View once Logged In](/files/f76630eee466f1d7e382a7e26b4fab119eb5c696)

### Key Concepts for the User Portal

#### Our Data Model

Before issuing a license, it might help to know how our data model is organized:

![Vendor Platform Data Model](/files/4c3e23c72a0cba9363e8622c7109c8f9903c1fc4)

* A **license** can belong to an order. You can have 0 to many licenses for an order.
* An **order** can belong to a customer. You can have 0-to-many orders for a customer.
* A **customer** can belong to an account. You can have 0-to-many customers for an account.

{% hint style="info" %}
A license will belong to an order. If you do not specify an orderID, a random orderID will be generated
{% endhint %}

If you do not specify an orderID, a random ID will be generated for you. Orders do not need to belong to customers. However, it can be useful to have this association for analytics, or for customer support, for example.

{% hint style="info" %}
A license manager is a user that is assigned to an order who can then log in to the user portal. Depending on their permissions, they can perform various edits to the licenses within the order.
{% endhint %}

### Logging in to the User Portal

License managers log into the user portal. They need to enter their email / password and the Company ID in order to log in.

{% hint style="info" %}
The user portal's URL can be customized to a vendor's subdomain, where then there is no need to enter company ID. Contact us for more information.
{% endhint %}

![User Portal Login](/files/297eec482bab95feb02d28274786be0c6adc7230)

{% hint style="info" %}
The company code can be found in the vendor platform under settings. The company code is also sent via email notification when a license manager is assigned to a license, provided an email notification was set up and configured for this purpose.
{% endhint %}

![Company Code Location](/files/8c2771033eb953c2a7feb1b0a75141f307e730b6)

### Customizing the User Portal

In the Vendor Platform, you can customize some parts of the user portal, such as:

* The color scheme and the logo
* Allowing end users to reset licenses in the user portal
* Displaying product download links

To customize the portal, log in to the Vendor Platform, and click on Settings > Portal configuration.

![Portal Configuration](/files/89dc92bc0f6452a9fc8f31f3a194a70c5e4e89ae)


# Reseller \_ Distributor Portals

Explore our reseller and distributor end-user portal at LicenseSpring: Enabling effortless license management and distribution.

{% hint style="info" %}
Distribution portal is available to Enterprise pricing plan only, and it can be activated on demand only.

[**Contact us**](https://licensespring.zendesk.com/hc/en-us/requests/new) for more information.
{% endhint %}

## Defining a Distributor

To give permissions to a distributor, you assign them as a license manager, and allow them to create their own orders, and add license managers to that order, who will act as resellers.

![Adjusting To Assign Distributor Permissions](/files/34cb26d8b5d7ada443739b524dbbf20f44c6a7af)

When they log in to the user portal, there will be a few changes:

* A new tab in the left will allow them to create a new order.
* On an order detail a new "License Managers" tab will appear, where they can use it to add new license managers to an order (such as a reseller).

![Create Orders through the Distributor Portal](/files/16f74f65bdb940b9f8ee181ca871d2ba913f5b42)

## Defining a Reseller

Resellers are license managers who are allowed to issue new licenses within orders they have been assigned to, but are not allowed to create new orders.

![Adding License Manager](/files/4c8b729ad9f76a447e5480b6efb5bc71c197a9b4)

You can then set which products and which license policies for those products the reseller is allowed to issue licenses for.

![Select Products and License Policies](/files/aa74cbe32aaae2402b64859072966434097d8c9d)

![Add Products and Select Enabled License Policies](/files/2d43c98a2ee7776de6304cc27a68399e701c1a6e)

When the reseller logs in to the user portal, they will be able to issue new licenses for orders they have been assigned to:

![Issuing New Licenses](/files/c8d54d8566440a61986e0e51cb4662ee34254a03)


# The License Manager

{% hint style="info" %}
A license manager is a user that is assigned to an order who can then log in to the user portal. Depending on their permissions, they can perform various edits to the licenses within the order.
{% endhint %}

### Create a New License Manager in the Vendor Platform

You can define a new license manager in the vendor platform by clicking on the "orders" tab, and then on license managers. You can then click on "Add License Manager".

![Adding a License Manager](/files/dacdf861d895a1fe3440a07328942f068f269316)

Next, enter the email address of the license manager, as this is the only required field.

![Popup for Adding License Managers](/files/939267976296ef3ce804cc0373ed7ee901dfac65)

Clicking confirm will add the license manager to the LicenseSpring vendor platform.

![License Manager Detail View](/files/372535e0e827b0b8372cc676dafd7a993ece0648)

### Setting/Modifying Permissions for a License Manager

You can set the following permissions for a given license manager:

* `Reset license` — Removes all devices activated on a given license.
* `Reset device` — Deactivates a specific device that has been node locked to a machine.
* `Manage managers` — Allows this license manager to add other license managers to a given order.
* `Manage license user credentials` — Allows a license manager to view and reset the passwords of users assigned to a given license.
* `Manage license users` — Allows the license manager to assign/unassign users to the user-based licenses for the orders they have been assigned to.
* `Issue license` — Allows the license manager to issue licenses within orders they have been assigned to. The permission will set which products and license policies they are allowed to issue licenses with.
* `Create order` — Allows license managers to create new orders that they will be assigned to.
* `Create bundle order` — Allows the license manager to create new bundle orders that they will be assigned to.
* `Borrow License` — Allows the license manager to borrow a license device on a floating license.

Under the `Permissions` tab on the license manager detail view, you can turn on and off these permissions.

![Permissions Tab](/files/b8e8e429f7a2af986c839bf2ef4a3cc1a0cf4d2a)

### Set Default Permissions When a New License Manager Is Created

When creating a new license manager, a default set of permissions will be assigned. You can set the default permissions from the license manager summary page:

![License Manager Default Permissions](/files/00fd52cbcaa28ee8943be677ba4c7a8a994ec0ed)

### Add a License Manager to an Existing Order

You can add a license manager at any time after an order has been created and licenses have been issued. To do so, navigate to the order details screen, click on the `License managers` tab, and `Add license manager`:

![Add License Manager to Order](/files/32af8abb6ee4dcbe7f0c657b4fdca2ff6e3e2b0f)

### License Manager Password Reset

A license manager can reset their password by going to the [**End User Portal**](/license-entitlements/portal/end-user-portal), and entering their company code and email address. They will receive instructions on resetting their password by email.

![Reset Password Button](/files/178f3554266cc5e747344c97e7f254cddbc345ce)

![Password Reset Prompt](/files/83d8c746ec071ace598f90aee3c7553891c6030d)

{% hint style="info" %}
The license manager needs to enter the company ID along with their email address, since they might be assigned as license managers within multiple LicenseSpring accounts. The software vendor can find their Company ID under "Settings" > "Company Code".
{% endhint %}

### License Manager Email Notifications

You can automatically send an email notification to a license manager when they are assigned to a new order and when other events occur.

For more information, please review [**Email Notifications**](/vendor-platform/settings/email-notifications).


# License Manager Guide

LicenseSpring provides an **end-user portal** designed to improve the interaction between software vendors and their customers. This portal allows authorized users, known as **License Managers**, to manage licenses and devices effectively. Whether you are managing licenses for a team or distributing them across different users, this guide will walk you through the essential functions of the LicenseSpring user portal.

### User Roles

The user portal is designed with distinct roles in mind, ensuring that only authorized personnel have access to specific features:

* **License Managers**: Users who manage licenses for a group or organization. They have access to the end-user portal which allows them to perform the actions stated below, depending on the permissions enabled.
* **End-Users**: These are the customers or employees using the software. They do not have access to the portal; they typically have the ability to activate licenses and view license details but not any administrative actions.

### Permissions Overview

Permissions in the LicenseSpring portal can vary depending on your role, but here are some of the key functions available based on your access level:

* **Assign/Unassign users**: Allows License Managers to assign or remove end-users from user-based licenses.
* **Reset Licenses or Devices**: Reissue licenses or free up activations tied to specific hardware.
* **Borrow Licenses**: Let users take a license offline temporarily; configurable by the License Manager.
* **Issue Licenses**: Used by resellers or distributors to generate licenses on behalf of end-users.
* **Revoke Devices**: Remotely revoke access for devices that are no longer authorized.
* **Reset End-User Credentials**: Reset passwords or other login credentials for end-users.
* **Create Bundle Orders**: Create orders containing multiple licenses in one transaction.
* **Create Orders**: Generate new orders directly from the portal.
* **Edit Customer**: Update customer information on existing orders.
* **Edit License**: Modify existing license details and use the "Use Custom Values" toggle when creating orders to adjust entitlements beyond default policy values.
* **Edit License Custom Fields**: Edit custom fields on existing licenses and set custom values during order creation.
* **Edit License Product Features**: Modify product features on existing licenses and configure features during order creation.

#### Default Permissions For End-Users

By default, as an end-user you can perform the following actions through the user portal:

* Add License Managers to an Order
* Assign/Unassign License Users
* Change License User’s Passwords
* Reset Licenses and Devices

#### Optional Permissions to Add

Some Vendors may change the default permissions granted to License Managers by adding or removing certain permissions. Additional permissions include:

* Borrow Licenses
* Create Bundle Orders
* Create Orders
* Revoke Devices
* Issue Licenses

### Using the Portal

The LicenseSpring user portal is designed to be intuitive, allowing License Managers to perform essential actions related to licenses, orders, and users. Below is a detailed guide on how to use the core functionalities of the portal effectively.

#### Logging In

To login, License Managers must enter their email/password combination and company ID in order to log in. If the user portal URL has been customized to a vendor’s subdomain, there will be no need to enter the company ID. The login screen for a non-whitelisted URL is shown below:

![](/files/52903cb4cb40526793c1a1f142cea221ee082ebb)

The company code, email and password will all be provided to you by the software vendor, as it is set through their portal.

#### Look Up a License

Licenses can be looked up on the end-user portal in a variety of ways, including by:

* License user email
* License key
* Order ID
* Product name
* Device hostname

To do so, click `Licenses` on the left-hand side of the portal then use the search bar at the top of the page as shown below:

![](/files/35bcf353dc5fd7d9673baaad54c7de4d2cf2f97c)

{% hint style="info" %}
LicenseSpring supports key-based and user-based licenses. See [Activate a Key Based License](/getting-started/activate-a-key-based-license) and [User Based Licensing](/sdks/tutorials/licensing-scenarios/user-based-licensing).
{% endhint %}

#### Look Up an Order

The workflow for looking up an order is very similar to looking up a license and can be searched only by order ID. First, get to the `Orders` page on the left-hand side then use the search bar, as seen in the following screenshot:

![](/files/a37614f81adde68bf737c82b539951e8ae39bc82)

#### Assign a User

To assign a user to a license, click on the specific license on the `Licenses` page, then go to the `License users` tab on the license details page. From here you can assign the user by clicking the blue `Assign license user` button, shown below:

![](/files/eb9bf1a28cd379d25ef56554a459d25d208db051)

#### Unassign a User

Once a user has been assigned to a license, you can remove them by clicking the button on the far right under `Actions`, then clicking remove:

![](/files/338e4cc89716f4c4f0aedbeb7b1f17c80abd274e)

#### Change a User’s Password

License Managers can change a license user’s password under the same `Actions` button, then by clicking the `Change password` button:

![](/files/92397de6cd5eb2708f460a1347490c26e78f4c87)

#### Customer Accounts

The **Customer Account** page provides a centralized view of all resources associated with a customer. From here, you can manage and review licenses, users, and related activity.

Available sections include **Customers**, **License Managers**, **Orders**, **Licenses**, **User Groups**, **License Users**, and **Devices**, allowing full visibility and management of licenses, users, and activated devices within the account.

<figure><img src="/files/hlS6KkYaXIAjPKmpF6sA" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/cLD3NnTc0wlJFHqBjIqx" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/LyjucDqFkhZFXPJsrZGY" alt=""><figcaption></figcaption></figure>

#### Reset a Device

To reset a device, head to the `Licenses` page then select the license that the device has been activated on. Click the `Devices` tab then the `Actions` three-dot button, then click `Reset` as shown below:

![](/files/f9e53d266360061787b6b316e666d01ad8560e41)

#### Reset a License

Resetting a license includes navigating to the `Licenses` page, then clicking the red `Reset license` button:

![](/files/609513880f74c204c0c2946f2f96ececc34c0085)

<figure><img src="/files/eH9zYRrPbEQO1dlobVpY" alt=""><figcaption></figcaption></figure>

{% hint style="info" %}
Within the User Portal, License Managers have limited editing capabilities on licenses. They are restricted to modifying the following fields only:

* **Reset Validity Period**&#x20;
* **Reset Maintenance Period**
* **Total Consumptions**&#x20;

All other license fields remain read-only for License Managers. This ensures that broader license configuration remains under the control of account administrators.
{% endhint %}

{% hint style="info" %}
Resetting a license refers to adjusting the license values back to their defaults (i.e. resetting consumptions, activations, etc).
{% endhint %}

#### Issue a License

To issue additional licenses, navigate to the `Orders` page on the left-hand side, select an individual order then click on the `Licenses` tab. From here, click `Issue new Licenses` as shown below, then select more licenses to issue within this order.

![](/files/7e110f6aeeec65bb97257e9c8afa5a4b8177776d)

#### Borrow a License

To borrow a license, the license must first be activated on a device. It can be borrowed by going to the `Devices` tab on the `Licenses` page. After clicking on the `Actions` button on the far right you will see the opportunity to `Borrow` the license, as shown below:

![](/files/4cf22956969a38f0d08e79fc104e51401e5b19e1)

#### Create an Order

To create an order, click the `Create order` button on the left-hand side, then provide the order ID, order reference, and customer details on the following page:

![](/files/ba68f1603243abf94c440462daf8478c62c1315d)

On the next page, you will be prompted to select the product(s) to add to the order. You can select an individual product or a product bundle (a package consisting of multiple products).

You will next select the product or product bundle as well as the license policy (how different values for a license are set, such as max activations, max license users, whether it is a trial, etc.). These license policies are created by the software vendor. After confirming the order details, the order will be created.

If the required permissions are in place: Only users who meet all of the following conditions can create bundle orders in the User Portal:

* The user is logged in as a **License Manager**
* The user has permission to **create orders**
* The user has permission to **create bundle orders**
* The user has **product-level permissions for every product included in the bundle**

{% hint style="info" %}
A frequent reason bundle order creation fails is missing product permissions.

Example:

* The product bundle **user-bundle2** contains two products:
  * user-prod
  * user2
* If you only have permission for user-prod but not for user2, you will not be able to create the bundle order.
  {% endhint %}

![](/files/577bd3537f8666d52654b25950a5f312e20e3ef8)

{% stepper %}
{% step %}

### Select a configured product bundle

The License Manager will see the option to select a configured product bundle when creating an order in the User Portal.
{% endstep %}

{% step %}

### Issue the bundle order

The License Manager can issue the bundle order in a single action.
{% endstep %}

{% step %}

### Portal processes bundle details

Once the order is successfully created, the User Portal retrieves and processes the bundle details.
{% endstep %}

{% step %}

### Individual licenses displayed

All individual licenses included in the bundle are displayed correctly, in the same way they appear on the main platform.
{% endstep %}
{% endstepper %}

![](/files/706777d0977d82080270458fa42e9b25b36760f7)

![](/files/40bc13bfa5c67ae6882e901f7a103e6801968e9c)

![](/files/a0e450d70e9b8cc3dc30f3a38e276558660f46e0)

If the License Manager has the **Edit License** permission, a toggle “Use Custom License Values” is available during product selection. This allows License Managers to specify custom values for licenses when assigning products.

![](/files/cdd608a984271b5d99efb5056008098776450900)

When the toggle is enabled, it allows the License Manager to edit and customize the license values for the selected product.

If the user has the **Edit License Product Features** permission and the product includes any features, the Product Features tab becomes visible once the License Manager enables the Use custom license values toggle.

![](/files/0c957e9059dada25ba46503d834eac6155d0d0a1)

{% hint style="info" %}
With the Use custom license values toggle enabled, users can edit the Product Features they want to include in a license. They can only add features that are already available in the product; License Managers do not have access to manage or create global features that are not part of the license policy.
{% endhint %}

If a user has the **Edit License Custom Fields** permission and the product contains custom fields, the Custom Fields tab will become visible once the Use custom license values toggle is enabled.

![](/files/32225ea836f8bc9a7d803dbfc54cad550ce53843)

After saving, the user will be able to view the changes they made as shown below:

![](/files/6147e537bf61d7d089c552989d09abef1e766dad)

Users with the **Edit Customer** permission can edit customer details from the Order page under the Customer tab.

![](/files/c07f84e893c0639ec7841868c353543564f7a2d1)

Users with the **Edit License** permission can edit license details directly from the License page.

![](/files/a777d6db85ceb49da5a7d7ef85782a7247acd0e6)

Users with the **Edit License Product Features** permission can manage product features on the License page under the Product Features tab. This includes adding, editing, and removing product features.

![](/files/59eef4a39216e5f83eeb72b178c79e48571364e2)

Users with the **Edit License Custom Fields** permission can edit license custom fields on the License page under the Custom Fields tab.

![](/files/4ccf3b834b3ad7b6be6f9654dc5dc616fd7a27a5)

#### Revoke a Device

To revoke a device, go to the `Licenses` page on the left-hand side then click on the `Devices` tab to see devices associated with the license. Then click the `Revoke` button as seen in the following screenshot.

![](/files/f6b670cb449ccd321889de9198323333137a3a27)


# Offline Portal

URL: [**offline.licensespring.com**](https://offline.licensespring.com/)

End-users can activate and de-activate their licenses using the offline activation method using this portal.

{% embed url="<https://www.youtube.com/watch?v=mN4KiwQbNrI>" %}

## What is the Offline Portal Used for?

The offline portal is primarily useful for a few tasks, including:

* Node-locking licenses to devices that do not have a direct connection to the internet or that cannot connect to LicenseSpring via the LicenseAPI.
* Completing the deactivation process of offline node-locked licenses.
* Updating consumptions and consumption features for offline devices.

## Activating a License Offline

In order to implement offline licensing in your application, please follow these steps:

{% stepper %}
{% step %}

### Issue a license for the end user

The end user will enter the license in the application (either a key, or user credentials), and the SDK needs to be set to activate offline. For more information on how to activate offline, please check the extended options in your respective SDK.
{% endstep %}

{% step %}

### End user provides license to the app

The end user enters the license file into the app, which will unlock the application and complete the binding of the license to their machine.
{% endstep %}
{% endstepper %}

## Retrieving the License File From the Vendor Platform

Regardless of how a license has been activated, you can retrieve the license file from the vendor platform. This means that entitlements for a license can be modified and the license file will be updated. You can then retrieve the license file and send it to the customer:

{% stepper %}
{% step %}

### Find the license on vendor platform

Go to vendor platform and find the license.
{% endstep %}

{% step %}

### Locate the device

On the devices tab find the device where the offline license is located.
{% endstep %}

{% step %}

### Download the refresh file

Click on the `download license refresh file`.
{% endstep %}

{% step %}

### Save the file

Save the `license_refresh.lic` and send it to the user.
{% endstep %}

{% step %}

### End user refreshes local license

The end user enters the file into the app, which will refresh the local license file with new entitlements and changes.
{% endstep %}
{% endstepper %}

![Downlading License Refresh File](/files/3c211b922f74c44efe98c8851785e6f7a6892a8e)

## Deactivating a License Offline

The end user can uncouple the computer from the license, which generates a new file (a deactivation file). If they then upload this file to the portal above, the license will be released and can be used on another machine.

## Updating Consumption Values Offline

In order to update the values of total consumptions and total features consumption, you have to run the procedure of deactivating the license offline. The Vendor can always send a license refresh file to the user with new entitlements periodically.

{% hint style="info" %}
If you are working with a license that is of type consumption, or with consumption features, then the deactivation file will contain the cached consumptions which will be added to the usage information on the license on our server.
{% endhint %}

## White Labeling the Offline Portal

Contact us if you would like to white label the offline portal on your own subdomain.


# Air Gapped Portal

The air-gapped license portal can be found at:

URL: [**airgap.licensespring.com**](https://airgap.licensespring.com/)

For instructions on how to activate licenses using the air-gapped portal, see: [Air Gapped License Activation](/license-entitlements/license-activation-types/air-gapped-license-activation).


# Introduction

These endpoints are a set of RESTful APIs exposed for client applications and client SDKs. Can be used to activate/deactivate licenses, check licenses, handle metered usage, etc.

APIs are accessed through SSL and signed with API key + shared key combination.

Base URL: **api.licensespring.com**

See the following pages for further steps:

* [**License API Authorization**](/license-api/license-api-authorization)
* [**Floating**](/license-api/floating)
* [**Activate License (Online Method)**](/license-api/license-activation-and-deactivation/activate-license-online-method)

{% hint style="info" %}
To edit license entitlements and orders, use the **Management API**: [Management API](/management-api/introduction)
{% endhint %}

{% hint style="warning" %}
Encode emails which contain special symbols like '+' when sending them as parameters

Example: <test+symbol@example.com> -> test%2Bsymbol%40example.com
{% endhint %}

{% hint style="warning" %}
When using param or body field `product` make sure that you are using the product code, and not the product name.
{% endhint %}


# Public API Metadata

The /.well-known endpoint is a **public metadata endpoint** designed to help clients, SDKs, and integrations automatically discover important configuration information about your API.

By accessing this endpoint, developers and tools can programmatically retrieve:

* The **base URL** for API requests (api\_base\_url)
* The **supported API versions** (supported\_api\_versions) and default version (default\_api\_version)
* The **current API release version** (version)
* The **environment** (e.g., dev, staging, prod)
* A **public key or certificate** for secure communications (pub\_key)
* A **list of available API endpoints** and their HTTP methods (endpoints)

This eliminates the need for manual configuration or hardcoding these values into client applications. This endpoint is publicly accessible and requires no authentication.

{% hint style="info" %}
**Use Case Example:** When a new SDK initializes, it can first call /.well-known to learn the API base URL, supported versions, and endpoints, ensuring that all requests are correctly configured without requiring manual updates in the code.
{% endhint %}

## Endpoint

* Method: GET
* URL: /api/v4/.well-known
* Description: Returns static or generated JSON with environment, version, and endpoint details.

## Example responses

{% code title="Response (200)" %}

```json
{
  "api_base_url": "http://api.licensespring.com/api/v4",
  "environment": "dev",
  "supported_api_versions": ["v4"],
  "default_api_version": "v4",
  "version": "4.1.34",
  "timestamp": "2025-10-28T10:32:21.580Z",
  "pub_key": "-----BEGIN CERTIFICATE-----\nMIIFrjCCA5YCCQDXhLQ....0ExGTAXBgNVBAgT-----END CERTIFICATE-----",
  "endpoints": [
    {"method": "GET", "url": "/api/v4/.well-known"},
    {"method": "GET", "url": "/api/v4/trial_key/"},
    {"method": "POST", "url": "/api/v4/activate_license/"},
    {"method": "POST", "url": "/api/v4/activate_bundle/"},
    {"method": "POST", "url": "/api/v4/activate_offline/"},
    {"method": "POST", "url": "/api/v4/activate_bundle_offline/"},
    {"method": "POST", "url": "/api/v4/deactivate_license/"},
    {"method": "POST", "url": "/api/v4/deactivate_offline/"},
    {"method": "POST", "url": "/api/v4/deactivate_bundle/"},
    {"method": "POST", "url": "/api/v4/deactivate_bundle_offline/"},
    {"method": "GET", "url": "/api/v4/check_license/"},
    {"method": "GET", "url": "/api/v4/check_license_feature/"},
    {"method": "POST", "url": "/api/v4/add_consumption/"},
    {"method": "POST", "url": "/api/v4/add_feature_consumption/"},
    {"method": "GET", "url": "/api/v4/get_device_variables/"},
    {"method": "POST", "url": "/api/v4/track_device_variables/"},
    {"method": "GET", "url": "/api/v4/versions/"},
    {"method": "GET", "url": "/api/v4/installation_file/"},
    {"method": "POST", "url": "/api/v4/floating/borrow/"},
    {"method": "POST", "url": "/api/v4/floating/release/"},
    {"method": "POST", "url": "/api/v4/floating/feature_release/"},
    {"method": "POST", "url": "/api/v4/change_password/"},
    {"method": "GET", "url": "/api/v4/customer_license_users/"},
    {"method": "GET", "url": "/api/v4/sso_url/"},
    {"method": "GET", "url": "/api/v4/products/"},
    {"method": "GET", "url": "/api/v4/product_details/"},
    {"method": "GET", "url": "/api/v4/user_licenses/"},
    {"method": "GET", "url": "/api/v4/device_licenses/"},
    {"method": "GET", "url": "/api/v4/oauth_url/"},
    {"method": "GET", "url": "/api/v4/check_bundle/"},
    {"method": "POST", "url": "/api/v4/webhook/order/"},
    {"method": "GET", "url": "/api/v4/webhook/license/"}
  ]
}
```

{% endcode %}

## Response fields

* api\_base\_url (string, optional) — Base URL for the API.
* supported\_api\_versions (array, optional) — List of API versions supported.
* default\_api\_version (string, optional) — Default API version to use.
* version (string, optional) — Current API release version.
* environment (string, optional) — Current environment (e.g., dev, staging, prod).
* timestamp (string, optional) — UTC timestamp when the response was generated (ISO-8601 UTC).
* pub\_key (string, optional) — Public certificate/key for API communication.
* endpoints (array, optional) — List of all available API endpoints with method and path.

{% hint style="success" %}
Provides public configuration metadata so clients and SDKs can automatically discover API details like base **URL, supported versions, and endpoints.**
{% endhint %}


# License API Authorization

## Overview

License API provides two methods for authorizing client requests:

* [**API Key Authorization**](/license-api/license-api-authorization/api-key-authorization) — API key + shared key per company
* [**OAuth Authorization**](/license-api/license-api-authorization/oauth-authorization) — OAuth 2.0 client credentials per company, customer account, customer, or license


# API Key Authorization

### Overview

To ensure the integrity of server requests, every request must be **signed**. The **signature** serves as a unique representation of the request and is included in the **Authorization** header.

#### Signature Details

The signature is generated using a specific algorithm applied to the request parameters. The resulting value is a canonicalized signing string, ensuring the request's authenticity.

#### Required Headers

Each client request must include the following headers:

**Date**

Current time given as a date string in [**RFC7231**](https://datatracker.ietf.org/doc/html/rfc7231#section-7.1.1.1) format. Note that this value is always in the GMT timezone.

Example:

```none
Date: Tue, 07 Jun 2014 20:51:35 GMT
```

**Authorization**

This header contains information on how the request is authorized:

* **algorithm**: Defines the signature hashing algorithm, by default `hmac-sha256`
* **headers**: Defines the headers used to derive the signature, set this to `date`
* **apikey**: The API key used for the request
* **signature**: The Base64-encoded value of the signing string. See [**Request Signature**](/license-api/license-api-authorization/request-signature).

Example Authorization header:

```none
algorithm="hmac-sha256", headers="date", signature="_base64_here_", apikey="_company_api_key_here_"
```

{% hint style="warning" %}
Ensure that the Date header value is set to current time. The server allows timestamps that are up to 15 minutes old.
{% endhint %}

### Errors

All returned errors have an HTTP status code of 400 or higher, and a response body as follows:

```typescript
{
  status: number,
  code: string,
  message: string
}
```

JSON Schema

```json
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "status": { "type": "number" },
    "code": { "type": "string" },
    "message": { "type": "string" }
  },
  "required": [
    "status",
    "code",
    "message"
  ],
  "additionalProperties": false
}
```

The following list of errors related to Authorization are common for all License API endpoints:

<details>

<summary>date_header_diff (400)</summary>

</details>

<details>

<summary>authorization_invalid_headers (400)</summary>

</details>

<details>

<summary>hmac_required (400)</summary>

</details>

<details>

<summary>signature_mismatch (400)</summary>

</details>

<details>

<summary>authorization_missing_params (400)</summary>

</details>

<details>

<summary>invalid_api_key (400)</summary>

</details>

<details>

<summary>revoked_api_key (400)</summary>

</details>

<details>

<summary>read_only_api_key (400)</summary>

</details>


# OAuth Authorization

### Overview

The OAuth system allows for vendors to set up separate credentials for their Company, Customer Accounts, Customers or individual Licenses. Each entity has a **Client ID** and **Client Secret** (which can be set to rotate automatically). These are used to obtain an **Access Token** from the OAuth endpoint, to be used for access to License API endpoints (as an alternative to [**API Key Authorization**](/license-api/license-api-authorization/api-key-authorization)). Vendors can enforce a company-wide mandatory OAuth rule instead of API keys.

### Obtaining an Access Token

Access Tokens are short-lived secret strings used to authorize a client request on the API. To obtain an access token from the OAuth system, perform the following request:

{% code title="Request OAuth Token (POST)" %}

```http
POST https://auth.licensespring.com/realms/user-portal/protocol/openid-connect/token
Content-Type: application/x-www-form-urlencoded

grant_type=client_credentials
client_id=CLIENT_ID_HERE
client_secret=CLIENT_SECRET_HERE
```

{% endcode %}

Example (curl):

```bash
curl --location 'https://auth.licensespring.com/realms/user-portal/protocol/openid-connect/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'client_id=CLIENT_ID_HERE' \
--data-urlencode 'client_secret=CLIENT_SECRET_HERE' \
--data-urlencode 'grant_type=client_credentials'
```

Successful response (200):

```json
{
  "access_token": "TOKEN_STRING_HERE",
  "expires_in": 480,
  "refresh_expires_in": 0,
  "token_type": "Bearer",
  "not-before-policy": 0,
  "scope": "profile email"
}
```

Unauthorized response (401):

```json
{
  "error": "invalid_client",
  "error_description": "Invalid client or Invalid client credentials"
}
```

Once the access token is retrieved, it is used as an `Authorization: Bearer` value in License API requests. The `expires_in` property in the response states the Access Token's TTL in seconds from the moment the token was generated.

#### Required Headers

Each client request must include the following headers:

* Date

  Current time given as a date string in [RFC7231](https://datatracker.ietf.org/doc/html/rfc7231#section-7.1.1.1) format. Note that this value is always in the GMT timezone.

  Example:

  ```
  Date: Tue, 07 Jun 2014 20:51:35 GMT
  ```
* Authorization

  This header contains the Access Token.

  Example:

  ```
  Authorization: Bearer TOKEN_STRING_HERE
  ```

{% hint style="warning" %}
Ensure that the Date header value is set to current time. The server allows timestamps that are up to 15 minutes old.
{% endhint %}

### Client Access Permissions

Client ID-Secret pairs can be scoped for the following resources:

* **Company**: Provides access to all licenses belonging to the company
* **Customer Account**: Provides access to all licenses associated to a Customer Account
* **Customer**: Provides access to all licenses associated to an individual Customer
* **License**: Provides access to one specific license

Each Client ID string states the type of scope used, e.g.: `auth-company-100123`, `auth-customeraccount-200234`, `auth-customer-300345`, `auth-license-1000456`

### Errors in Endpoints

When calling a License API endpoint using the Access Token authorization, the system will return an error response if the Access Token is not valid. All returned errors have an HTTP status code of 400 or higher, and a response body as follows:

```typescript
{
  status: number,
  code: string,
  message: string
}
```

JSON Schema

```json
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "status": { "type": "number" },
    "code": { "type": "string" },
    "message": { "type": "string" }
  },
  "required": [
    "status",
    "code",
    "message"
  ],
  "additionalProperties": false
}
```

The following errors related to Access Tokens can be returned:

> **oauth\_token\_malformed** (400)

> **oauth\_token\_expired** (400)

In case OAuth is enforced company-wide and a request is being sent using an API key:

> **oauth\_required** (400)


# Request Signature

### Overview

Requests sent to the server have to be digitally signed for purposes of authentication. The signature uses the Date header and signing algorithm used in [**License API Authorization**](/license-api/license-api-authorization).

### Signature Structure

The **signature** in the **Authorization** header is a **Base64-encoded HMAC-hashed** value of the signing string, which is composed of:

{% stepper %}
{% step %}

### Constant string

A constant string: `licenseSpring`
{% endstep %}

{% step %}

### Newline

A newline character `\n`
{% endstep %}

{% step %}

### Date header

The value of the Date header being sent in the request (see [**License API Authorization**](/license-api/license-api-authorization) for more details). This is a date string in [**RFC7231**](https://datatracker.ietf.org/doc/html/rfc7231#section-7.1.1.1) format
{% endstep %}
{% endstepper %}

This signing string is then hashed with the customer's key (shared key) using the algorithm provided in the Authorization header (e.g. **HMAC-SHA256**). The resulting hash is then encoded in **Base64**.

{% hint style="info" %}
Make sure the encoded string has no extra whitespace on any line, otherwise the base64 encoded value will be incorrect.
{% endhint %}

### Example of Signature Generation

The following is an example of how to generate a signature. First, let's assume the following values:

* Your company's **shared key** is `kw4qSnpSwXzgiv5yxYpZZmFEd9QAeiKTQ6OuyMja`
* The Date header you set in the request is `Tue, 07 Jun 2011 20:51:35 GMT`
* We'll use the default hashing algorithm, HMAC-SHA256

Now we can generate the signature:

Create the signing string:

```
licenseSpring
date: Tue, 07 Jun 2011 20:51:35 GMT
```

Hash the signing string using HMAC-SHA256

Encode the hashed value with Base64: `UDysfR6MndUZReo07Y9r+vErn8vSxrnQ5ulit18iJ/Q=`

The resulting string is then inserted into the **Authorization** header as the "signature" parameter:

```
algorithm="hmac-sha256", headers="date", signature="UDysfR6MndUZReo07Y9r+vErn8vSxrnQ5ulit18iJ/Q=", apikey="_company_api_key_here_"
```

### Sample Code

Generating a **signature**

{% tabs %}
{% tab title="JavaScript" %}

```javascript
const crypto = require('node:crypto'),
    shared_key = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
    signing_string = 'licenseSpring\ndate: Tue, 07 Jun 2011 20:51:35 GMT';

let signature = crypto.createHmac('sha256', shared_key).update(signing_string).digest('base64');

console.log(signature);
// UDysfR6MndUZReo07Y9r+vErn8vSxrnQ5ulit18iJ/Q=
```

{% endtab %}

{% tab title="Python" %}

```python
import base64
import hashlib
import hmac
import time
from wsgiref.handlers import format_date_time

# Can be found in `Account Settings` -> `Settings` -> `Keys`
shared_key = '_your_shared_key_goes_here_'

def sign(secret_key, datestamp):
    msg = 'licenseSpring\ndate: %s' % datestamp
    hashed = hmac.new(bytes(secret_key, 'utf-8'), msg.encode('utf-8'), hashlib.sha256).digest()
    return base64.b64encode(hashed).decode()

# Generate headers
date_header = format_date_time(time.time())

signing_key = sign(shared_key, date_header)
```

{% endtab %}
{% endtabs %}

Generating full HTTP request headers with SDK Demo keys

{% tabs %}
{% tab title="JavaScript" %}

```javascript
const fetch = require('node-fetch');

const sharedKey = 'XXXXXXXXX-XXXXX-XXXXXXXXXXXXX_XXXXXX_XXXXXX',
      apiKey = 'XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX',
      productCode = "XX",
      baseURL = 'https://api.licensespring.com/api/v4/webhook';

function GenerateHeaders(){
    const signingDate = (new Date()).toUTCString(),
          signingString = `licenseSpring\ndate: ${signingDate}`;

    const signature = crypto.createHmac('sha256', sharedKey).update(signingString).digest('base64');

    return {
            'Content-Type': 'application/json',
            'Date': signingDate,
            'Authorization' : `algorithm="hmac-sha256",headers="date",signature="${signature}",apikey="${apiKey}"`,
        };
}
```

{% endtab %}

{% tab title="Python" %}

```python
import base64
import hashlib
import hmac
import time
from wsgiref.handlers import format_date_time

# Can be found in `Account Settings` -> `Settings` -> `Keys`
shared_key = '_your_shared_key_goes_here_'
uuid = '_your_uuid_key_goes_here_'

def sign(secret_key, datestamp):
    msg = 'licenseSpring\ndate: %s' % datestamp
    hashed = hmac.new(bytes(secret_key, 'utf-8'), msg.encode('utf-8'), hashlib.sha256).digest()
    return base64.b64encode(hashed).decode()

# Generating headers
date_header = format_date_time(time.time())

signing_key = sign(shared_key, date_header)

auth_header = ','.join([
    'algorithm="hmac-sha256"',
    'headers="date"',
    'signature="%s"' % signing_key,
    'apiKey="%s"' % uuid
])

headers = {
  'Date': date_header,
  'Authorization': auth_header
}
```

{% endtab %}
{% endtabs %}

Full sample code for checking a license key with SDK Demo keys

{% tabs %}
{% tab title="JavaScript" %}

```javascript
const fetch = require('node-fetch');

const sharedKey = 'XXXXXXXXX-XXXXX-XXXXXXXXXXXXX_XXXXXX_XXXXXX',
      apiKey = 'XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX',
      productCode = "XX",
      baseURL = 'https://api.licensespring.com/api/v4';

function GenerateHeaders(){
    const signingDate = (new Date()).toUTCString(),
          signingString = `licenseSpring\ndate: ${signingDate}`;

    const signature = crypto.createHmac('sha256', sharedKey).update(signingString).digest('base64');

    return {
            'Content-Type': 'application/json',
            'Date': signingDate,
            'Authorization' : `algorithm="hmac-sha256",headers="date",signature="${signature}",apikey="${apiKey}"`,
        };
}

const checkLicense = async (hwid, licenseKey) => {
    const headers = GenerateHeaders()

    const response = await fetch(baseURL + '/check_license' + `?product=${productCode}&hardware_id=${hwid}&license_key=${licenseKey}`, {
        method: 'GET',
        headers: headers
    })
    console.log("/license response", await response.json());
}

checkLicense('d32q3ed3rq3rxq2r3q23q23q23', '1111-2222-3333-4444');
```

{% endtab %}

{% tab title="Python" %}

```python
import base64
import hashlib
import hmac
import time
from wsgiref.handlers import format_date_time

import requests

API_URL = 'https://api.licensespring.com'

# Can be found in `Account Settings` -> `Settings` -> `Keys`
shared_key = '_your_shared_key_goes_here_'
uuid = '_your_uuid_key_goes_here_'

def sign(secret_key, datestamp):
    msg = 'licenseSpring\ndate: %s' % datestamp
    hashed = hmac.new(bytes(secret_key, 'utf-8'), msg.encode('utf-8'), hashlib.sha256).digest()
    return base64.b64encode(hashed).decode()

# Generate headers
date_header = format_date_time(time.time())

signing_key = sign(shared_key, date_header)

auth_header = ','.join([
    'algorithm="hmac-sha256"',
    'headers="date"',
    'signature="%s"' % signing_key,
    'apiKey="%s"' % uuid
])

# Send request
product_short_code = '_your_product_short_code_goes_here_'
hardware_id = '_your_hardware_id_goes_here_'
license_key = '_your_license_key_goes_here_'

response = requests.get(
    url='{}{}'.format(API_URL, '/api/v4/check_license/'),
    params={'product': product_short_code, 'hardware_id': hardware_id, 'license_key': license_key},
    headers={
        'Date': date_header,
        'Authorization': auth_header,
        'Content-Type': 'application/json'
    }
)

print(response.json())
```

{% endtab %}
{% endtabs %}


# Response Signature

### Overview

Response objects from License API endpoints for activating and checking licenses contain a `license_signature` value, which is an HMAC-SHA256 signature used to verify the authenticity of the response. This mechanism ensures protection against counterfeit licensing servers and man-in-the-middle attacks.

### License Signature Details

When a webhook response is received, it includes a **license\_signature**. This signature is a secure hash generated using a server-side private key and is based on a specific format of the signing string.

#### Signing String Format

The string to be signed is constructed as follows:

```
LOWERCASE(HARDWARE_ID#USER_EMAIL_OR_LICENSE_KEY#VALIDITY_PERIOD)
```

#### Example Activation Request and Response

Request:

```json
{
  "hardware_id": "A53F-0CBC-15FC-7E81-BF35-A720-A575-7C0C-8815-0463-DB78-E674-D140-CF15-85BB-EC01",
  "license_key": "FUH3-4E7A-LZJL-7JTP",
  "product": "TP"
}
```

Response:

```json
{
  "license_signature": "60c22a575a67f5b2a1e9ff3fe204363046f1e5d097b8ebb468d903d0aaf739ac...",
  "validity_period": "2019-06-15T00:00:00.000Z",
  "license_type": "subscription",
  ...
}
```

#### Constructed Signing String

Based on the above request and response:

```
a53f-0cbc-15fc-7e81-bf35-a720-a575-7c0c-8815-0463-db78-e674-d140-cf15-85bb-ec01#fuh3-4e7a-lzjl-7jtp#2019-06-15t00:00:00.000z
```

### Verifying the License Signature

You can use the server's public key to verify the **license\_signature**. By validating the signature, you ensure the response originates from LicenseSpring's trusted servers.

Download the server public key from the link below to implement signature verification using the example provided:

{% file src="/files/MuIwiFmMUjvrVaJF1UJj" %}

### Code Sample

{% code title="verify-signature.js" %}

```javascript
import crypto from 'node:crypto'
import dayjs from 'dayjs'
import utc from 'dayjs/plugin/utc.js'

dayjs.extend(utc)

const response = {
  // ... truncated license response ...
  "license_key": "163U-AKLB-5BNJ-VYOF-4567",
  "hardware_id": "6993f191bca2346c4015be4ff158805da70f10cd7d82aedd11dd38c2b47025a2",
  "validity_period": null,
  "license_signature": "VAkfjWGvQDl00YkjYS/1oCz........3HtzwFt8tQ=",
};

const signingString = `${
  licenseResponse.hardware_id
}#${
  licenseResponse.username
    ? licenseResponse.username.split('|')[0]
    : licenseResponse.license_key
}#${
  licenseResponse.validity_period ? dayjs(licenseResponse.validity_period).toISOString() : ''
}`.toLowerCase();

const verifier = crypto.createVerify('RSA-SHA256');
verifier.update(signingString);
const result = verifier.verify(publicKey, licenseResponse.license_signature, 'base64');

console.log(result); // if signature is valid, this will be "true"
```

{% endcode %}


# Response Signature v2

### Overview

Response objects from License API endpoints contain an HTTP header `LicenseSignature` which is an HMAC-SHA256 signature used to verify the integrity and authenticity of the response. This is not the same as the `license_signature` in certain response objects (see [**Response Signature**](/license-api/license-api-authorization/response-signature) for that property).

The `LicenseSignature` header signs the entire response object using our private key, which enables you to use our public key to verify the signature.

### Verifying Response Signature v2

You can use the server's public key to verify the `LicenseSignature` header. By validating the signature, you ensure the response originates from LicenseSpring's trusted servers.

Download the server public key from the link below to implement signature verification using the example provided:

[Download prod.pub](https://api.archbee.com/api/presign/IJdHyjBlO9LOXOrDnWJTx/8_-OM1Oqw9QE45OBPr7Mt_prod.pub)

### Code Sample

{% code title="verify-response-signature.js" %}

```javascript
import crypto from 'node:crypto'
import dayjs from 'dayjs'
import utc from 'dayjs/plugin/utc.js'

dayjs.extend(utc)

// Header and response received from our servers:
const responseLicenseSignatureHeader = '...';
const responseBody = {
  // ... response object from LicenseSpring's server ...
};

/* 
  NOTE: specifically for the Activate License Offline endpoint, existing
  signatures in the response body need to be removed before calculating
  the v2 signature, like this:
  
  delete responseBody.license_signature;
  delete responseBody.license_signature_v2;
*/

const verifier = crypto.createVerify('RSA-SHA256');
verifier.update(JSON.stringify(response));
const result = verifier.verify(publicKey, responseLicenseSignatureHeader, 'base64');

console.log(result); // will be "true" if signature is valid
```

{% endcode %}


# Rate Limiting

## API Rate Limiting

LicenseSpring enforces rate limits on all API requests to ensure platform stability and fair usage across all customers.

### How Rate Limiting Works

Rate limits are applied **per company, per API**. Each company has a separate counter for the License API and the Management API, the two limits are independent and can carry different values.

All API keys belonging to a company share the same counter for a given API type. Adding more API keys does not increase your rate limit.

### Per-Plan Limits

All limits are measured over a **60-second rolling window**.

| Plan             | License API (requests / 60s) | Management API (requests / 60s) |
| ---------------- | ---------------------------- | ------------------------------- |
| Free             | 1,800                        | 600                             |
| Business Starter | 3,600                        | 900                             |
| Business Plus    | 4,500                        | 1,200                           |
| Enterprise       | 18,000                       | 2,400                           |

{% hint style="info" %}
These limits apply to multi-tenant Enterprise deployments and can be relaxed upon request. For single-tenant Enterprise deployments, rate limits are fully customizable. Contact [LicenseSpring support](https://licensespring.zendesk.com/hc/en-us/requests/new) for more information
{% endhint %}

### Flood Protection

In addition to per-company limits, LicenseSpring maintains the following backstop protections against traffic floods. These apply independently of your plan limit:

* **Per-IP:** 200 requests per 10 seconds
* **Per-Authorization:** 50 requests per second

These backstops cannot be configured and apply to all plans.

### Frequently Asked Questions

**Do my API keys each get their own limit?** No. All API keys for a company share a single counter per API type. The limit is per company, not per key.

**Are the License API and Management API limits separate?** Yes. Each has its own independent counter and limit value.

**What happens if I exceed my limit?** The API returns `HTTP 429 Too Many Requests`. Requests resume normally once the 60-second window resets.


# License Activation & Deactivation

The following articles explain how to activate or deactivate Licenses.

### License Activation Types

The API provides options for activating/deactivating licenses using either the Online method or Offline method. For details, see [**License Activation Types**](/license-entitlements/license-activation-types).

### License Authorization Method

There are two types of product licenses based on how the client application authorizes itself to interact with a license:

#### Key-based product licenses

Each license assigned to the product requires the client to provide a license key in order to activate, check or deactivate the license (property `license_key` in the request body).

The license response object will contain the `license_key` property, and the property `product_details.authorization_method` will be the string `license_key`.

#### User-based product licenses

Each license assigned to the product has a corresponding "license user" instead of a license key. There are multiple ways a user-based product license can be accessed when making a request:

* By providing the values for `username` and `password` for the license user
* By providing the values for `id_token` and `customer_account_code` if using single sign-on authentication using Implicit grant
* By providing the values for `code` and `customer_account_code` if using single sign-on authentication using Authorization code grant

In the license response object, `product_details.authorization_method` will have the string value `user` and the response object will contain the `user` object with information on the license user.

### License Types

The `license_type` property defines one of the 4 types of licenses based on their duration and how the license is used:

* Perpetual: `perpetual`
* Time-limited: `time-limited`
* Subscription: `subscription`
* Consumption: `consumption`

For more information see: [**License Types**](/license-entitlements/license-types)

### API Methods

For more information see: [**License Types**](/license-entitlements/license-types)

### API Methods

* [**Activate License (Online Method)**](/license-api/license-activation-and-deactivation/activate-license-online-method)
* [**Activate License (Offline Method)**](/license-api/license-activation-and-deactivation/activate-license-offline-method)
* [**Activate Bundle (Online Method)**](/license-api/license-activation-and-deactivation/activate-bundle-online-method)
* [**Activate Bundle (Offline Method)**](/license-api/license-activation-and-deactivation/activate-bundle-offline-method)
* [**Deactivate License (Online Method)**](/license-api/license-activation-and-deactivation/deactivate-license-online-method)
* [**Deactivate License (Offline Method)**](/license-api/license-activation-and-deactivation/deactivate-license-offline-method)
* [**Deactivate Bundle (Online Method)**](/license-api/license-activation-and-deactivation/deactivate-bundle-online-method)
* [**Deactivate Bundle (Offline Method)**](/license-api/license-activation-and-deactivation/deactivate-bundle-offline-method)


# Activate License (Online Method)

### Endpoint

* Method: `POST`
* Path: `/api/v4/activate_license`
* Description: Activates a license for a device and returns the license payload.

### Authentication

These requests are authenticated and signed. See [License API Authorization](/license-api/license-api-authorization).

#### Required headers

* `Date` (string) — RFC7231 GMT date string (example: `Thu, 17 Nov 2022 20:51:35 GMT`)
* `Authorization` (string) — signature or bearer token (depending on auth method)

#### Optional headers

* `licensespring-tpm-signature`  (string) — TPM licensing signature and algorithm
  * `licensespring-tpm-signature: algorithm="ES256",signature=""`

#### Recommended headers

* `Accept: application/json`
* `Content-Type: application/json`

### Request

#### Body parameters

Required:

* `hardware_id` (string) — Unique hardware ID generated for the client device
* `product` (string) — Product short code

One of (depending on product authorization method):

* `license_key` (string) — Required for key-based products
* `username` + `password` (string) — Required for user-based products
* `id_token` (use access\_token value) + `customer_account_code` (string) — Required for SSO Implicit grant
* `code` + `customer_account_code` (string) — Required for SSO Authorization Code grant

Optional:

* `license_id` (number) — Ensures the action affects only the license with this ID
* `app_ver` (string)
* `app_name` (string)
* `sdk_ver` (string)
* `variables` (object) — Device variables as a JSON object
* `redirect_uri` (string)
* `bundle_code` (string)
* `include_metadata_string` (boolean)
* `tpm_enrollment` (object) — Signing public key for TPM licensing

#### Minimal example (JSON)

```json
{
  "hardware_id": "some-unique-id",
  "product": "XY",
  "license_key": "AAAA-BBBB-CCCC-DDDD"
}
```

***

### Schema

#### Request Body

<details>

<summary><strong>Request schema (TypeScript + JSON Schema)</strong></summary>

**TypeScript**

```typescript
type LicenseActivationRequestBody = ({

  // for key-based licenses:
  license_key: string

} | {

  // for user-based licenses, using username & password authentication:
  username: string
  password: string

} | {

  // for user-based licenses, using single sign-on Implicit grant authentication:
  id_token: string
  customer_account_code: string

} | {

  // for user-based licenses, using single sign-on Authorization code grant authentication:
  code: string
  customer_account_code: string

}) & {

  // required properties:
  hardware_id: string
  product: string

  // optional properties:
  bundle_code?: string | undefined
  license_id?: number | undefined
  app_ver?: string | undefined
  app_name?: string | undefined
  sdk_ver?: string | undefined
  redirect_uri?: string | undefined
  include_metadata_string?: string | undefined
  tpm_enrollment?: { signing_public_key: string } | undefined
  variables?: { [key: string]: string } | undefined
}
```

**JSON Schema**

```json
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "allOf": [
    {
      "oneOf": [
        {
          "type": "object",
          "properties": {
            "license_key": { "type": "string" }
          },
          "required": ["license_key"],
          "additionalProperties": false
        },
        {
          "type": "object",
          "properties": {
            "username": { "type": "string" },
            "password": { "type": "string" }
          },
          "required": ["username", "password"],
          "additionalProperties": false
        },
        {
          "type": "object",
          "properties": {
            "id_token": { "type": "string" },
            "customer_account_code": { "type": "string" }
          },
          "required": ["id_token", "customer_account_code"],
          "additionalProperties": false
        },
        {
          "type": "object",
          "properties": {
            "code": { "type": "string" },
            "customer_account_code": { "type": "string" }
          },
          "required": ["code", "customer_account_code"],
          "additionalProperties": false
        }
      ]
    },
    {
      "type": "object",
      "properties": {
        "hardware_id": { "type": "string" },
        "product": { "type": "string" },
        "bundle_code": { "type": "string" },
        "redirect_uri": { "type": "string" },
        "license_id": { "type": "number" },
        "app_ver": { "type": "string" },
        "app_name": { "type": "string" },
        "sdk_ver": { "type": "string" },
        "include_metadata_string": { "type": "boolean" },
        "tpm_enrollment": { 
          "type": "object",
          "properties": {
            "signing_public_key": { "type": "string" },
          },
          "required": ["signing_public_key"],
        },
        "variables": {
          "type": ["object", "null"],
          "additionalProperties": { "type": "string" }
        }
      },
      "required": ["hardware_id", "product"],
      "additionalProperties": false
    }
  ]
}
```

</details>

***

#### Response Body

<details>

<summary><strong>Response schema (TypeScript + JSON Schema)</strong></summary>

Full response schemas are shared across activation endpoints. See [Activate License *Offline Method*](/license-api/license-activation-and-deactivation/activate-license-offline-method) for the complete response TypeScript shape.

**TypeScript**

```typescript
// See Activate License (Offline) for the full shape.
type LicenseActivationResponseBody = object;
```

**JSON Schema**

```json
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "$comment": "See Activate License (Offline) for the full schema."
}
```

</details>

Response objects contain a `license_signature` value which the client app can use to verify authenticity (see [**Response Signature**](/license-api/license-api-authorization/response-signature)).

***

### License Authorization Method

There are two types of product licenses based on how the client application authorizes itself to interact with a license:

#### Key-based product licenses

Each license assigned to the product requires the client to provide a license key (license\_key) in order to activate, check or deactivate the license. The response will contain license\_key and product\_details.authorization\_method will be "license-key".

#### User-based product licenses

Each license has a corresponding "license user" instead of a license key. Ways to access:

* username & password
* id\_token (use access\_token value) & customer\_account\_code (SSO Implicit grant)
* code & customer\_account\_code (SSO Authorization code grant)

In the response, product\_details.authorization\_method will be "user" and the response will contain a user object.

***

### License Types

license\_type property values:

* Perpetual: perpetual
* Time-limited: time-limited
* Subscription: subscription
* Consumption: consumption

See: [License Types](/license-entitlements/license-types)

***

### Errors

If an error occurs, HTTP status will be 400 or higher and the response body will have:

```typescript
{
  status: number,
  code: string,
  message: string
}
```

JSON Schema

```json
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "status": { "type": "number" },
    "code": { "type": "string" },
    "message": { "type": "string" }
  },
  "required": [
    "status",
    "code",
    "message"
  ],
  "additionalProperties": false
}
```

List of exceptions:

> unknown\_product (400): Provided product was not found

> license\_not\_found (400): License with the provided license user not found

> license\_not\_enabled (400): The license is not enabled

> vm\_not\_allowed (400): Virtual machine not allowed

> offline\_floating\_cloud\_not\_supported (400): Floating cloud licenses cannot be activated offline

> license\_start\_date\_error (400): This license cannot be activated before start date: {date}

> blacklisted (400): This device is blacklisted

> license\_activated\_max\_times (400): This license key has already been activated the maximum number of times

> license\_user\_activated\_max\_times (400): License user has activated this license max times

> license\_expired (400): License validity period has expired

> license\_transferred\_max\_times (400): This license has already been transferred the maximum number of times

> license\_device\_exists (409): A device matching this hardware\_id is already created on the license.

> product\_version\_not\_supported (400): License product version does not support the app\_ver sent in the request

> tpm\_not\_enabled (400): This license doesn't have TPM licensing enabled, and tpm\_enrollment is being sent in the request

> tpm\_enrollment\_required (400): This license has TPM licensing enabled, but tpm\_enrollment is not being sent in the request

> tpm\_invalid\_public\_key (400): The TPM public key sent is not valid

> tpm\_signature\_required (400): The TPM signature is missing from the header and TPM usage is enforced

> tpm\_signature\_missmatch (400): The TPM signature/algorithm is not ES256 and the signature can't be verified against the TPM public key

***

### Examples

{% tabs %}
{% tab title="curl" %}

```bash
curl --location --request POST '/api/v4/activate_license' \
--header 'Accept: application/json' \
--header 'Content-Type: application/json' \
--header 'Date: string' \
--header 'Authorization: string' \
--data-raw '{
  "hardware_id": "some-unique-id",
  "product": "XY",
  "license_key": "AAAA-BBBB-CCCC-DDDD"
}'
```

{% endtab %}

{% tab title="nodejs" %}

```javascript
var request = require('request');

var options = {
  method: 'POST',
  url: '/api/v4/activate_license',
  headers: {
    'Accept': 'application/json',
    'Content-Type': 'application/json',
    'Date': 'string',
    'Authorization': 'string'
  },
  body: JSON.stringify({
    hardware_id: 'some-unique-id',
    product: 'XY',
    license_key: 'AAAA-BBBB-CCCC-DDDD'
  })
};

request(options, function (error, response) {
  if (error) throw new Error(error);
  console.log(response.body);
});
```

{% endtab %}

{% tab title="javascript (fetch)" %}

```javascript
var myHeaders = new Headers();
myHeaders.append("Accept", "application/json");
myHeaders.append("Content-Type", "application/json");
myHeaders.append("Date", "string");
myHeaders.append("Authorization", "string");

var raw = JSON.stringify({
  hardware_id: "some-unique-id",
  product: "XY",
  license_key: "AAAA-BBBB-CCCC-DDDD"
});

var requestOptions = {
  method: 'POST',
  headers: myHeaders,
  body: raw,
  redirect: 'follow'
};

fetch("/api/v4/activate_license", requestOptions)
  .then(response => response.text())
  .then(result => console.log(result))
  .catch(error => console.log('error', error));
```

{% endtab %}

{% tab title="python" %}

```python
import requests

url = "/api/v4/activate_license"

payload = {
  "hardware_id": "some-unique-id",
  "product": "XY",
  "license_key": "AAAA-BBBB-CCCC-DDDD"
}

headers = {
  "Accept": "application/json",
  "Content-Type": "application/json",
  "Date": "string",
  "Authorization": "string"
}

response = requests.post(url, json=payload, headers=headers)
print(response.text)
```

{% endtab %}

{% tab title="ruby" %}

```ruby
require "uri"
require "net/http"
require "json"

url = URI("/api/v4/activate_license")

http = Net::HTTP.new(url.host, url.port)
request = Net::HTTP::Post.new(url)
request["Accept"] = "application/json"
request["Content-Type"] = "application/json"
request["Date"] = "string"
request["Authorization"] = "string"
request.body = JSON.dump({
  hardware_id: "some-unique-id",
  product: "XY",
  license_key: "AAAA-BBBB-CCCC-DDDD"
})

response = http.request(request)
puts response.read_body
```

{% endtab %}
{% endtabs %}

#### Single-Sign-On License Activation

{% hint style="info" %}
SSO setup and how to get the SSO URL: [Single Sign On URL](/license-api/single-sign-on-url).
{% endhint %}

```bash
curl --location --request POST 'https://api.licensespring.com/api/v4/activate_license/' \
--header 'Date: Mon, 10 May 2021 13:14:18 GMT' \
--header 'Authorization: algorithm="hmac-sha256",headers="date",signature="{generated_signature}",apikey="{your_api_key}"' \
--header 'Content-Type: application/json' \
--data-raw '{
    "id_token": "{access_token_here}",
  	"code": "{code_here}",
    "customer_account_code": "acme_inc",
    "hardware_id": "some-unique-id",
    "product": "XY"
}'
```

{% hint style="warning" %}
Use either id\_token or code, not both at the same time.
{% endhint %}

***

### Device Variables

The optional `variables` property lets you set device variables during activation. See [Device Variables](/license-api/device-variables).


# Activate License (Offline Method)

{% hint style="info" %}
Currently we support trial, perpetual and consumption license types for offline activation purposes
{% endhint %}

### Endpoint

* Method: `POST`
* Path: `/api/v4/activate_offline`
* Description: Activates a license using the offline activation flow (base64 payload).

### Authentication

See [License API Authorization](/license-api/license-api-authorization).

#### Required headers

* `Date` (string) — RFC7231 GMT date string
* `Authorization` (string) — signature or bearer token

#### Recommended headers

* `Accept: application/json`

### Request

#### Body

The request body is a **base64-encoded, stringified JSON object**.

{% hint style="danger" %}
If using `multipart/form-data`, the `file` form parameter is mandatory.
{% endhint %}

### Schema

The request body is a string representing a base64-encoded JSON object containing all the required activation data.

<details>

<summary><strong>Request body schema (TypeScript + JSON Schema)</strong></summary>

**TypeScript**

```typescript
type LicenseOfflineActivationObject = ({

  // for key-based licenses:
  license_key: string

} | {

  // for user-based licenses:
  username: string
  password: string

}) & {

  // required properties:
  hardware_id: string
  product: string
  signature: string
  date: string

} & ({
  api_key: string // for API key authorization
} | {
  client_id: string // for OAuth authorization
}) & {

  // optional properties:
  request?: string | undefined
  request_id?: string | undefined
  bundle_code?: string | undefined
  license_id?: number | undefined
  is_vm?: boolean | undefined
  vm_info?: string | undefined
  os_ver?: string | undefined
  hostname?: string | undefined
  os_hostname?: string | undefined
  ip?: string | undefined
  ip_local?: string | undefined
  app_ver?: string | undefined
  app_name?: string | undefined
  sdk_ver?: string | undefined
  mac_address?: string | undefined
  include_metadata_string?: boolean | undefined
  licensespring-tpm-signature?: string | undefined
  tpm_enrollment?: { signing_public_key: string } | undefined
  variables?: { [key: string]: string } | undefined
}
```

**JSON Schema**

```json
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "allOf": [
    {
      "oneOf": [
        {
          "type": "object",
          "properties": {
            "license_key": { "type": "string" }
          },
          "required": ["license_key"],
          "additionalProperties": false
        },
        {
          "type": "object",
          "properties": {
            "username": { "type": "string" },
            "password": { "type": "string" }
          },
          "required": ["username", "password"],
          "additionalProperties": false
        }
      ]
    },
    {
      "type": "object",
      "properties": {
        "api_key": { "type": "string" },
        "client_id": { "type": "string" },
        "request": { "type": "string" },
        "request_id": { "type": "string" },
        "date": { "type": "string" },
        "signature": { "type": "string" },
        "licensespring-tpm-signature": { "type": "string" },
        "hardware_id": { "type": "string" },
        "product": { "type": "string" },
        "bundle_code": { "type": "string" },
        "license_id": { "type": "number" },
        "is_vm": { "type": "boolean" },
        "vm_info": { "type": "string" },
        "os_ver": { "type": "string" },
        "hostname": { "type": "string" },
        "os_hostname": { "type": "string" },
        "ip": { "type": "string" },
        "ip_local": { "type": "string" },
        "app_ver": { "type": "string" },
        "app_name": { "type": "string" },
        "sdk_ver": { "type": "string" },
        "mac_address": { "type": "string" },
        "include_metadata_string": { "type": "string" },
        "tpm_enrollment": { 
          "type": "object",
          "properties": {
            "signing_public_key": { "type": "string" },
          },
          "required": ["signing_public_key"],
        },
        "variables": {
          "type": "object",
          "additionalProperties": { "type": "string" }
        }
      },
      "allOf": [
        { "anyOf": [{ "required": ["api_key"] }, { "required": ["client_id"] }] },
        { "anyOf": [{ "required": ["license_key"] }, { "required": ["username", "password"] }] }
      ],
      "required": ["hardware_id", "product", "date", "signature"],
      "additionalProperties": false
    }
  ]
}
```

</details>

### Signature

The request `signature` is constructed and signed with HMAC-SHA256 using the company shared key (or client secret for OAuth). Steps to construct the signing string:

{% stepper %}
{% step %}

### Build the signing string — part 1

Start with:

* The literal string: licenseSpring
* Then a newline
  {% endstep %}

{% step %}

### Build the signing string — part 2

Append:

* The string "date: " plus the date value from the license payload, then a newline
  {% endstep %}

{% step %}

### Build the signing string — part 3

Append:

* Either the license\_key or username value from the payload (whichever is present), then a newline
  {% endstep %}

{% step %}

### Build the signing string — part 4

Append:

* The hardware\_id value from the payload, then a newline
  {% endstep %}

{% step %}

### Build the signing string — part 5

Append:

* The api\_key (or client\_id) value from the payload

Encrypt the complete string using HMAC-SHA256 with the signing key (Shared Key for API key auth, Client Secret for OAuth). Example (Node.js):

```javascript
import crypto from 'node:crypto';

const activationPayload = {
  // ...payload content...
};

// api_key or client_id depending on authorization type used:
const key = (activationPayload.api_key || activationPayload.client_id);

// if using API key authorization: the signing key is the Shared Key
// if using OAuth: the signing key is the Client Secret
const signingKey = '...';

const signingString =
  'licenseSpring\n' +
  'date: ' + activationPayload.date + '\n' +
  (activationPayload.license_key || activationPayload.username) + '\n' +
  activationPayload.hardware_id + '\n' +
  key;

const signature = crypto
  .createHmac('sha256', signingKey)
  .update(signingString)
  .digest('base64');
```

{% endstep %}
{% endstepper %}

### Finalized payload

Stringify the activation object and encode it to base64.

{% tabs %}
{% tab title="JS Browser" %}

```javascript
const activationPayload = {
  // ...payload content...
};
const requestBody = btoa(JSON.stringify(activationPayload));
```

{% endtab %}

{% tab title="nodeJS" %}

```javascript
const activationPayload = {
  // ...payload content...
};
const requestBody = Buffer.from(JSON.stringify(activationPayload)).toString('base64');
```

{% endtab %}
{% endtabs %}

### Examples

Send the base64 payload as the request body. Set the Date and Authorization headers.

{% tabs %}
{% tab title="curl" %}

```bash
curl --location --request POST '/api/v4/activate_offline' \
--header 'Accept: application/json' \
--header 'Date: string' \
--header 'Authorization: string' \
--data-raw '_BASE64_PAYLOAD_HERE_'
```

{% endtab %}

{% tab title="nodejs" %}

```javascript
var request = require('request');
var options = {
  method: 'POST',
  url: '/api/v4/activate_offline',
  headers: {
    'Accept': 'application/json',
    'Date': 'string',
    'Authorization': 'string'
  },
  body: Buffer.from(JSON.stringify(offline_payload)).toString('base64')
};

request(options, function (error, response) {
  if (error) throw new Error(error);
  console.log(response.body);
});
```

{% endtab %}

{% tab title="javascript (fetch)" %}

```javascript
var myHeaders = new Headers();
myHeaders.append("Accept", "application/json");
myHeaders.append("Date", "string");
myHeaders.append("Authorization", "string");

var requestOptions = {
  method: 'POST',
  headers: myHeaders,
  body: btoa(JSON.stringify(offline_payload)),
  redirect: 'follow'
};

fetch("/api/v4/activate_offline", requestOptions)
  .then(response => response.text())
  .then(result => console.log(result))
  .catch(error => console.log('error', error));
```

{% endtab %}

{% tab title="python" %}

```python
import requests

url = "/api/v4/activate_offline"

payload = "_BASE64_PAYLOAD_HERE_"
headers = {
  'Accept': 'application/json',
  'Date': 'string',
  'Authorization': 'string'
}

response = requests.request("POST", url, headers=headers, data=payload)

print(response.text)
```

{% endtab %}

{% tab title="ruby" %}

```ruby
require "uri"
require "net/http"

url = URI("/api/v4/activate_offline")

http = Net::HTTP.new(url.host, url.port)
request = Net::HTTP::Post.new(url)
request["Accept"] = "application/json"
request["Date"] = "string"
request["Authorization"] = "string"
request.body = "_BASE64_PAYLOAD_HERE_"

response = http.request(request)
puts response.read_body
```

{% endtab %}
{% endtabs %}

### Response

<details>

<summary><strong>Response schema (TypeScript + JSON Schema)</strong></summary>

**TypeScript**

```typescript
type InstallationFile = {
  id: number,
  channel: string,
  environment: string,
  version: string,
  installation_file: string,
  hash_md5: string,
  eula_link: string,
  release_date: string | null,
  requires_version: string,
  size: string,
  release_notes_link: string,
};

type LicenseActivationResponseBody = {
  id: number,
  request: string,
  active: boolean,
  allow_grace_period: boolean,
  allow_overages: boolean,
  allow_unlimited_activations: boolean,
  allow_offline_activation: boolean,
  can_borrow: boolean,
  device_id: number,
  hardware_id: string,
  enable_maintenance_period: boolean,
  floating_timeout: number,
  grace_period: number,
  installation_file: InstallationFile,
  is_air_gapped: boolean,
  is_expired: boolean,
  is_floating_cloud: boolean,
  is_floating: boolean,
  is_hardware_key_auth: boolean,
  is_tpm_auth: boolean,
  is_trial: boolean,
  license_signature: string,
  license_signature_v2: string,
  offline_signature: string,
  license_type: string,
  license_template_id: number | null,
  maintenance_period: string | null,
  max_activations: number,
  max_borrow_time: number,
  max_license_users: number,
  max_overages: number,
  max_transfers: number,
  order_store_id: string | null,
  prevent_vm: boolean,
  start_date: string | null,
  times_activated: number,
  transfer_count: number,
  validity_period: string | null,
  is_bundle: boolean | null,
  bundle_license_id: number | null,
  company: { id: number },
  
  product_features: ({
    id: number,
    code: string,
    name?: string,
    product_feature_id: number,
    expiry_date: string | null,
    valid_duration: string | null,
    is_expired: boolean,
    is_global?: boolean,
    metadata: JSON,
    metadata_string?: string,
    can_borrow: boolean,
    max_borrow_time: number,
    feature_type: 'activation' | 'consumption',
    is_floating: boolean,
    is_floating_cloud: boolean,

    // the following properties are only present if is_floating=true or is_floating_cloud=true
    floating_users?: number,
    floating_timeout?: number,

    // the following properties are only present if feature_type=consumption
    max_consumption: number,
    allow_negative_consumptions: boolean,
    allow_unlimited_consumptions: boolean,
    total_consumptions: number,
    allow_overages: boolean,
    max_overages: number,
    reset_consumption: boolean,
    consumption_period: 'daily' | 'weekly' | 'monthly' | 'annually',
  })[],
  
  custom_fields: ({
    name: string,
    data_type: 'number' | 'text' | 'date/time',
    value: string,
  })[],
  
  customer: {
    email: string,
    company_name: string,
    reference: string,
    phone: string,
    first_name: string,
    last_name: string,
    city: string,
    postcode: string,
    state: string,
    country: string,
    address: string,
    customer_account: {
      id: number,
      name: string,
      code: string
    } | null,
    metadata: JSON,
    metadata_string?: string
  },
  
  product_details: {
    product_id: number,
    product_name: string,
    short_code: string,
    authorization_method: 'license-key' | 'user',
    metadata: JSON,
    metadata_string?: string
  },
  
  variables: {
    value: string,
    device_id: number,
    variable: string,
    created_at: string | null,
  }[],
  
  metadata: JSON,
  
  // the following property is only present if is_trial=true
  trial_days: number,
  
  // the following property is only present when is_floating_cloud=true
  floating_in_use: boolean,
  
  // the following properties are only present if is_floating=true or is_floating_cloud=true
  floating_in_use_devices: number,
  floating_users: number,
  
  // the following properties are only present if license_type='consumption'
  max_consumptions: number,
  total_consumptions: number,
  allow_unlimited_consumptions: boolean,
  allow_negative_consumptions: boolean,
  reset_consumption: boolean,
  consumption_period: string | null,
} & ({
  license_key: string,
} | {
  user: {
    id: number,
    email: string,
    first_name: string,
    last_name: string,
    phone_number: string,
    is_initial_password: boolean,
    max_activations: number,
    allow_unlimited_activations: boolean,
    total_activations: number
  }
});
```

**JSON Schema**

```json
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "id": { "type": "number" },
    "request": { "type": "string" },
    "active": { "type": "boolean" },
    "allow_grace_period": { "type": "boolean" },
    "allow_overages": { "type": "boolean" },
    "allow_unlimited_activations": { "type": "boolean" },
    "allow_offline_activation": { "type": "boolean" },
    "can_borrow": { "type": "boolean" },
    "device_id": { "type": "number" },
    "hardware_id": { "type": "string" },
    "enable_maintenance_period": { "type": "boolean" },
    "floating_timeout": { "type": "number" },
    "grace_period": { "type": "number" },

    "installation_file": {
      "type": "object",
      "properties": {
        "id": { "type": "number" },
        "channel": { "type": "string" },
        "environment": { "type": "string" },
        "version": { "type": "string" },
        "installation_file": { "type": "string" },
        "hash_md5": { "type": "string" },
        "eula_link": { "type": "string" },
        "release_date": { "type": ["string", "null"] },
        "requires_version": { "type": "string" },
        "size": { "type": "string" },
        "release_notes_link": { "type": "string" }
      }
    },

    "is_air_gapped": { "type": "boolean" },
    "is_expired": { "type": "boolean" },
    "is_floating_cloud": { "type": "boolean" },
    "is_floating": { "type": "boolean" },
    "is_hardware_key_auth": { "type": "boolean" },
    "is_tpm_auth": { "type": "boolean" },
    "is_trial": { "type": "boolean" },

    "license_signature": { "type": "string" },
    "license_signature_v2": { "type": "string" },
    "offline_signature": { "type": "string" },
    "license_type": { "type": "string" },

    "license_template_id": { "type": ["number", "null"] },
    "maintenance_period": { "type": ["string", "null"] },

    "max_activations": { "type": "number" },
    "max_borrow_time": { "type": "number" },
    "max_license_users": { "type": "number" },
    "max_overages": { "type": "number" },
    "max_transfers": { "type": "number" },

    "order_store_id": { "type": ["string", "null"] },
    "prevent_vm": { "type": "boolean" },
    "start_date": { "type": ["string", "null"] },

    "times_activated": { "type": "number" },
    "transfer_count": { "type": "number" },
    "validity_period": { "type": ["string", "null"] },

    "is_bundle": { "type": ["boolean", "null"] },
    "bundle_license_id": { "type": ["number", "null"] },

    "company": {
      "type": "object",
      "properties": {
        "id": { "type": "number" }
      }
    },

    "product_features": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "id": { "type": "number" },
          "code": { "type": "string" },
          "name": { "type": "string" },
          "product_feature_id": { "type": "number" },
          "expiry_date": { "type": ["string", "null"] },
          "valid_duration": { "type": ["string", "null"] },
          "is_expired": { "type": "boolean" },
          "is_global": { "type": "boolean" },
          "metadata": {},
          "metadata_string": { "type": "string" },
          "can_borrow": { "type": "boolean" },
          "max_borrow_time": { "type": "number" },
          "feature_type": {
            "type": "string",
            "enum": ["activation", "consumption"]
          },
          "is_floating": { "type": "boolean" },
          "is_floating_cloud": { "type": "boolean" },

          "floating_users": { "type": "number" },
          "floating_timeout": { "type": "number" },

          "max_consumption": { "type": "number" },
          "allow_negative_consumptions": { "type": "boolean" },
          "allow_unlimited_consumptions": { "type": "boolean" },
          "total_consumptions": { "type": "number" },
          "allow_overages": { "type": "boolean" },
          "max_overages": { "type": "number" },
          "reset_consumption": { "type": "boolean" },
          "consumption_period": {
            "type": "string",
            "enum": ["daily", "weekly", "monthly", "annually"]
          }
        }
      }
    },

    "custom_fields": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "name": { "type": "string" },
          "data_type": {
            "type": "string",
            "enum": ["number", "text", "date/time"]
          },
          "value": { "type": "string" }
        }
      }
    },

    "customer": {
      "type": "object",
      "properties": {
        "email": { "type": "string" },
        "company_name": { "type": "string" },
        "reference": { "type": "string" },
        "phone": { "type": "string" },
        "first_name": { "type": "string" },
        "last_name": { "type": "string" },
        "city": { "type": "string" },
        "postcode": { "type": "string" },
        "state": { "type": "string" },
        "country": { "type": "string" },
        "address": { "type": "string" },

        "customer_account": {
          "type": ["object", "null"],
          "properties": {
            "id": { "type": "number" },
            "name": { "type": "string" },
            "code": { "type": "string" }
          }
        },

        "metadata": {},
        "metadata_string": { "type": "string" }
      }
    },

    "product_details": {
      "type": "object",
      "properties": {
        "product_id": { "type": "number" },
        "product_name": { "type": "string" },
        "short_code": { "type": "string" },
        "authorization_method": {
          "type": "string",
          "enum": ["license-key", "user"]
        },
        "metadata": {},
        "metadata_string": { "type": "string" }
      }
    },

    "variables": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "value": { "type": "string" },
          "device_id": { "type": "number" },
          "variable": { "type": "string" },
          "created_at": { "type": ["string", "null"] }
        }
      }
    },

    "metadata": {},

    "trial_days": { "type": "number" },
    "floating_in_use": { "type": "boolean" },
    "floating_in_use_devices": { "type": "number" },
    "floating_users": { "type": "number" },

    "max_consumptions": { "type": "number" },
    "total_consumptions": { "type": "number" },
    "allow_unlimited_consumptions": { "type": "boolean" },
    "allow_negative_consumptions": { "type": "boolean" },
    "reset_consumption": { "type": "boolean" },
    "consumption_period": { "type": ["string", "null"] }
  },

  "oneOf": [
    {
      "properties": {
        "license_key": { "type": "string" }
      }
    },
    {
      "properties": {
        "user": {
          "type": "object",
          "properties": {
            "id": { "type": "number" },
            "email": { "type": "string" },
            "first_name": { "type": "string" },
            "last_name": { "type": "string" },
            "phone_number": { "type": "string" },
            "is_initial_password": { "type": "boolean" },
            "max_activations": { "type": "number" },
            "allow_unlimited_activations": { "type": "boolean" },
            "total_activations": { "type": "number" }
          }
        }
      }
    }
  ]
}
```

</details>

### Response signatures

The response contains two digital signatures:

* license\_signature: HMAC-SHA256 signature explained in [Response Signature](/license-api/license-api-authorization/response-signature).
* offline\_signature: HMAC-SHA256 signature specific to this endpoint, constructed identically to the request `signature` generation but using the response `date` value.

### Errors

If an error occurs, the response will have an HTTP status code of 400 or higher, and the response body will contain an error description in the following format:

```typescript
{
  status: number,
  code: string,
  message: string
}
```

JSON Schema

```json
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "status": { "type": "number" },
    "code": { "type": "string" },
    "message": { "type": "string" }
  },
  "required": [
    "status",
    "code",
    "message"
  ],
  "additionalProperties": false
}
```

List of exceptions

> missing\_headers (400): some headers are missing&#x20;
>
> * When missing authorization or date headers

> missing\_parameters (400): some parameters are missing in the request&#x20;
>
> * When no request body at all or no file found in request body

> authorization\_missing\_params (400): some parameters are missing in authorization
>
> * when request body is not properly base64 encoded
> * when file is missing in request body (multipart case)
> * when license\_key or hardware\_id body parameters are missing
> * when data body parameter is missing
> * when api\_key parameter is missing

> tpm\_not\_enabled (400): This license doesn't have TPM licensing enabled, and tpm\_enrollment is being sent in the request

> tpm\_enrollment\_required (400): This license has TPM licensing enabled, but tpm\_enrollment is not being sent in the request

> tpm\_invalid\_public\_key (400): The TPM public key sent is not valid

> tpm\_signature\_required (400): The TPM signature is missing from the body/license file and TPM usage is enforced

> tpm\_signature\_missmatch (400): The TPM signature/algorithm is not ES256 and the signature can't be verified against the TPM public key

{% hint style="info" %}
If you want to use this API endpoint directly, instead of using an SDK (which does most of the heavy lifting for you), please contact us for additional instructions.
{% endhint %}

### Guide on offline licensing

If any aspect of the offline licensing model remains unclear or raises questions, see: [Offline License Activation](/license-entitlements/license-activation-types/offline-license-activation).


# Activate Bundle (Online Method)

### Endpoint

* Method: `POST`
* Path: `/api/v4/activate_bundle`
* Description: Activates all licenses inside a bundle and returns the activated licenses list.

### Authentication

See [License API Authorization](/license-api/license-api-authorization).

#### Required headers

* `Date` (string) — RFC7231 GMT date string
* `Authorization` (string) — signature or bearer token

#### Optional headers

* `licensespring-tpm-signature`  (string) — TPM licensing signature and algorithm
  * `licensespring-tpm-signature: algorithm="ES256",signature=""`

#### Recommended headers

* `Accept: application/json`
* `Content-Type: application/json`

### Request

#### Body parameters

Required:

* `hardware_id` (string) — Unique hardware ID generated for the client device
* `product` (string) — Bundle product short code

One of:

* `license_key` (string) — For key-based bundles
* `username` + `password` (string) — For user-based bundles
* `id_token` + `customer_account_code` (string) — SSO Implicit grant
* `code` + `customer_account_code` (string) — SSO Authorization Code grant

Optional:

* `license_id` (number)
* `is_vm` (boolean)
* `vm_info` (string)
* `os_ver` (string)
* `hostname` (string)
* `os_hostname` (string)
* `ip` (string)
* `ip_local` (string)
* `app_ver` (string)
* `sdk_ver` (string)
* `mac_address` (string)
* `variables` (object)
* `tpm_enrollment` (object) — Signing public key for TPM licensing

#### Minimal example (JSON)

```json
{
  "hardware_id": "some-unique-id",
  "product": "BUNDLE",
  "license_key": "AAAA-BBBB-CCCC-DDDD"
}
```

### Examples

{% tabs %}
{% tab title="curl" %}

```bash
curl --location --request POST '/api/v4/activate_bundle' \
--header 'Accept: application/json' \
--header 'Content-Type: application/json' \
--header 'Date: string' \
--header 'Authorization: string' \
--data-raw '{
  "hardware_id": "some-unique-id",
  "product": "BUNDLE",
  "license_key": "AAAA-BBBB-CCCC-DDDD"
}'
```

{% endtab %}

{% tab title="nodejs" %}

```javascript
var request = require('request');

var options = {
  method: 'POST',
  url: '/api/v4/activate_bundle',
  headers: {
    'Accept': 'application/json',
    'Content-Type': 'application/json',
    'Date': 'string',
    'Authorization': 'string'
  },
  body: JSON.stringify({
    hardware_id: 'some-unique-id',
    product: 'BUNDLE',
    license_key: 'AAAA-BBBB-CCCC-DDDD'
  })
};

request(options, function (error, response) {
  if (error) throw new Error(error);
  console.log(response.body);
});
```

{% endtab %}

{% tab title="javascript (fetch)" %}

```javascript
var myHeaders = new Headers();
myHeaders.append("Accept", "application/json");
myHeaders.append("Content-Type", "application/json");
myHeaders.append("Date", "string");
myHeaders.append("Authorization", "string");

var raw = JSON.stringify({
  hardware_id: "some-unique-id",
  product: "BUNDLE",
  license_key: "AAAA-BBBB-CCCC-DDDD"
});

var requestOptions = {
  method: 'POST',
  headers: myHeaders,
  body: raw,
  redirect: 'follow'
};

fetch("/api/v4/activate_bundle", requestOptions)
  .then(response => response.text())
  .then(result => console.log(result))
  .catch(error => console.log('error', error));
```

{% endtab %}

{% tab title="python" %}

```python
import requests

url = "/api/v4/activate_bundle"

payload = {
  "hardware_id": "some-unique-id",
  "product": "BUNDLE",
  "license_key": "AAAA-BBBB-CCCC-DDDD"
}

headers = {
  "Accept": "application/json",
  "Content-Type": "application/json",
  "Date": "string",
  "Authorization": "string"
}

response = requests.post(url, json=payload, headers=headers)
print(response.text)
```

{% endtab %}

{% tab title="ruby" %}

```ruby
require "uri"
require "net/http"
require "json"

url = URI("/api/v4/activate_bundle")

http = Net::HTTP.new(url.host, url.port)
request = Net::HTTP::Post.new(url)
request["Accept"] = "application/json"
request["Content-Type"] = "application/json"
request["Date"] = "string"
request["Authorization"] = "string"
request.body = JSON.dump({
  hardware_id: "some-unique-id",
  product: "BUNDLE",
  license_key: "AAAA-BBBB-CCCC-DDDD"
})

response = http.request(request)
puts response.read_body
```

{% endtab %}
{% endtabs %}

### Schema

#### Request Body

<details>

<summary><strong>Request schema (TypeScript + JSON Schema)</strong></summary>

**TypeScript**

```typescript
type BundleActivationRequestBody = ({

  // for key-based licenses:
  license_key: string

} | {

  // for user-based licenses:
  username: string
  password: string

} | {

  // for user-based licenses using single sign-on Implicit grant:
  id_token: string
  customer_account_code: string

} | {

  // for user-based licenses using single sign-on Authorization code grant:
  code: string
  customer_account_code: string

}) & {

  // required properties:
  hardware_id: string
  product: string

  // optional properties:
  bundle_code?: string | undefined
  license_id?: number | undefined
  is_vm?: boolean | undefined
  vm_info?: string | undefined
  os_ver?: string | undefined
  hostname?: string | undefined
  os_hostname?: string | undefined
  ip?: string | undefined
  ip_local?: string | undefined
  app_ver?: string | undefined
  sdk_ver?: string | undefined
  mac_address?: string | undefined
  tpm_enrollment?: { signing_public_key: string } | undefined
  variables?: { [key: string]: string } | undefined
}
```

**JSON Schema**

```json
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "allOf": [
    {
      "oneOf": [
        {
          "type": "object",
          "properties": {
            "license_key": { "type": "string" }
          },
          "required": ["license_key"],
          "additionalProperties": false
        },
        {
          "type": "object",
          "properties": {
            "username": { "type": "string" },
            "password": { "type": "string" }
          },
          "required": ["username", "password"],
          "additionalProperties": false
        },
        {
          "type": "object",
          "properties": {
            "id_token": { "type": "string" },
            "customer_account_code": { "type": "string" }
          },
          "required": ["id_token", "customer_account_code"],
          "additionalProperties": false
        },
        {
          "type": "object",
          "properties": {
            "code": { "type": "string" },
            "customer_account_code": { "type": "string" }
          },
          "required": ["code", "customer_account_code"],
          "additionalProperties": false
        }
      ]
    },
    {
      "type": "object",
      "properties": {
        "hardware_id": { "type": "string" },
        "product": { "type": "string" },
        "bundle_code": { "type": "string" },
        "license_id": { "type": "number" },
        "is_vm": { "type": "boolean" },
        "vm_info": { "type": "string" },
        "os_ver": { "type": "string" },
        "hostname": { "type": "string" },
        "os_hostname": { "type": "string" },
        "ip": { "type": "string" },
        "ip_local": { "type": "string" },
        "app_ver": { "type": "string" },
        "sdk_ver": { "type": "string" },
        "mac_address": { "type": "string" },
        "tpm_enrollment": { 
          "type": "object",
          "properties": {
            "signing_public_key": { "type": "string" },
          },
          "required": ["signing_public_key"],
        },
        "variables": {
          "type": ["object", "null"],
          "additionalProperties": { "type": "string" }
        }
      },
      "required": ["hardware_id", "product"],
      "additionalProperties": false
    }
  ]
}
```

</details>

#### Response Body

<details>

<summary><strong>Response schema (TypeScript + JSON Schema)</strong></summary>

**TypeScript**

```typescript
// Response is a list of activated licenses.
// Each item matches the Activate License (Online) response schema.
type BundleActivationResponseList = LicenseActivationResponseBody[];
```

**JSON Schema**

```json
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "array",
  "items": {
    "type": "object"
  }
}
```

</details>

See [Activate License *Online Method*](/license-api/license-activation-and-deactivation/activate-license-online-method) for the item schema.

### License Authorization Method

There are two types of product licenses based on how the client application authorizes itself to interact with a license:

#### Key-based product licenses

Each license assigned to the product requires the client to provide a license key in order to activate, check or deactivate the license (property `license_key` in the request body).

The license response object will contain the `license_key` property, and the property `product_details.authorization_method` will be the string `license_key`.

#### User-based product licenses

Each license assigned to the product has a corresponding "license user" instead of a license key. There are multiple ways a user-based product license can be accessed when making a request:

* By providing the values for `username` and `password` for the license user
* By providing the values for `id_token` and `customer_account_code` if using single sign-on authentication using Implicit grant
* By providing the values for `code` and `customer_account_code` if using single sign-on authentication using Authorization code grant

In the license response object, `product_details.authorization_method` will have the string value `user` and the response object will contain the `user` object with information on the license user.

### License Types

The `license_type` property defines one of the 4 types of licenses based on their duration and how the license is used:

* Perpetual: `perpetual`
* Time-limited: `time-limited`
* Subscription: `subscription`
* Consumption: `consumption`

For more information see: [**License Types**](/license-entitlements/license-types)

### Errors

If an error occurs, the response will have an HTTP status code of 400 or higher, and the response body will contain an error description in the following format:

```typescript
{
  status: number,
  code: string,
  message: string
}
```

JSON Schema

```json
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "status": { "type": "number" },
    "code": { "type": "string" },
    "message": { "type": "string" }
  },
  "required": [
    "status",
    "code",
    "message"
  ],
  "additionalProperties": false
}
```

### List of Exceptions

> unknown\_product (400): Provided product was not found

> product\_not\_bundle (400): Specified product "code" is not a bundle

> license\_not\_found (400): License with the provided license user not found

> license\_not\_enabled (400): The license is not enabled

> vm\_not\_allowed (400): Virtual machine not allowed

> offline\_floating\_cloud\_not\_supported (400): Floating cloud licenses cannot be activated offline

> license\_start\_date\_error (400): This license cannot be activated before start date: {date}

> blacklisted (400): This device is blacklisted

> license\_activated\_max\_times (400): This license key has already been activated the maximum number of times

> license\_user\_activated\_max\_times (400): License user has activated this license max times

> license\_expired (400): License validity period has expired

> license\_transferred\_max\_times (400): This license has already been transferred the maximum number of times

> license\_device\_exists (409): A device matching this hardware\_id is already created on the license.

> tpm\_not\_enabled (400): This license doesn't have TPM licensing enabled, and tpm\_enrollment is being sent in the request

> tpm\_enrollment\_required (400): This license has TPM licensing enabled, but tpm\_enrollment is not being sent in the request

> tpm\_invalid\_public\_key (400): The TPM public key sent is not valid

> tpm\_signature\_required (400): The TPM signature is missing from the header and TPM usage is enforced

> tpm\_signature\_missmatch (400): The TPM signature/algorithm is not ES256 and the signature can't be verified against the TPM public key


# Activate Bundle (Offline Method)

{% hint style="info" %}
Currently we support trial, perpetual and consumption license types for offline activation purposes.
{% endhint %}

### Endpoint

* Method: `POST`
* Path: `/api/v4/activate_bundle_offline`
* Description: Activates all licenses inside a bundle using the offline activation flow (base64 payload).

### Authentication

See [License API Authorization](/license-api/license-api-authorization).

#### Required headers

* `Date` (string) — RFC7231 GMT date string
* `Authorization` (string) — signature or bearer token

#### Recommended headers

* `Accept: application/json`

### Request

#### Body

The request body is a **base64-encoded, stringified JSON object**.

<details>

<summary><strong>Interactive API reference</strong></summary>

:::ApiMethodV2

```json
{
  "tab": "examples",
  "url": "/api/v4/activate_bundle_offline",
  "name": "/api/v4/activate_bundle_offline",
  "method": "POST",
  "request": {
    "pathParameters": [],
    "queryParameters": [],
    "headerParameters": [
      {
        "kind": "required",
        "name": "Date",
        "type": "string",
        "children": [],
        "description": "In RFC7231 format (see \"License API Authorization\"), eg.: Thu, 17 Nov 2022 20:51:35 GMT"
      },
      {
        "kind": "required",
        "name": "Authorization",
        "type": "string",
        "children": [],
        "description": "See \"License API Authorization\" article"
      }
    ],
    "bodyDataParameters": [
      {
        "name": "base64-encoded JSON object",
        "kind": "required",
        "type": "Object",
        "description": "The request body is a base64-encoded stringified JSON object containing the properties below. See the \"Finalized payload\" section for examples on how to generate this payload",
        "children": [
          {
            "name": "api_key OR client_id",
            "kind": "required",
            "type": "string",
            "description": "Authorization parameter used for the request, either `api_key` if using API key-based authorization, or `client_id` if using SSO authorization"
          },
          {
            "name": "date",
            "kind": "required",
            "type": "string",
            "description": "In RFC7231 format (see \"License API Authorization\"), eg.: Thu, 17 Nov 2022 20:51:35 GMT"
          },
          {
            "name": "request_id",
            "kind": "required",
            "type": "string",
            "description": "Custom unique ID used to identify the request"
          },
          {
            "name": "request",
            "kind": "required",
            "type": "string",
            "description": "Determines the type of request: \"activation\" or \"deactivation\""
          },
          {
            "name": "signature",
            "kind": "required",
            "type": "string",
            "description": "Calculated request signature (see section below)"
          },
          {
            "name": "product",
            "kind": "required",
            "type": "string",
            "description": "Product short code"
          },
          {
            "name": "hardware_id",
            "kind": "required",
            "type": "string",
            "description": "Unique hardware ID generated for the client device"
          },
          {
            "name": "license_key",
            "kind": "optional",
            "type": "string",
            "description": "Required if product is key-based (see section below)"
          },
          {
            "name": "username",
            "kind": "optional",
            "type": "string",
            "description": "Required if product is user based (see section below)"
          },
          {
            "name": "password",
            "kind": "optional",
            "type": "string",
            "description": "Required if product is user based (see section below)"
          },
          {
            "name": "license_id",
            "kind": "optional",
            "type": "Number",
            "description": "Ensures that the action affects only the license with the specified ID. This is useful if e.g. you have multiple licenses for the same product assigned to the same user"
          },
          {
            "name": "os_ver",
            "kind": "optional",
            "type": "string",
            "description": "Operating System description string which will be stored in license device data"
          },
          {
            "name": "hostname",
            "kind": "optional",
            "type": "string",
            "description": "Hostname string which will be stored in license device data"
          },
          {
            "name": "os_hostname",
            "kind": "optional",
            "type": "string",
            "description": "Legacy version of \"hostname\" parameter (see above)"
          },
          {
            "name": "ip",
            "kind": "optional",
            "type": "string",
            "description": "Client IP address string which will be stored in license device data"
          },
          {
            "name": "ip_local",
            "kind": "optional",
            "type": "string",
            "description": "Legacy version of \"ip\" parameter (see above)"
          },
          {
            "name": "app_ver",
            "kind": "optional",
            "type": "string",
            "description": "Application version string which will be stored in license device data"
          },
          {
            "name": "sdk_ver",
            "kind": "optional",
            "type": "string",
            "description": "SDK version string which will be stored in license device data"
          },
          {
            "name": "mac_address",
            "kind": "optional",
            "type": "string",
            "description": "Client MAC address which will be stored in license device data"
          },
          {
            "name": "variables",
            "kind": "optional",
            "type": "Object",
            "description": "Stores device variables. Variables are sent as a JSON object where the key is the variable name, and the value is the value of the variable"
          }
        ],
        "schema": [
          {
            "name": "api_key OR client_id",
            "kind": "required",
            "type": "string",
            "description": "Authorization parameter used for the request, either `api_key` if using API key-based authorization, or `client_id` if using SSO authorization"
          },
          {
            "name": "date",
            "kind": "required",
            "type": "string",
            "description": "In RFC7231 format (see \"License API Authorization\"), eg.: Thu, 17 Nov 2022 20:51:35 GMT"
          },
          {
            "name": "request_id",
            "kind": "required",
            "type": "string",
            "description": "Custom unique ID used to identify the request"
          },
          {
            "name": "request",
            "kind": "required",
            "type": "string",
            "description": "Determines the type of request: \"activation\" or \"deactivation\""
          },
          {
            "name": "signature",
            "kind": "required",
            "type": "string",
            "description": "Calculated request signature (see section below)"
          },
          {
            "name": "product",
            "kind": "required",
            "type": "string",
            "description": "Product short code"
          },
          {
            "name": "hardware_id",
            "kind": "required",
            "type": "string",
            "description": "Unique hardware ID generated for the client device"
          },
          {
            "name": "license_key",
            "kind": "optional",
            "type": "string",
            "description": "Required if product is key-based (see section below)"
          },
          {
            "name": "username",
            "kind": "optional",
            "type": "string",
            "description": "Required if product is user based (see section below)"
          },
          {
            "name": "password",
            "kind": "optional",
            "type": "string",
            "description": "Required if product is user based (see section below)"
          },
          {
            "name": "license_id",
            "kind": "optional",
            "type": "Number",
            "description": "Ensures that the action affects only the license with the specified ID. This is useful if e.g. you have multiple licenses for the same product assigned to the same user"
          },
          {
            "name": "os_ver",
            "kind": "optional",
            "type": "string",
            "description": "Operating System description string which will be stored in license device data"
          },
          {
            "name": "hostname",
            "kind": "optional",
            "type": "string",
            "description": "Hostname string which will be stored in license device data"
          },
          {
            "name": "os_hostname",
            "kind": "optional",
            "type": "string",
            "description": "Legacy version of \"hostname\" parameter (see above)"
          },
          {
            "name": "ip",
            "kind": "optional",
            "type": "string",
            "description": "Client IP address string which will be stored in license device data"
          },
          {
            "name": "ip_local",
            "kind": "optional",
            "type": "string",
            "description": "Legacy version of \"ip\" parameter (see above)"
          },
          {
            "name": "app_ver",
            "kind": "optional",
            "type": "string",
            "description": "Application version string which will be stored in license device data"
          },
          {
            "name": "sdk_ver",
            "kind": "optional",
            "type": "string",
            "description": "SDK version string which will be stored in license device data"
          },
          {
            "name": "mac_address",
            "kind": "optional",
            "type": "string",
            "description": "Client MAC address which will be stored in license device data"
          },
          {
            "name": "variables",
            "kind": "optional",
            "type": "Object",
            "description": "Stores device variables. Variables are sent as a JSON object where the key is the variable name, and the value is the value of the variable"
          }
        ]
      }
    ],
    "formDataParameters": []
  },
  "results": {
    "languages": [
      {
        "id": "es8g1cuKJMbNPiIVt9VoF",
        "code": "",
        "language": "200",
        "customLabel": ""
      },
      {
        "id": "7eyIlKSv2QgJcowEsDZ-2",
        "code": "",
        "language": "400",
        "customLabel": ""
      }
    ],
    "selectedLanguageId": "es8g1cuKJMbNPiIVt9VoF"
  },
  "examples": {
    "languages": [
      {
        "id": "MVswmprxv53Y8L_yhrmmd",
        "language": "curl",
        "code": "curl --location --request POST '/api/v4/activate_bundle_offline' \\\n--header 'Accept: application/json' \\\n--header 'Date: string' \\\n--header 'Authorization: string' \\\n--data-raw '_BASE64_PAYLOAD_HERE_'",
        "customLabel": ""
      },
      {
        "id": "ZCTQO7_LVyOjAVX7pP4kO",
        "language": "nodejs",
        "code": "var request = require('request');\nvar options = {\n   'method': 'POST',\n   'url': '/api/v4/activate_bundle_offline',\n   'headers': {\n      'Accept': 'application/json',\n      'Date': 'string',\n      'Authorization': 'string'\n   },\n   body: Buffer.from(JSON.stringify(offline_payload)).toString('base64')\n};\n\nrequest(options, function (error, response) {\n   if (error) throw new Error(error);\n   console.log(response.body);\n});\n",
        "customLabel": ""
      },
      {
        "id": "LI5XFmSVTa4QEJC9tahg2",
        "language": "javascript",
        "code": "var myHeaders = new Headers();\nmyHeaders.append(\"Accept\", \"application/json\");\nmyHeaders.append(\"Date\", \"string\");\nmyHeaders.append(\"Authorization\", \"string\");\n\nvar requestOptions = {\n   method: 'POST',\n   headers: myHeaders,\n   body: btoa(JSON.stringify(offline_payload)),\n   redirect: 'follow'\n};\n\nfetch(\"/api/v4/activate_bundle_offline\", requestOptions)\n   .then(response => response.text())\n   .then(result => console.log(result))\n   .catch(error => console.log('error', error));",
        "customLabel": ""
      },
      {
        "id": "cmQNdWH3b3clWrX7G_IxJ",
        "language": "python",
        "code": "import requests\n\nurl = \"/api/v4/activate_bundle_offline\"\n\npayload = \"{\\\"Payload\\\":\\\"Object\\\"}\"\nheaders = {\n   'Accept': 'application/json',\n   'Date': 'string',\n   'Authorization': 'string'\n}\n\nresponse = requests.request(\"POST\", url, headers=headers, data=payload)\n\nprint(response.text)\n",
        "customLabel": ""
      },
      {
        "id": "i6V2Wf-nI00sMv9ahE5rB",
        "language": "ruby",
        "code": "require \"uri\"\nrequire \"net/http\"\n\nurl = URI(\"/api/v4/activate_bundle_offline\")\n\nhttp = Net::HTTP.new(url.host, url.port);\nrequest = Net::HTTP::Post.new(url)\nrequest[\"Accept\"] = \"application/json\"\nrequest[\"Date\"] = \"string\"\nrequest[\"Authorization\"] = \"string\"\nrequest.body = \"{\\\"Payload\\\":\\\"Object\\\"}\"\n\nresponse = http.request(request)\nputs response.read_body\n",
        "customLabel": ""
      }
    ],
    "selectedLanguageId": "i6V2Wf-nI00sMv9ahE5rB"
  },
  "description": "Activates all licenses inside of a bundle using the offline activation method. Returns the list of activated licenses in response\n",
  "currentNewParameter": {
    "label": "Body Parameter",
    "value": "bodyDataParameters"
  }
}
```

:::

</details>

### Schema

#### Request Body

The request body is a string representing a base64-encoded JSON object containing all the required activation data.

{% hint style="danger" %}
If using `multipart/form-data`, the `file` form parameter is mandatory.
{% endhint %}

<details>

<summary><strong>Request schema (TypeScript + JSON Schema)</strong></summary>

**TypeScript**

```typescript
type LicenseOfflineActivationObject = ({

  // for key-based licenses:
  license_key: string

} | {

  // for user-based licenses:
  username: string
  password: string

}) & {

  // required properties:
  hardware_id: string
  product: string
  request_id: string
  signature: string
  date: string
  request: "activation"

} & ({
  api_key: string // for API key authorization
} | {
  client_id: string // for OAuth authorization
}) & {

  // optional properties:
  bundle_code?: string | undefined
  license_id?: number | undefined
  is_vm?: boolean | undefined
  vm_info?: string | undefined
  os_ver?: string | undefined
  hostname?: string | undefined
  os_hostname?: string | undefined
  ip?: string | undefined
  ip_local?: string | undefined
  app_ver?: string | undefined
  sdk_ver?: string | undefined
  mac_address?: string | undefined
  licensespring-tpm-signature?: string | undefined
  tpm_enrollment?: { signing_public_key: string } | undefined
  variables?: { [key: string]: string } | undefined
}
```

**JSON Schema**

```json
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "allOf": [
    {
      "oneOf": [
        {
          "type": "object",
          "properties": {
            "license_key": { "type": "string" }
          },
          "required": ["license_key"],
          "additionalProperties": false
        },
        {
          "type": "object",
          "properties": {
            "username": { "type": "string" },
            "password": { "type": "string" }
          },
          "required": ["username", "password"],
          "additionalProperties": false
        }
      ]
    },
    {
      "type": "object",
      "properties": {
        "api_key": { "type": "string" },
        "client_id": { "type": "string" },
        "request": { "type": "string" },
        "request_id": { "type": "string" },
        "date": { "type": "string" },
        "signature": { "type": "string" },
        "licensespring-tpm-signature": { "type": "string" },
        "hardware_id": { "type": "string" },
        "product": { "type": "string" },
        "bundle_code": { "type": "string" },
        "license_id": { "type": "number" },
        "is_vm": { "type": "boolean" },
        "vm_info": { "type": "string" },
        "os_ver": { "type": "string" },
        "hostname": { "type": "string" },
        "os_hostname": { "type": "string" },
        "ip": { "type": "string" },
        "ip_local": { "type": "string" },
        "app_ver": { "type": "string" },
        "sdk_ver": { "type": "string" },
        "mac_address": { "type": "string" },
        "tpm_enrollment": { 
          "type": "object",
          "properties": {
            "signing_public_key": { "type": "string" },
          },
          "required": ["signing_public_key"],
        },
        "variables": {
          "type": "object",
          "additionalProperties": { "type": "string" }
        }
      },
     "allOf": [
        { "anyOf": [{ "required": ["api_key"] }, { "required": ["client_id"] }] },
        { "anyOf": [{ "required": ["license_key"] }, { "required": ["username"] }] }
      ],
      "required": ["hardware_id", "product", "date", "signature"],
      "additionalProperties": false
    }
  ]
}
```

</details>

#### Signature

The `signature` value is constructed as follows:

{% stepper %}
{% step %}

### Build the signing string — part 1

Start with the literal string: licenseSpring then a newline.
{% endstep %}

{% step %}

### Build the signing string — part 2

Append: date: then a newline.
{% endstep %}

{% step %}

### Build the signing string — part 3

Append either the license identifier (whichever is present for the request):

* the "license\_key" value, or
* the "username" value then a newline.
  {% endstep %}

{% step %}

### Build the signing string — part 4

Append the "hardware\_id" value, then a newline.
{% endstep %}

{% step %}

### Build the signing string — part 5

Append the authorization identifier:

* the "api\_key" value (if using API key auth), or
* the "client\_id" value (if using OAuth) This completes the signing string.
  {% endstep %}

{% step %}

### Create HMAC

Encrypt the complete signing string using HMAC-SHA256 with the company signing key:

* If using API key authorization: signing key = Shared Key
* If using OAuth: signing key = Client Secret

Example (Node.js):
{% endstep %}
{% endstepper %}

```javascript
import crypto from 'node:crypto';

const activationPayload = {
  // ...payload content...
};

// api_key or client_id depending on authorization type used:
const key = (activationPayload.api_key || activationPayload.client_id);

// if using API key authorization: the signing key is the Shared Key
// if using OAuth: the signing key is the Client Secret
const signingKey = '...';

const signingString =
  'licenseSpring\n' +
  'date: ' + activationPayload.date + '\n' +
  (activationPayload.license_key || activationPayload.username) + '\n' +
  activationPayload.hardware_id + '\n' +
  key;

const signature = crypto
  .createHmac('sha256', signingKey)
  .update(signingString)
  .digest('base64');
```

#### Finalized payload

This object is then stringified and encoded to base64.

{% tabs %}
{% tab title="JS Browser" %}

```javascript
const activationPayload = {
  // ...payload content...
};
const requestBody = btoa(JSON.stringify(activationPayload));
```

{% endtab %}

{% tab title="nodeJS" %}

```javascript
const activationPayload = {
  // ...payload content...
};
const requestBody = Buffer.from(JSON.stringify(activationPayload)).toString('base64');
```

{% endtab %}
{% endtabs %}

#### Response Body

<details>

<summary><strong>Response schema (TypeScript + JSON Schema)</strong></summary>

**TypeScript**

```typescript
type BundleActivationResponseBody = {
  licenses: LicenseActivationResponse[],
  bundle_signature_v2: string,
};

type LicenseActivationResponse = {
  id: number,
  allow_grace_period: boolean,
  allow_overages: boolean,
  allow_unlimited_activations: boolean,
  borrowed_until: string | null,
  can_borrow: boolean,
  channel: string,
  device_id: number,
  enable_maintenance_period: boolean
  environment: string,
  eula_link: string,
  floating_timeout: number,
  grace_period: number,
  hash_md5: string,
  installation_file: string,
  is_air_gapped: boolean,
  is_borrowed: boolean,
  is_expired: boolean,
  is_floating_cloud: boolean,
  is_floating: boolean,
  is_hardware_key_auth: boolean,
  is_tpm_auth: boolean,
  license_active: boolean,
  license_enabled: boolean,
  license_signature: string,
  license_signature_v2: string,
  offline_signature: string,
  license_type: string,
  maintenance_period: string | null,
  max_activations: number,
  max_borrow_time: number,
  max_license_users: number,
  max_overages: number,
  max_transfers: number,
  order_store_id: string,
  prevent_vm: boolean,
  release_date: string,
  release_notes_link: string,
  requires_version: string,
  size: string,
  start_date: string | null,
  times_activated: number,
  transfer_count: number,
  validity_period: string | null, // string is in DateISO8601UTC format
  version: string,
  company: { id: number },
  
  product_features: ({
    id: number,
    code: string,
    name: string,
    expiry_date: string,
    metadata: JSON,
    feature_type: 'activation' | 'consumption',
    is_floating: boolean,
    is_floating_cloud: boolean,

    // the following properties are only present if is_floating=true or is_floating_cloud=true
    floating_users: number,
    floating_timeout: number,

    // the following properties are only present if feature_type=consumption
    max_consumption: number,
    allow_unlimited_consumptions: boolean,
    total_consumptions: number,
    allow_overages: number,
    max_overages: number,
    reset_consumption: boolean,
    consumption_period: 'daily' | 'weekly' | 'monthly' | 'annualy' | null,
  })[],
  
  custom_fields: ({
    name: string,
    data_type: 'numer' | 'text' | 'date/time',
    value: string,
  })[],
  
  customer: {
    email: string,
    company_name: string,
    reference: string,
    phone: string,
    first_name: string,
    last_name: string,
    city: string,
    postcode: string,
    state: string,
    country: string,
    address: string,
    customer_account: string | null,
    metadata: JSON,
  },
  
  product_details: {
    product_id: number,
    product_name: string,
    short_code: string,
    authorization_method: 'license-key' | 'user',
    metadata: JSON,
  },
  
  metadata: JSON,
  
  // the following property is only present if is_trial=true
  trial_days: number,
  
  // the following properties is only present if is_floating=true or is_floating_cloud=true
  floating_in_use_devices: number,
  floating_users: number,
  
  // the following properties are only present if license_type='consumption'
  max_consumptions: number,
  total_consumptions: number,
  allow_unlimited_consumptions: boolean,
  reset_consumption: boolean,
  consumption_period: string | null, // string is in DateISO8601UTC format
  
} & ({

  // for key-based licenses:
  license_key: string,

} | {

  // for user-based licenses:
  user: {
    id: number,
    email: string,
    first_name: string,
    last_name: string,
    phone_number: string,
    is_initial_password: boolean,
    max_activations: number,
    allow_unlimited_activations: boolean,
    total_activations: number
  }
});
```

**JSON Schema**

```json
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "bundle_signature_v2": {
      "type": "string"
    },
    "licenses": {
      "type": "array",
      "items": {
        "type": "object",
        "allOf": [
          {
            "oneOf": [
              {
                "type": "object",
                "properties": {
                  "license_key": {
                    "type": "string"
                  }
                },
                "required": [
                  "license_key"
                ],
                "additionalProperties": false
              },
              {
                "type": "object",
                "properties": {
                  "user": {
                    "type": "object",
                    "properties": {
                      "id": {
                        "type": "number"
                      },
                      "email": {
                        "type": "string"
                      },
                      "first_name": {
                        "type": "string"
                      },
                      "last_name": {
                        "type": "string"
                      },
                      "phone_number": {
                        "type": "string"
                      },
                      "is_initial_password": {
                        "type": "boolean"
                      },
                      "max_activations": {
                        "type": "number"
                      },
                      "allow_unlimited_activations": {
                        "type": "boolean"
                      },
                      "total_activations": {
                        "type": "number"
                      }
                    },
                    "required": [
                      "id",
                      "email",
                      "first_name",
                      "last_name",
                      "phone_number",
                      "is_initial_password",
                      "max_activations",
                      "allow_unlimited_activations",
                      "total_activations"
                    ],
                    "additionalProperties": false
                  }
                },
                "required": [
                  "user"
                ],
                "additionalProperties": false
              }
            ]
          },
          {
            "type": "object",
            "properties": {
              "id": {
                "type": "number"
              },
              "allow_grace_period": {
                "type": "boolean"
              },
              "allow_overages": {
                "type": "boolean"
              },
              "allow_unlimited_activations": {
                "type": "boolean"
              },
              "borrowed_until": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "can_borrow": {
                "type": "boolean"
              },
              "channel": {
                "type": "string"
              },
              "device_id": {
                "type": "number"
              },
              "enable_maintenance_period": {
                "type": "boolean"
              },
              "environment": {
                "type": "string"
              },
              "eula_link": {
                "type": "string"
              },
              "floating_timeout": {
                "type": "number"
              },
              "grace_period": {
                "type": "number"
              },
              "hash_md5": {
                "type": "string"
              },
              "installation_file": {
                "type": "string"
              },
              "is_air_gapped": {
                "type": "boolean"
              },
              "is_borrowed": {
                "type": "boolean"
              },
              "is_expired": {
                "type": "boolean"
              },
              "is_floating_cloud": {
                "type": "boolean"
              },
              "is_floating": {
                "type": "boolean"
              },
              "is_hardware_key_auth": {
                "type": "boolean"
              },
              "is_tpm_auth": {
                "type": "boolean"
              },
              "license_active": {
                "type": "boolean"
              },
              "license_enabled": {
                "type": "boolean"
              },
              "license_signature": {
                "type": "string"
              },
              "license_signature_v2": {
                "type": "string"
              },
              "offline_signature": {
                "type": "string"
              },
              "license_type": {
                "type": "string"
              },
              "maintenance_period": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "max_activations": {
                "type": "number"
              },
              "max_borrow_time": {
                "type": "number"
              },
              "max_license_users": {
                "type": "number"
              },
              "max_overages": {
                "type": "number"
              },
              "max_transfers": {
                "type": "number"
              },
              "order_store_id": {
                "type": "string"
              },
              "prevent_vm": {
                "type": "boolean"
              },
              "release_date": {
                "type": "string"
              },
              "release_notes_link": {
                "type": "string"
              },
              "requires_version": {
                "type": "string"
              },
              "size": {
                "type": "string"
              },
              "start_date": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "times_activated": {
                "type": "number"
              },
              "transfer_count": {
                "type": "number"
              },
              "validity_period": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "version": {
                "type": "string"
              },
              "company": {
                "type": "object",
                "properties": {
                  "id": {
                    "type": "number"
                  }
                }
              },
              "product_features": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "number"
                    },
                    "code": {
                      "type": "string"
                    },
                    "name": {
                      "type": "string"
                    },
                    "expiry_date": {
                      "type": "string"
                    },
                    "metadata": {},
                    "feature_type": {
                      "type": "string",
                      "enum": [
                        "activation",
                        "consumption"
                      ]
                    },
                    "is_floating": {
                      "type": "boolean"
                    },
                    "is_floating_cloud": {
                      "type": "boolean"
                    },
                    "floating_users": {
                      "type": "number"
                    },
                    "floating_timeout": {
                      "type": "number"
                    },
                    "max_consumption": {
                      "type": "number"
                    },
                    "allow_unlimited_consumptions": {
                      "type": "boolean"
                    },
                    "total_consumptions": {
                      "type": "number"
                    },
                    "allow_overages": {
                      "type": "boolean"
                    },
                    "max_overages": {
                      "type": "number"
                    },
                    "reset_consumption": {
                      "type": "boolean"
                    },
                    "consumption_period": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "enum": [
                        "daily",
                        "weekly",
                        "monthly",
                        "annualy",
                        null
                      ]
                    }
                  },
                  "required": [
                    "id",
                    "code",
                    "name",
                    "expiry_date",
                    "metadata",
                    "feature_type",
                    "is_floating",
                    "is_floating_cloud"
                  ]
                }
              },
              "custom_fields": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "name": {
                      "type": "string"
                    },
                    "data_type": {
                      "type": "string",
                      "enum": [
                        "numer",
                        "text",
                        "date/time"
                      ]
                    },
                    "value": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "name",
                    "data_type",
                    "value"
                  ]
                }
              },
              "customer": {
                "type": "object",
                "properties": {
                  "email": {
                    "type": "string"
                  },
                  "company_name": {
                    "type": "string"
                  },
                  "reference": {
                    "type": "string"
                  },
                  "phone": {
                    "type": "string"
                  },
                  "first_name": {
                    "type": "string"
                  },
                  "last_name": {
                    "type": "string"
                  },
                  "city": {
                    "type": "string"
                  },
                  "postcode": {
                    "type": "string"
                  },
                  "state": {
                    "type": "string"
                  },
                  "country": {
                    "type": "string"
                  },
                  "address": {
                    "type": "string"
                  },
                  "customer_account": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "metadata": {}
                },
                "required": [
                  "email",
                  "company_name",
                  "reference",
                  "phone",
                  "first_name",
                  "last_name",
                  "city",
                  "postcode",
                  "state",
                  "country",
                  "address",
                  "metadata"
                ]
              },
              "product_details": {
                "type": "object",
                "properties": {
                  "product_id": {
                    "type": "number"
                  },
                  "product_name": {
                    "type": "string"
                  },
                  "short_code": {
                    "type": "string"
                  },
                  "authorization_method": {
                    "type": "string",
                    "enum": [
                      "license-key",
                      "user"
                    ]
                  },
                  "metadata": {}
                },
                "required": [
                  "product_id",
                  "product_name",
                  "short_code",
                  "authorization_method",
                  "metadata"
                ]
              },
              "metadata": {},
              "trial_days": {
                "type": "number"
              },
              "floating_in_use_devices": {
                "type": "number"
              },
              "floating_users": {
                "type": "number"
              },
              "max_consumptions": {
                "type": "number"
              },
              "total_consumptions": {
                "type": "number"
              },
              "allow_unlimited_consumptions": {
                "type": "boolean"
              },
              "reset_consumption": {
                "type": "boolean"
              },
              "consumption_period": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "id",
              "allow_grace_period",
              "allow_overages",
              "allow_unlimited_activations",
              "can_borrow",
              "channel",
              "device_id",
              "enable_maintenance_period",
              "environment",
              "eula_link",
              "floating_timeout",
              "grace_period",
              "hash_md5",
              "installation_file",
              "is_air_gapped",
              "is_borrowed",
              "is_expired",
              "is_floating_cloud",
              "is_floating",
              "is_hardware_key_auth",
              "is_tpm_auth",
              "license_active",
              "license_enabled",
              "license_signature",
              "license_signature_v2",
              "offline_signature",
              "license_type",
              "max_activations",
              "max_borrow_time",
              "max_license_users",
              "max_overages",
              "max_transfers",
              "order_store_id",
              "prevent_vm",
              "release_date",
              "release_notes_link",
              "requires_version",
              "size",
              "times_activated",
              "transfer_count",
              "version",
              "product_features",
              "custom_fields",
              "customer",
              "product_details",
              "metadata"
            ],
            "additionalProperties": false
          }
        ]
      }
    }
  }
}
```

</details>

#### Response Signature

The response objects two digital signatures which the client app can use to verify the authenticty of the response:

1. `license_signature` is the HMAC-SHA256 signature explained in the [Response Signature](/license-api/license-api-authorization/response-signature) article.
2. `offline_signature` is an additional HMAC-SHA256 signature specific to this endpoint. This value is constructed identically to the `signature` parameter in the request payload (see [Signature](#signature) above) using the `date` value received in the response.

### License Authorization Method

There are two types of product licenses based on how the client application authorizes itself to interact with a license:

#### Key-based product licenses

Each license assigned to the product requires the client to provide a license key in order to activate, check or deactivate the license (property `license_key` in the request body).

The license response object will contain the `license_key` property, and the property `product_details.authorization_method` will be the string `license_key`.

#### User-based product licenses

Each license assigned to the product has a corresponding "license user" instead of a license key. Requires `username` and `password` to be provided.

In the license response object, `product_details.authorization_method` will have the string value `user` and the response object will contain the `user` object with information on the license user.

### License Types

The `license_type` property defines one of the 4 types of licenses based on their duration and how the license is used:

* Perpetual: `perpetual`
* Time-limited: `time-limited`
* Subscription: `subscription`
* Consumption: `consumption`

For more information see: [License Types](/license-entitlements/license-types)

### Device variables

The optional `variables` parameter lets you set device variables during activation. For more information, see [Device Variables](/license-api/device-variables).

### Errors

If an error occurs, the response will have an HTTP status code of 400 or higher, and the response body will contain an error description in the following format:

```typescript
{
  status: number,
  code: string,
  message: string
}
```

JSON Schema

```json
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "status": { "type": "number" },
    "code": { "type": "string" },
    "message": { "type": "string" }
  },
  "required": [
    "status",
    "code",
    "message"
  ],
  "additionalProperties": false
}
```

#### List of exceptions

> **missing\_headers** (400): some headers are missing
>
> * when missing authorization or date headers

> **missing\_parameters** (400): some parameters are missing in the request
>
> * when no request body at all or no file found in request body

> **authorization\_missing\_params** (400): some parameters are missing in authorization
>
> * when has request body but request body is not properly base 64 encoded
> * when file is missing in request body
> * when license\_key or hardware\_id body parameters are missing
> * when data body parameter is missing
> * when api\_key parameter is missing

> **product\_not\_bundle** (400): Specified product "{CODE}" is not a bundle

> tpm\_not\_enabled (400): This license doesn't have TPM licensing enabled, and tpm\_enrollment is being sent in the request

> tpm\_enrollment\_required (400): This license has TPM licensing enabled, but tpm\_enrollment is not being sent in the request

> tpm\_invalid\_public\_key (400): The TPM public key sent is not valid

> tpm\_signature\_required (400): The TPM signature is missing from the body/license file and TPM usage is enforced

> tpm\_signature\_missmatch (400): The TPM signature/algorithm is not ES256 and the signature can't be verified against the TPM public key

{% hint style="info" %}
If you want to use this API endpoint directly, instead of using an SDK (which does most of the heavy lifting), contact us for additional instructions.
{% endhint %}

### Guide on using Offline Licenses

If any aspect of the offline licensing model remains unclear or raises questions, we encourage you to explore our in-depth guide on [link](https://docs.licensespring.com/license-entitlements/activation-types/offline)

This resource offers a comprehensive explanation of the offline licensing process, how it operates, and ways to utilize its advantages for your specific software applications.


# Deactivate License (Online Method)

### Endpoint

* Method: `POST`
* Path: `/api/v4/deactivate_license`
* Description: Deactivates a license for a device and returns `license_deactivated` on success.

### Authentication

See [License API Authorization](/license-api/license-api-authorization).

#### Required headers

* `Date` (string) — RFC7231 GMT date string
* `Authorization` (string) — signature or bearer token

#### Recommended headers

* `Accept: application/json`
* `Content-Type: application/json`

#### Optional headers

* `licensespring-tpm-signature`  (string) — TPM licensing signature and algorithm
  * `licensespring-tpm-signature: algorithm="ES256",signature=""`

### Request

#### Body parameters

Required:

* `hardware_id` (string)
* `product` (string)

One of:

* `license_key` (string)
* `username` (+ `password` when applicable)
* `id_token` (use access\_token value) + `customer_account_code`
* `code` + `customer_account_code`

Optional:

* `license_id` (number)
* `sdk_ver` (string)
* `consumptions` (number)

### Examples

{% tabs %}
{% tab title="curl" %}

```bash
curl --location --request POST '/api/v4/deactivate_license' \
--header 'Accept: application/json' \
--header 'Content-Type: application/json' \
--header 'Date: string' \
--header 'Authorization: string' \
--data-raw '{
  "hardware_id": "some-unique-id",
  "product": "XY",
  "license_key": "AAAA-BBBB-CCCC-DDDD"
}'
```

{% endtab %}

{% tab title="nodejs" %}

```javascript
var request = require('request');
var options = {
  method: 'POST',
  url: '/api/v4/deactivate_license',
  headers: {
    'Accept': 'application/json',
    'Content-Type': 'application/json',
    'Date': 'string',
    'Authorization': 'string'
  },
  body: JSON.stringify({
    hardware_id: 'some-unique-id',
    product: 'XY',
    license_key: 'AAAA-BBBB-CCCC-DDDD'
  })
};
request(options, function (error, response) {
  if (error) throw new Error(error);
  console.log(response.body);
});
```

{% endtab %}

{% tab title="javascript (fetch)" %}

```javascript
var myHeaders = new Headers();
myHeaders.append("Accept", "application/json");
myHeaders.append("Content-Type", "application/json");
myHeaders.append("Date", "string");
myHeaders.append("Authorization", "string");

var raw = JSON.stringify({
  hardware_id: "some-unique-id",
  product: "XY",
  license_key: "AAAA-BBBB-CCCC-DDDD"
});

var requestOptions = {
  method: 'POST',
  headers: myHeaders,
  body: raw,
  redirect: 'follow'
};

fetch("/api/v4/deactivate_license", requestOptions)
  .then(response => response.text())
  .then(result => console.log(result))
  .catch(error => console.log('error', error));
```

{% endtab %}

{% tab title="python" %}

```python
import requests

url = "/api/v4/deactivate_license"

payload = {
  "hardware_id": "some-unique-id",
  "product": "XY",
  "license_key": "AAAA-BBBB-CCCC-DDDD"
}

headers = {
  "Accept": "application/json",
  "Content-Type": "application/json",
  "Date": "string",
  "Authorization": "string"
}

response = requests.post(url, json=payload, headers=headers)
print(response.text)
```

{% endtab %}

{% tab title="ruby" %}

```ruby
require "uri"
require "net/http"
require "json"

url = URI("/api/v4/deactivate_license")

http = Net::HTTP.new(url.host, url.port)
request = Net::HTTP::Post.new(url)
request["Accept"] = "application/json"
request["Content-Type"] = "application/json"
request["Date"] = "string"
request["Authorization"] = "string"
request.body = JSON.dump({
  hardware_id: "some-unique-id",
  product: "XY",
  license_key: "AAAA-BBBB-CCCC-DDDD"
})

response = http.request(request)
puts response.read_body
```

{% endtab %}
{% endtabs %}

<details>

<summary><strong>Interactive API reference</strong></summary>

:::ApiMethodV2

```json
{
  "tab": "examples",
  "url": "/api/v4/deactivate_license",
  "name": "/api/v4/deactivate_license",
  "method": "POST",
  "request": {
    "pathParameters": [],
    "queryParameters": [],
    "headerParameters": [
      {
        "kind": "required",
        "name": "Date",
        "type": "string",
        "children": [],
        "description": "In RFC7231 format (see \"License API Authorization\"), eg.: Thu, 17 Nov 2022 20:51:35 GMT"
      },
      {
        "kind": "required",
        "name": "Authorization",
        "type": "string",
        "children": [],
        "description": "See \"License API Authorization\" article"
      }
    ],
    "bodyDataParameters": [
      {
        "kind": "required",
        "name": "hardware_id",
        "type": "String",
        "children": [],
        "description": "Unique hardware ID generated for the client device"
      },
      {
        "kind": "required",
        "name": "product",
        "type": "string",
        "children": [],
        "description": "Product short code"
      },
      {
        "kind": "optional",
        "name": "license_key",
        "type": "string",
        "children": [],
        "description": "Required if product is key-based (see section below)"
      },
      {
        "kind": "optional",
        "name": "username",
        "type": "string",
        "children": [],
        "description": "Required if product is user based (see section below)"
      },
      {
        "kind": "optional",
        "name": "id_token",
        "type": "string",
        "children": [],
        "description": "Required if using SSO (see section below). This is the id_token received from the SSO if Implicit grant authorization method is being used."
      },
      {
        "kind": "optional",
        "name": "code",
        "type": "string",
        "children": [],
        "description": "Required if using SSO (see section below). This is the code received from the SSO if Authorization code grant method is being used."
      },
      {
        "kind": "optional",
        "name": "customer_account_code",
        "type": "string",
        "children": [],
        "description": "Required if using SSO (see section below). This is the customer_account_code to which SSO provider is configured. "
      },
      {
        "name": "license_id",
        "kind": "optional",
        "type": "Number",
        "description": "Ensures that the action affects only the license with the specified ID. This is useful if e.g. you have multiple licenses for the same product assigned to the same user. For obtaining a list of licenses assigned to a user, see \"User Licenses\"",
        "children": []
      },
      {
        "kind": "optional",
        "name": "sdk_ver",
        "type": "string",
        "description": "Version of the SDK used",
        "": "Version of the SDK used"
      },
      {
        "name": "consumptions",
        "kind": "optional",
        "type": "number",
        "description": "Increases/decreases total_consumptions",
        "": "Increases/decreases total_consumptions"
      }
    ],
    "formDataParameters": []
  },
  "results": {
    "languages": [
      {
        "id": "es8g1cuKJMbNPiIVt9VoF",
        "code": "license_deactivated",
        "language": "200",
        "customLabel": ""
      },
      {
        "id": "7eyIlKSv2QgJcowEsDZ-2",
        "code": "{\n    \"unknown_product\": \"Provided product was not found\",\n    \"license_not_found\": \"License with the provided license user not found\",\n    \"license_not_enabled\": \"The license is not enabled\",\n    \"vm_not_allowed\": \"Virtual machine not allowed\",\n    \"offline_floating_cloud_not_supported\": \" Floating cloud licenses cannot be activated offline.\",\n    \"license_start_date_error\": \"This license cannot be activated before start date: {date}\",\n    \"blacklisted\": \"This device is blacklisted\",\n    \"license_activated_max_times\": \"This license  has already been activated the maximum number of times\",\n    \"license_user_activated_max_times\": \"License user has activated this license max times\",\n    \"license_expired\": \"License validity period has expired.\",\n    \"license_transferred_max_times\": \"This license has already been transferred the maximum number of times\",\n    \"license_device_exists\": \"A device matching this hardware_id is already created on the license\"\n}",
        "language": "400",
        "customLabel": ""
      }
    ],
    "selectedLanguageId": "7eyIlKSv2QgJcowEsDZ-2"
  },
  "examples": {
    "languages": [
      {
        "id": "xgX5uw4vq7BJNbqvQ_ia0",
        "language": "curl",
        "code": "curl --location --request POST '/api/v4/deactivate_license' \\\n--header 'Accept: application/json' \\\n--header 'Date: string' \\\n--header 'Authorization: string' \\\n--data-raw '{\"hardware_id\":\"String\",\"product\":\"string\",\"license_key\":\"string\",\"username\":\"string\",\"password\":\"string\",\"id_token\":\"string\",\"code\":\"string\",\"customer_account_code\":\"string\",\"is_vm\":\"Boolean\",\"vm_info\":\"string\",\"os_ver\":\"string\",\"hostname\":\"string\",\"os_hostname\":\"string\",\"ip\":\"string\",\"ip_local\":\"string\",\"app_ver\":\"string\",\"sdk_ver\":\"string\",\"mac_address\":\"string\"}'",
        "customLabel": ""
      },
      {
        "id": "3Fz0Q_qaZA0RnhG5k5R_W",
        "language": "nodejs",
        "code": "var request = require('request');\nvar options = {\n   'method': 'POST',\n   'url': '/api/v4/deactivate_license',\n   'headers': {\n      'Accept': 'application/json',\n      'Date': 'string',\n      'Authorization': 'string'\n   },\n   body: '{\"hardware_id\":\"String\",\"product\":\"string\",\"license_key\":\"string\",\"username\":\"string\",\"password\":\"string\",\"id_token\":\"string\",\"code\":\"string\",\"customer_account_code\":\"string\",\"is_vm\":\"Boolean\",\"vm_info\":\"string\",\"os_ver\":\"string\",\"hostname\":\"string\",\"os_hostname\":\"string\",\"ip\":\"string\",\"ip_local\":\"string\",\"app_ver\":\"string\",\"sdk_ver\":\"string\",\"mac_address\":\"string\"}'\n\n};\nrequest(options, function (error, response) {\n   if (error) throw new Error(error);\n   console.log(response.body);\n});\n",
        "customLabel": ""
      },
      {
        "id": "uJvCS-wF1cAIBTUqKJTug",
        "language": "javascript",
        "code": "var myHeaders = new Headers();\nmyHeaders.append(\"Accept\", \"application/json\");\nmyHeaders.append(\"Date\", \"string\");\nmyHeaders.append(\"Authorization\", \"string\");\n\nvar raw = \"{\\\"hardware_id\\\":\\\"String\\\",\\\"product\\\":\\\"string\\\",\\\"license_key\\\":\\\"string\\\",\\\"username\\\":\\\"string\\\",\\\"password\\\":\\\"string\\\",\\\"id_token\\\":\\\"string\\\",\\\"code\\\":\\\"string\\\",\\\"customer_account_code\\\":\\\"string\\\",\\\"is_vm\\\":\\\"Boolean\\\",\\\"vm_info\\\":\\\"string\\\",\\\"os_ver\\\":\\\"string\\\",\\\"hostname\\\":\\\"string\\\",\\\"os_hostname\\\":\\\"string\\\",\\\"ip\\\":\\\"string\\\",\\\"ip_local\\\":\\\"string\\\",\\\"app_ver\\\":\\\"string\\\",\\\"sdk_ver\\\":\\\"string\\\",\\\"mac_address\\\":\\\"string\\\"}\";\n\nvar requestOptions = {\n   method: 'POST',\n   headers: myHeaders,\n   body: raw,\n   redirect: 'follow'\n};\n\nfetch(\"/api/v4/deactivate_license\", requestOptions)\n   .then(response => response.text())\n   .then(result => console.log(result))\n   .catch(error => console.log('error', error));",
        "customLabel": ""
      },
      {
        "id": "oHvG2_KC9rAafj9m1qUOF",
        "language": "python",
        "code": "import requests\n\nurl = \"/api/v4/deactivate_license\"\n\npayload = \"{\\\"hardware_id\\\":\\\"String\\\",\\\"product\\\":\\\"string\\\",\\\"license_key\\\":\\\"string\\\",\\\"username\\\":\\\"string\\\",\\\"password\\\":\\\"string\\\",\\\"id_token\\\":\\\"string\\\",\\\"code\\\":\\\"string\\\",\\\"customer_account_code\\\":\\\"string\\\",\\\"is_vm\\\":\\\"Boolean\\\",\\\"vm_info\\\":\\\"string\\\",\\\"os_ver\\\":\\\"string\\\",\\\"hostname\\\":\\\"string\\\",\\\"os_hostname\\\":\\\"string\\\",\\\"ip\\\":\\\"string\\\",\\\"ip_local\\\":\\\"string\\\",\\\"app_ver\\\":\\\"string\\\",\\\"sdk_ver\\\":\\\"string\\\",\\\"mac_address\\\":\\\"string\\\"}\"\nheaders = {\n   'Accept': 'application/json',\n   'Date': 'string',\n   'Authorization': 'string'\n}\n\nresponse = requests.request(\"POST\", url, headers=headers, data=payload)\n\nprint(response.text)\n",
        "customLabel": ""
      },
      {
        "id": "urxE_DgGNxfjEo27HuGSl",
        "language": "ruby",
        "code": "require \"uri\"\nrequire \"net/http\"\n\nurl = URI(\"/api/v4/deactivate_license\")\n\nhttp = Net::HTTP.new(url.host, url.port);\nrequest = Net::HTTP::Post.new(url)\nrequest[\"Accept\"] = \"application/json\"\nrequest[\"Date\"] = \"string\"\nrequest[\"Authorization\"] = \"string\"\nrequest.body = \"{\\\"hardware_id\\\":\\\"String\\\",\\\"product\\\":\\\"string\\\",\\\"license_key\\\":\\\"string\\\",\\\"username\\\":\\\"string\\\",\\\"password\\\":\\\"string\\\",\\\"id_token\\\":\\\"string\\\",\\\"code\\\":\\\"string\\\",\\\"customer_account_code\\\":\\\"string\\\",\\\"is_vm\\\":\\\"Boolean\\\",\\\"vm_info\\\":\\\"string\\\",\\\"os_ver\\\":\\\"string\\\",\\\"hostname\\\":\\\"string\\\",\\\"os_hostname\\\":\\\"string\\\",\\\"ip\\\":\\\"string\\\",\\\"ip_local\\\":\\\"string\\\",\\\"app_ver\\\":\\\"string\\\",\\\"sdk_ver\\\":\\\"string\\\",\\\"mac_address\\\":\\\"string\\\"}\"\n\nresponse = http.request(request)\nputs response.read_body\n",
        "customLabel": ""
      }
    ],
    "selectedLanguageId": "uJvCS-wF1cAIBTUqKJTug"
  },
  "description": "Dectivate a license for a product\n",
  "currentNewParameter": {
    "label": "Body Parameter",
    "value": "bodyDataParameters"
  }
}
```

:::

</details>

### Schema

#### Request Body

<details>

<summary><strong>Request schema (TypeScript + JSON Schema)</strong></summary>

**TypeScript**

```typescript
type LicenseActivationRequestBody = ({

  // for key-based licenses:
  license_key: string

} | {

  // for user-based licenses:
  username: string
  password: string

} | {

  // for user-based licenses using single sign-on Implicit grant:
  id_token: string
  customer_account_code: string

} | {

  // for user-based licenses using single sign-on Authorization code grant:
  code: string
  customer_account_code: string

}) & {

  // required properties:
  hardware_id: string
  product: string

  // optional properties:
  bundle_code?: string | undefined
  license_id?: number | undefined
  sdk_ver?: string | undefined
}
```

**JSON Schema**

```json
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "allOf": [
    {
      "oneOf": [
        {
          "type": "object",
          "properties": {
            "license_key": { "type": "string" }
          },
          "required": ["license_key"],
          "additionalProperties": false
        },
        {
          "type": "object",
          "properties": {
            "username": { "type": "string" },
            "password": { "type": "string" }
          },
          "required": ["username", "password"],
          "additionalProperties": false
        },
        {
          "type": "object",
          "properties": {
            "id_token": { "type": "string" },
            "customer_account_code": { "type": "string" }
          },
          "required": ["id_token", "code", "customer_account_code"],
          "additionalProperties": false
        },
        {
          "type": "object",
          "properties": {
            "code": { "type": "string" },
            "customer_account_code": { "type": "string" }
          },
          "required": ["id_token", "code", "customer_account_code"],
          "additionalProperties": false
        }
      ]
    },
    {
      "type": "object",
      "properties": {
        "hardware_id": { "type": "string" },
        "product": { "type": "string" },
        "bundle_code": { "type": "string" },
        "license_id": { "type": "number" },
        "sdk_ver": { "type": "string" }
      },
      "required": ["hardware_id", "product"],
      "additionalProperties": false
    }
  ]
}
```

</details>

#### Response Body

If the request succeeds, the endpoint responds with the HTTP code 200 and the string `license_deactivated`.

### License Authorization Method

There are two types of product licenses based on how the client application authorizes itself to interact with a license:

#### Key-based product licenses

Each license assigned to the product requires the client to provide a license key in order to activate, check or deactivate the license (property `license_key` in the request body).

The license response object will contain the `license_key` property, and the property `product_details.authorization_method` will be the string `license_key`.

#### User-based product licenses

Each license assigned to the product has a corresponding "license user" instead of a license key. There are multiple ways a user-based product license can be accessed when making a request:

* By providing the values for `username` and `password` for the license user
* By providing the values for `id_token` (use access\_token value) and `customer_account_code` if using single sign-on authentication using Implicit grant
* By providing the values for `code` and `customer_account_code` if using single sign-on authentication using Authorization code grant

In the license response object, `product_details.authorization_method` will have the string value `user` and the response object will contain the `user` object with information on the license user.

### Errors

If an error occurs, the response will have an HTTP status code of 400 or higher, and the response body will contain an error description in the following format:

```typescript
{
  status: number,
  code: string,
  message: string
}
```

JSON Schema

```json
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "status": { "type": "number" },
    "code": { "type": "string" },
    "message": { "type": "string" }
  },
  "required": [
    "status",
    "code",
    "message"
  ],
  "additionalProperties": false
}
```

#### List of Exceptions

> unknown\_product (400): Provided product was not found

> license\_not\_found (400): License with the provided license user not found

> license\_not\_active (400): The license is not active

> device\_not\_found (400): An active device matching the hardware\_id not found

> missing\_headers (400): Some headers are missing

> tpm\_invalid\_public\_key (400): The TPM public key sent is not valid

> tpm\_signature\_required (400): The TPM signature is missing from the header and TPM usage is enforced

> tpm\_signature\_missmatch (400): The TPM signature/algorithm is not ES256 and the signature can't be verified against the TPM public key


# Deactivate License (Offline Method)

{% hint style="info" %}
Currently we support trial, perpetual and consumption license types for offline deactivation purposes
{% endhint %}

### Endpoint

* Method: `POST`
* Path: `/api/v4/deactivate_offline`
* Description: Deactivates a license using the offline flow (base64 payload).

### Authentication

See [License API Authorization](/license-api/license-api-authorization).

#### Required headers

* `Date` (string) — RFC7231 GMT date string
* `Authorization` (string) — signature or bearer token

#### Recommended headers

* `Accept: application/json`

### Request

#### Body

The request body is a **base64-encoded, stringified JSON object** (see schema below).

{% hint style="danger" %}
If using `multipart/form-data`, the `file` form parameter is mandatory.
{% endhint %}

{% tabs %}
{% tab title="curl" %}

```bash
curl --location --request POST '/api/v4/deactivate_offline' \
--header 'Accept: application/json' \
--header 'Date: string' \
--header 'Authorization: string' \
--data-raw '_BASE64_PAYLOAD_HERE_'
```

{% endtab %}

{% tab title="nodejs" %}

```javascript
var request = require('request');
var options = {
   'method': 'POST',
   'url': '/api/v4/deactivate_offline',
   'headers': {
      'Accept': 'application/json',
      'Date': 'string',
      'Authorization': 'string'
   },
   body: Buffer.from(JSON.stringify(offline_payload)).toString('base64')
};

request(options, function (error, response) {
   if (error) throw new Error(error);
   console.log(response.body);
});
```

{% endtab %}

{% tab title="javascript" %}

```javascript
var myHeaders = new Headers();
myHeaders.append("Accept", "application/json");
myHeaders.append("Date", "string");
myHeaders.append("Authorization", "string");

var requestOptions = {
   method: 'POST',
   headers: myHeaders,
   body: btoa(JSON.stringify(offline_payload)),
   redirect: 'follow'
};

fetch("/api/v4/deactivate_offline", requestOptions)
   .then(response => response.text())
   .then(result => console.log(result))
   .catch(error => console.log('error', error));
```

{% endtab %}

{% tab title="python" %}

```python
import requests

url = "/api/v4/deactivate_offline"

payload = "{\"Payload\":\"Object\"}"
headers = {
   'Accept': 'application/json',
   'Date': 'string',
   'Authorization': 'string'
}

response = requests.request("POST", url, headers=headers, data=payload)

print(response.text)
```

{% endtab %}

{% tab title="ruby" %}

```ruby
require "uri"
require "net/http"

url = URI("/api/v4/deactivate_offline")

http = Net::HTTP.new(url.host, url.port);
request = Net::HTTP::Post.new(url)
request["Accept"] = "application/json"
request["Date"] = "string"
request["Authorization"] = "string"
request.body = "{\"Payload\":\"Object\"}"

response = http.request(request)
puts response.read_body
```

{% endtab %}
{% endtabs %}

***

### Schema

#### Request Body

The request body is a string representing a base64-encoded JSON object containing all the required activation data.

{% hint style="danger" %}
If using "multipart/form-data" for the request, the "file" form parameter is mandatory
{% endhint %}

<details>

<summary><strong>Request schema (TypeScript + JSON Schema)</strong></summary>

**TypeScript**

```typescript
type LicenseOfflineActivationObject = ({

  // for key-based licenses:
  license_key: string

} | {

  // for user-based licenses:
  username: string
  password: string

}) & {

  // required properties:
  hardware_id: string
  product: string
  request_id: string
  signature: string
  date: string
  request: "deactivation"
  
} & ({
  api_key: string // for API key authorization
} | {
  client_id: string // for OAuth authorization
}) & {

  // optional properties:
  bundle_code?: string | undefined
  license_id?: number | undefined
  is_vm?: boolean | undefined
  vm_info?: string | undefined
  os_ver?: string | undefined
  hostname?: string | undefined
  os_hostname?: string | undefined
  ip?: string | undefined
  ip_local?: string | undefined
  app_ver?: string | undefined
  sdk_ver?: string | undefined
  mac_address?: string | undefined
  consumptions?: number | undefined
  licensespring-tpm-signature?: string | undefined
  product_features?: {
    feature: string,
    consumptions: number,
  }[]
}
```

**JSON Schema**

```json
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "allOf": [
    {
      "oneOf": [
        {
          "type": "object",
          "properties": {
            "license_key": { "type": "string" }
          },
          "required": ["license_key"],
          "additionalProperties": false
        },
        {
          "type": "object",
          "properties": {
            "username": { "type": "string" },
            "password": { "type": "string" }
          },
          "required": ["username", "password"],
          "additionalProperties": false
        }
      ]
    },
    {
      "type": "object",
      "properties": {
        "api_key": { "type": "string" },
        "client_id": { "type": "string" },
        "request": { "type": "string" },
        "request_id": { "type": "string" },
        "date": { "type": "string" },
        "signature": { "type": "string" },
        "licensespring-tpm-signature": { "type": "string" },
        "hardware_id": { "type": "string" },
        "product": { "type": "string" },
        "bundle_code": { "type": "string" },
        "license_id": { "type": "number" },
        "is_vm": { "type": "boolean" },
        "vm_info": { "type": "string" },
        "os_ver": { "type": "string" },
        "hostname": { "type": "string" },
        "os_hostname": { "type": "string" },
        "ip": { "type": "string" },
        "ip_local": { "type": "string" },
        "app_ver": { "type": "string" },
        "sdk_ver": { "type": "string" },
        "mac_address": { "type": "string" },
        "consumptions": { "type": "number" }, 
        "product_features": {
          "type": "object",
          "properties": {
            "feature": { "type": "string" },
            "consumptions": { "type": "number" }
          }
        }
      },
     "allOf": [
        { "anyOf": [{ "required": ["api_key"] }, { "required": ["client_id"] }] },
        { "anyOf": [{ "required": ["license_key"] }, { "required": ["username"] }] }
      ],
      "required": ["hardware_id", "product", "date", "signature"],
      "additionalProperties": false
    }
  ]
}
```

</details>

***

### Signature

The signature value is constructed from a specific signing string and then encrypted using HMAC-SHA256 with the company shared key (or client secret for OAuth). Use the following steps to build the signing string:

{% stepper %}
{% step %}

### Step

Concatenate the string "licenseSpring" plus a newline.
{% endstep %}

{% step %}

### Step

Add the string "date: " plus the "date" value from the license payload object, plus a newline.
{% endstep %}

{% step %}

### Step

Add either the "license\_key" or "username" value from the request payload (whichever is present), plus a newline.
{% endstep %}

{% step %}

### Step

Add the "hardware\_id" value from the request payload, plus a newline.
{% endstep %}

{% step %}

### Step

Add the "api\_key" value from the request payload.
{% endstep %}
{% endstepper %}

Encrypt the complete string with HMAC-SHA256 using the signing key (Shared Key for API key authorization; Client Secret for OAuth). Example in Node.js:

```javascript
import crypto from 'node:crypto';

const activationPayload = {
  // ...payload content...
};

// api_key or client_id depending on authorization type used:
const key = (activationPayload.api_key || activationPayload.client_id);

// if using API key authorization: the signing key is the Shared Key
// if using OAuth: the signing key is the Client Secret
const signingKey = '...';

const signingString =
  'licenseSpring\n' +
  'date: ' + activationPayload.date + '\n' +
  (activationPayload.license_key || activationPayload.username) + '\n' +
  activationPayload.hardware_id + '\n' +
  key;

const signature = crypto
  .createHmac('sha256', signingKey)
  .update(signingString)
  .digest('base64');
```

***

#### Finalized payload

This object is then stringified and encoded to base64.

{% tabs %}
{% tab title="JS Browser" %}

```javascript
const activationPayload = {
  // ...payload content...
};
const requestBody = btoa(JSON.stringify(activationPayload));
```

{% endtab %}

{% tab title="nodeJS" %}

```javascript
const activationPayload = {
  // ...payload content with "signature" property...
};
const requestBody = Buffer.from(JSON.stringify(activationPayload)).toString('base64');
```

{% endtab %}
{% endtabs %}

***

#### Response Body

If the request succeeds, the endpoint responds with HTTP 200 and the string:

license\_deactivated

***

### License Authorization Method

There are two types of product licenses based on how the client application authorizes itself to interact with a license.

#### Key-based product licenses

* Client provides property license\_key in the request body.
* The license response object will contain license\_key.
* product\_details.authorization\_method will be "license\_key".

#### User-based product licenses

* Product uses a license user instead of a license key.
* Access methods include:
  * Providing username and password
  * Providing id\_token and customer\_account\_code (Implicit grant SSO)
  * Providing code and customer\_account\_code (Authorization code grant SSO)

In responses, product\_details.authorization\_method will be "user" and the response will contain the user object with information on the license user.

***

### Errors

If an error occurs, the response will have HTTP status code >= 400 and the body will contain an error description:

```typescript
{
  status: number,
  code: string,
  message: string
}
```

JSON Schema:

```json
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "status": { "type": "number" },
    "code": { "type": "string" },
    "message": { "type": "string" }
  },
  "required": [
    "status",
    "code",
    "message"
  ],
  "additionalProperties": false
}
```

#### List of exceptions

> missing\_headers (400): some headers are missing
>
> * when missing authorization or date headers

> missing\_parameters (400): some parameters are missing in the request
>
> * when no request body at all or no file found in request body

> authorization\_missing\_params (400): some parameters are missing in authorization
>
> * when the request body is not properly base64 encoded
> * when file is missing in request body
> * when license\_key or hardware\_id body parameters are missing
> * when data body parameter is missing
> * when api\_key parameter is missing

> tpm\_invalid\_public\_key (400): The TPM public key sent is not valid

> tpm\_signature\_required (400): The TPM signature is missing from the body/license file and TPM usage is enforced

> tpm\_signature\_missmatch (400): The TPM signature/algorithm is not ES256 and the signature can't be verified against the TPM public key

***

### Guide on using Offline Licenses

If any aspect of the offline licensing model remains unclear, see [Offline License Activation](/license-entitlements/license-activation-types/offline-license-activation).


# Deactivate Bundle (Online Method)

### Endpoint

* Method: `POST`
* Path: `/api/v4/deactivate_bundle`
* Description: Deactivates a bundle license for a device and returns `license_deactivated` on success.

### Authentication

See [License API Authorization](/license-api/license-api-authorization).

#### Required headers

* `Date` (string) — RFC7231 GMT date string
* `Authorization` (string) — signature or bearer token

#### Recommended headers

* `Accept: application/json`
* `Content-Type: application/json`

#### Optional headers

* `licensespring-tpm-signature`  (string) — TPM licensing signature and algorithm
  * `licensespring-tpm-signature: algorithm="ES256",signature=""`

### Request

#### Body parameters

Required:

* `hardware_id` (string)
* `product` (string) — Bundle product short code

One of:

* `license_key` (string)
* `username` + `password` (string)
* `id_token` (use access\_token value) + `customer_account_code` (string)
* `code` + `customer_account_code` (string)

Optional:

* `license_id` (number)
* `sdk_ver` (string)
* device metadata fields (`is_vm`, `vm_info`, `os_ver`, `hostname`, `ip`, `mac_address`, ...)

#### Minimal example (JSON)

```json
{
  "hardware_id": "some-unique-id",
  "product": "BUNDLE",
  "license_key": "AAAA-BBBB-CCCC-DDDD"
}
```

### Schema

<details>

<summary><strong>Request schema (TypeScript + JSON Schema)</strong></summary>

**TypeScript**

```typescript
type BundleDeactivationRequestBody = ({

  // for key-based licenses:
  license_key: string

} | {

  // for user-based licenses:
  username: string
  password: string

} | {

  // for user-based licenses using single sign-on Implicit grant:
  id_token: string
  customer_account_code: string

} | {

  // for user-based licenses using single sign-on Authorization code grant:
  code: string
  customer_account_code: string

}) & {
  hardware_id: string
  product: string

  license_id?: number | undefined
  sdk_ver?: string | undefined
}
```

**JSON Schema**

```json
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "allOf": [
    {
      "oneOf": [
        {
          "type": "object",
          "properties": {
            "license_key": { "type": "string" }
          },
          "required": ["license_key"],
          "additionalProperties": false
        },
        {
          "type": "object",
          "properties": {
            "username": { "type": "string" },
            "password": { "type": "string" }
          },
          "required": ["username", "password"],
          "additionalProperties": false
        },
        {
          "type": "object",
          "properties": {
            "id_token": { "type": "string" },
            "customer_account_code": { "type": "string" }
          },
          "required": ["id_token", "customer_account_code"],
          "additionalProperties": false
        },
        {
          "type": "object",
          "properties": {
            "code": { "type": "string" },
            "customer_account_code": { "type": "string" }
          },
          "required": ["code", "customer_account_code"],
          "additionalProperties": false
        }
      ]
    },
    {
      "type": "object",
      "properties": {
        "hardware_id": { "type": "string" },
        "product": { "type": "string" },
        "license_id": { "type": "number" },
        "sdk_ver": { "type": "string" }
      },
      "required": ["hardware_id", "product"],
      "additionalProperties": false
    }
  ]
}
```

</details>

### Examples

{% tabs %}
{% tab title="curl" %}

```bash
curl --location --request POST '/api/v4/deactivate_bundle' \
--header 'Accept: application/json' \
--header 'Content-Type: application/json' \
--header 'Date: string' \
--header 'Authorization: string' \
--data-raw '{
  "hardware_id": "some-unique-id",
  "product": "BUNDLE",
  "license_key": "AAAA-BBBB-CCCC-DDDD"
}'
```

{% endtab %}

{% tab title="nodejs" %}

```javascript
var request = require('request');

var options = {
  method: 'POST',
  url: '/api/v4/deactivate_bundle',
  headers: {
    'Accept': 'application/json',
    'Content-Type': 'application/json',
    'Date': 'string',
    'Authorization': 'string'
  },
  body: JSON.stringify({
    hardware_id: 'some-unique-id',
    product: 'BUNDLE',
    license_key: 'AAAA-BBBB-CCCC-DDDD'
  })
};

request(options, function (error, response) {
  if (error) throw new Error(error);
  console.log(response.body);
});
```

{% endtab %}

{% tab title="javascript (fetch)" %}

```javascript
var myHeaders = new Headers();
myHeaders.append("Accept", "application/json");
myHeaders.append("Content-Type", "application/json");
myHeaders.append("Date", "string");
myHeaders.append("Authorization", "string");

var raw = JSON.stringify({
  hardware_id: "some-unique-id",
  product: "BUNDLE",
  license_key: "AAAA-BBBB-CCCC-DDDD"
});

var requestOptions = {
  method: 'POST',
  headers: myHeaders,
  body: raw,
  redirect: 'follow'
};

fetch("/api/v4/deactivate_bundle", requestOptions)
  .then(response => response.text())
  .then(result => console.log(result))
  .catch(error => console.log('error', error));
```

{% endtab %}

{% tab title="python" %}

```python
import requests

url = "/api/v4/deactivate_bundle"

payload = {
  "hardware_id": "some-unique-id",
  "product": "BUNDLE",
  "license_key": "AAAA-BBBB-CCCC-DDDD"
}

headers = {
  "Accept": "application/json",
  "Content-Type": "application/json",
  "Date": "string",
  "Authorization": "string"
}

response = requests.post(url, json=payload, headers=headers)
print(response.text)
```

{% endtab %}

{% tab title="ruby" %}

```ruby
require "uri"
require "net/http"
require "json"

url = URI("/api/v4/deactivate_bundle")

http = Net::HTTP.new(url.host, url.port)
request = Net::HTTP::Post.new(url)
request["Accept"] = "application/json"
request["Content-Type"] = "application/json"
request["Date"] = "string"
request["Authorization"] = "string"
request.body = JSON.dump({
  hardware_id: "some-unique-id",
  product: "BUNDLE",
  license_key: "AAAA-BBBB-CCCC-DDDD"
})

response = http.request(request)
puts response.read_body
```

{% endtab %}
{% endtabs %}

### License Authorization Method

There are two types of product licenses based on how the client application authorizes itself to interact with a license:

#### Key-based product licenses

Each license assigned to the product requires the client to provide a license key in order to activate, check or deactivate the license (property license\_key in the request body).

The license response object will contain the license\_key property, and product\_details.authorization\_method will be the string license\_key.

#### User-based product licenses

Each license assigned to the product has a corresponding "license user" instead of a license key. There are multiple ways a user-based product license can be accessed when making a request:

* By providing the values for username and password for the license user
* By providing the values for id\_token (use access\_token value) and customer\_account\_code if using single sign-on authentication using Implicit grant
* By providing the values for code and customer\_account\_code if using single sign-on authentication using Authorization code grant

In the license response object, product\_details.authorization\_method will have the string value user and the response object will contain the user object with information on the license user.

### Errors

If an error occurs, the response will have an HTTP status code of 400 or higher, and the response body will contain an error description in the following format:

```typescript
{
  status: number,
  code: string,
  message: string
}
```

JSON Schema

```json
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "status": { "type": "number" },
    "code": { "type": "string" },
    "message": { "type": "string" }
  },
  "required": [
    "status",
    "code",
    "message"
  ],
  "additionalProperties": false
}
```

<details>

<summary>List of Exceptions</summary>

> unknown\_product (400): Provided product was not found

> product\_not\_bundle (400): Specified product "code" is not a bundle

> license\_not\_found (400): License with the provided license user not found

> license\_not\_active (400): The license is not active

> device\_not\_found (400): An active device matching the hardware\_id not found

> missing\_headers (400): Some headers are missing

> tpm\_invalid\_public\_key (400): The TPM public key sent is not valid

> tpm\_signature\_required (400): The TPM signature is missing from the header and TPM usage is enforced

> tpm\_signature\_missmatch (400): The TPM signature/algorithm is not ES256 and the signature can't be verified against the TPM public key

</details>


# Deactivate Bundle (Offline Method)

### Deactivate Bundle (Offline Method)

Deactivates a bundle using the offline activation method. Returns the list of affected licenses in response.

***

### Endpoint

* Method: `POST`
* Path: `/api/v4/deactivate_bundle_offline`
* Description: Deactivates a bundle using the offline flow (base64 payload).

### Authentication

See [License API Authorization](/license-api/license-api-authorization).

#### Required headers

* `Date` (string) — RFC7231 GMT date string
* `Authorization` (string) — signature or bearer token

#### Recommended headers

* `Accept: application/json`

### Request

#### Body

The request body is a **base64-encoded, stringified JSON object** (see schema below).

{% hint style="danger" %}
If using `multipart/form-data`, the `file` form parameter is mandatory.
{% endhint %}

### Examples

{% tabs %}
{% tab title="curl" %}

```bash
curl --location --request POST '/api/v4/deactivate_bundle_offline' \
--header 'Accept: application/json' \
--header 'Date: string' \
--header 'Authorization: string' \
--data-raw '_BASE64_PAYLOAD_HERE_'
```

{% endtab %}

{% tab title="nodejs" %}

```javascript
var request = require('request');
var options = {
   'method': 'POST',
   'url': '/api/v4/deactivate_bundle_offline',
   'headers': {
      'Accept': 'application/json',
      'Date': 'string',
      'Authorization': 'string'
   },
   body: Buffer.from(JSON.stringify(offline_payload)).toString('base64')
};

request(options, function (error, response) {
   if (error) throw new Error(error);
   console.log(response.body);
});
```

{% endtab %}

{% tab title="javascript (browser / fetch)" %}

```javascript
var myHeaders = new Headers();
myHeaders.append("Accept", "application/json");
myHeaders.append("Date", "string");
myHeaders.append("Authorization", "string");

var requestOptions = {
   method: 'POST',
   headers: myHeaders,
   body: btoa(JSON.stringify(offline_payload)),
   redirect: 'follow'
};

fetch("/api/v4/deactivate_bundle_offline", requestOptions)
   .then(response => response.text())
   .then(result => console.log(result))
   .catch(error => console.log('error', error));
```

{% endtab %}

{% tab title="python" %}

```python
import requests

url = "/api/v4/deactivate_bundle_offline"

payload = "{\"Payload\":\"Object\"}"
headers = {
   'Accept': 'application/json',
   'Date': 'string',
   'Authorization': 'string'
}

response = requests.request("POST", url, headers=headers, data=payload)

print(response.text)
```

{% endtab %}

{% tab title="ruby" %}

```ruby
require "uri"
require "net/http"

url = URI("/api/v4/deactivate_bundle_offline")

http = Net::HTTP.new(url.host, url.port);
request = Net::HTTP::Post.new(url)
request["Accept"] = "application/json"
request["Date"] = "string"
request["Authorization"] = "string"
request.body = "{\"Payload\":\"Object\"}"

response = http.request(request)
puts response.read_body
```

{% endtab %}
{% endtabs %}

***

### Schema

#### Request Body

The request body is a string representing a base64-encoded JSON object containing all the required activation data.

{% hint style="danger" %}
If using "multipart/form-data" for the request, the "file" form parameter is mandatory
{% endhint %}

<details>

<summary><strong>Request schema (TypeScript + JSON Schema)</strong></summary>

**TypeScript**

```typescript
type LicenseOfflineActivationObject = ({

  // for key-based licenses:
  license_key: string

} | {

  // for user-based licenses:
  username: string

}) & {

  // required properties:
  hardware_id: string
  product: string
  request_id: string
  signature: string
  date: string
  request: "deactivation"

} & ({
  api_key: string // for API key authorization
} | {
  client_id: string // for OAuth authorization
}) & {

  // optional properties:
  license_id?: number | undefined
  sdk_ver?: string | undefined
  licensespring-tpm-signature?: string | undefined
}
```

**JSON Schema**

```json
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "allOf": [
    {
      "oneOf": [
        {
          "type": "object",
          "properties": {
            "license_key": { "type": "string" }
          },
          "required": ["license_key"],
          "additionalProperties": false
        },
        {
          "type": "object",
          "properties": {
            "username": { "type": "string" }
          },
          "required": ["username"],
          "additionalProperties": false
        }
      ]
    },
    {
      "type": "object",
      "properties": {
        "api_key": { "type": "string" },
        "client_id": { "type": "string" },
        "request": { "type": "string" },
        "request_id": { "type": "string" },
        "date": { "type": "string" },
        "signature": { "type": "string" },
        "licensespring-tpm-signature": { "type": "string" },
        "hardware_id": { "type": "string" },
        "product": { "type": "string" },
        "license_id": { "type": "number" },
        "sdk_ver": { "type": "string" }
      },
     "allOf": [
        { "anyOf": [{ "required": ["api_key"] }, { "required": ["client_id"] }] },
        { "anyOf": [{ "required": ["license_key"] }, { "required": ["username"] }] }
      ],
      "required": ["hardware_id", "product", "date", "signature"],
      "additionalProperties": false
    }
  ]
}
```

</details>

***

### Signature

The `signature` value is constructed by creating a signing string and HMAC-SHA256 signing it with the company shared key (or client secret for OAuth). Build the signing string from these parts in order and with newline separators:

{% stepper %}
{% step %}

### Step

Start with the string: licenseSpring
{% endstep %}

{% step %}

### Step

Add a newline and the string: date:
{% endstep %}

{% step %}

### Step

Add a newline and either the license\_key or username value (whichever is present)
{% endstep %}

{% step %}

### Step

Add a newline and the hardware\_id value
{% endstep %}

{% step %}

### Step

Add a newline and the api\_key (or client\_id) value
{% endstep %}
{% endstepper %}

Then HMAC-SHA256 the complete signing string using the signing key:

* If using API key authorization: signing key = Shared Key
* If using OAuth: signing key = Client Secret

Example (Node.js):

```javascript
import crypto from 'node:crypto';

const activationPayload = {
  // ...payload content...
};

// api_key or client_id depending on authorization type used:
const key = (activationPayload.api_key || activationPayload.client_id);

// if using API key authorization: the signing key is the Shared Key
// if using OAuth: the signing key is the Client Secret
const signingKey = '...';

const signingString =
  'licenseSpring\n' +
  'date: ' + activationPayload.date + '\n' +
  (activationPayload.license_key || activationPayload.username) + '\n' +
  activationPayload.hardware_id + '\n' +
  key;

const signature = crypto
  .createHmac('sha256', signingKey)
  .update(signingString)
  .digest('base64');
```

***

### Finalized payload

Stringify the payload object and encode to base64.

{% tabs %}
{% tab title="JS Browser" %}

```javascript
const activationPayload = {
  // ...payload content...
};
const requestBody = btoa(JSON.stringify(activationPayload));
```

{% endtab %}

{% tab title="nodeJS" %}

```javascript
const activationPayload = {
  // ...payload content...
};
const requestBody = Buffer.from(JSON.stringify(activationPayload)).toString('base64');
```

{% endtab %}
{% endtabs %}

Send the resulting base64 string as the raw request body.

***

### Response Body

Success:

* HTTP 200
* Body: `"license_deactivated"`

Errors:

* HTTP 400 or higher
* Body format:

```json
{
  "status": number,
  "code": "string",
  "message": "string"
}
```

JSON Schema for error responses:

```json
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "status": { "type": "number" },
    "code": { "type": "string" },
    "message": { "type": "string" }
  },
  "required": [
    "status",
    "code",
    "message"
  ],
  "additionalProperties": false
}
```

#### List of exceptions

> missing\_headers (400): some headers are missing
>
> * when missing authorization or date headers

> missing\_parameters (400): some parameters are missing in the request
>
> * when no request body at all or no file found in request body

> authorization\_missing\_params (400): some parameters are missing in authorization
>
> * when request body is not properly base64 encoded
> * when file is missing in request body
> * when license\_key or hardware\_id body parameters are missing
> * when data body parameter is missing
> * when api\_key parameter is missing

> product\_not\_bundle (400): Specified product "{CODE}" is not a bundle

> tpm\_invalid\_public\_key (400): The TPM public key sent is not valid

> tpm\_signature\_required (400): The TPM signature is missing from the body/license file and TPM usage is enforced

> tpm\_signature\_missmatch (400): The TPM signature/algorithm is not ES256 and the signature can't be verified against the TPM public key

{% hint style="info" %}
If you want to use this API endpoint directly, instead of using SDK (which does most of the heavy lifting), please contact us for additional instructions.
{% endhint %}

***

### License Authorization Method

There are two authorization approaches for products:

* Key-based product licenses
  * Client provides `license_key` in the request body.
  * License response object will contain `license_key`.
  * `product_details.authorization_method` = `license_key`.
* User-based product licenses
  * Requires `username` (and password where applicable).
  * `product_details.authorization_method` = `user`.
  * Response includes a `user` object with license user info.

***

### License Types

The `license_type` property can be:

* Perpetual: `perpetual`
* Time-limited: `time-limited`
* Subscription: `subscription`
* Consumption: `consumption`

For more information see: [License Types](/license-entitlements/license-types)

***

### Device variables

The optional `variables` parameter lets you set device variables during activation. For more information see: [Device Variables](/license-api/device-variables)

***

### Guide on using Offline Licenses

If any aspect of the offline licensing model remains unclear, see the in-depth guide:\
<https://docs.licensespring.com/license-entitlements/activation-types/offline>


# License Check


# Check License

### Endpoint

* Method: `GET`
* Path: `/api/v4/check_license`
* Description: Checks the status and validity of a license.

### Authentication

See [License API Authorization](/license-api/license-api-authorization).

#### Required headers

* `Date` (string) — RFC7231 GMT date string
* `Authorization` (string) — signature or bearer token

#### Optional headers

* `licensespring-tpm-signature`  (string) — TPM licensing signature and algorithm
  * `licensespring-tpm-signature: algorithm="ES256",signature=""`

#### Recommended headers

* `Accept: application/json`

### Request

#### Query parameters

Required:

* `hardware_id` (string) — Unique hardware ID generated for the client device
* `product` (string) — Product short code

One of:

* `license_key` (string) — Required if product is key-based
* `username` (string) — Required if product is user-based

Optional:

* `license_id` (number) — Targets a specific license ID
* `include_expired_features` (boolean)
* `env` (string)
* `os_ver` (string)
* `hostname` (string)
* `os_hostname` (string)
* `ip` (string)
* `ip_local` (string)
* `app_ver` (string)
* `sdk_ver` (string)
* `mac_address` (string)

### Examples

{% tabs %}
{% tab title="curl" %}

```bash
curl --location --request GET '/api/v4/check_license?hardware_id=string&product=string&license_key=string' \
--header 'Accept: application/json' \
--header 'Date: string' \
--header 'Authorization: string'
```

{% endtab %}

{% tab title="nodejs" %}

```javascript
var request = require('request');
var options = {
  method: 'GET',
  url: '/api/v4/check_license?hardware_id=string&product=string&license_key=string',
  headers: {
    'Accept': 'application/json',
    'Date': 'string',
    'Authorization': 'string'
  }
};
request(options, function (error, response) {
  if (error) throw new Error(error);
  console.log(response.body);
});
```

{% endtab %}

{% tab title="javascript (fetch)" %}

```javascript
var myHeaders = new Headers();
myHeaders.append("Accept", "application/json");
myHeaders.append("Date", "string");
myHeaders.append("Authorization", "string");

var requestOptions = {
  method: 'GET',
  headers: myHeaders,
  redirect: 'follow'
};

fetch("/api/v4/check_license?hardware_id=string&product=string&license_key=string", requestOptions)
  .then(response => response.text())
  .then(result => console.log(result))
  .catch(error => console.log('error', error));
```

{% endtab %}

{% tab title="python" %}

```python
import requests

url = "/api/v4/check_license?hardware_id=string&product=string&license_key=string"

headers = {
  "Accept": "application/json",
  "Date": "string",
  "Authorization": "string"
}

response = requests.get(url, headers=headers)
print(response.text)
```

{% endtab %}

{% tab title="ruby" %}

```ruby
require "uri"
require "net/http"

url = URI("/api/v4/check_license?hardware_id=string&product=string&license_key=string")

http = Net::HTTP.new(url.host, url.port)
request = Net::HTTP::Get.new(url)
request["Accept"] = "application/json"
request["Date"] = "string"
request["Authorization"] = "string"

response = http.request(request)
puts response.read_body
```

{% endtab %}
{% endtabs %}

### Schema

<details>

<summary><strong>Request schema (TypeScript + JSON Schema)</strong></summary>

**TypeScript**

```typescript
type LicenseCheckQueryParameters = ({

  // for key-based licenses:
  license_key: string

} | {

  // for user-based licenses:
  username: string

}) & {

  // required properties:
  hardware_id: string
  product: string

  // optional properties:
  license_id?: number | undefined
  is_vm?: boolean | undefined
  vm_info?: string | undefined
  os_ver?: string | undefined
  hostname?: string | undefined
  os_hostname?: string | undefined
  ip?: string | undefined
  ip_local?: string | undefined
  app_ver?: string | undefined
  sdk_ver?: string | undefined
  mac_address?: string | undefined
  include_expired_features?: boolean | undefined
}
```

**JSON Schema**

```json
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "allOf": [
    {
      "oneOf": [
        {
          "type": "object",
          "properties": {
            "license_key": { "type": "string" }
          },
          "required": ["license_key"],
          "additionalProperties": false
        },
        {
          "type": "object",
          "properties": {
            "username": { "type": "string" }
          },
          "required": ["username"],
          "additionalProperties": false
        }
      ]
    },
    {
      "type": "object",
      "properties": {
        "hardware_id": { "type": "string" },
        "product": { "type": "string" },
        "license_id": { "type": "number" },
        "is_vm": { "type": "boolean" },
        "vm_info": { "type": "string" },
        "os_ver": { "type": "string" },
        "hostname": { "type": "string" },
        "os_hostname": { "type": "string" },
        "ip": { "type": "string" },
        "ip_local": { "type": "string" },
        "app_ver": { "type": "string" },
        "sdk_ver": { "type": "string" },
        "mac_address": { "type": "string" },
        "include_expired_features": { "type": "boolean" }
      },
      "required": ["hardware_id", "product"],
      "additionalProperties": false
    }
  ]
}
```

</details>

<details>

<summary><strong>Response schema (TypeScript)</strong></summary>

**TypeScript**

```typescript
type LicenseCheckResponseBody = {
  id: number,
  allow_grace_period: boolean,
  allow_overages: boolean,
  allow_unlimited_activations: boolean,
  borrowed_until: string | null,
  can_borrow: boolean,
  channel: string,
  device_id: number,
  enable_maintenance_period: boolean
  environment: string,
  eula_link: string,
  floating_timeout: number,
  grace_period: number,
  hash_md5: string,
  installation_file: string,
  is_air_gapped: boolean,
  is_borrowed: boolean,
  is_expired: boolean,
  is_floating_cloud: boolean,
  is_floating: boolean,
  is_hardware_key_auth: boolean,
  is_tpm_auth: boolean,
  license_active: boolean,
  license_enabled: boolean,
  license_signature: string,
  license_signature_v2: string,
  offline_signature: string,
  license_type: string,
  maintenance_period: string | null,
  max_activations: number,
  max_borrow_time: number,
  max_license_users: number,
  max_overages: number,
  max_transfers: number,
  order_store_id: string,
  prevent_vm: boolean,
  release_date: string,
  release_notes_link: string,
  requires_version: string,
  size: string,
  start_date: string | null,
  times_activated: number,
  transfer_count: number,
  validity_period: string | null,
  version: string,
  company: { id: number },
  
  product_features: ({
    id: number,
    code: string,
    name: string,
    expiry_date: string,
    metadata: JSON,
    feature_type: 'activation' | 'consumption',
    is_floating: boolean,
    is_floating_cloud: boolean,

    floating_users: number,
    floating_timeout: number,

    max_consumption: number,
    allow_unlimited_consumptions: boolean,
    total_consumptions: number,
    allow_overages: number,
    max_overages: number,
    reset_consumption: boolean,
    consumption_period: 'daily' | 'weekly' | 'monthly' | 'annualy' | null
  })[],
  
  custom_fields: ({
    name: string,
    data_type: 'numer' | 'text' | 'date/time',
    value: string,
  })[],
  
  customer: {
    email: string,
    company_name: string,
    reference: string,
    phone: string,
    first_name: string,
    last_name: string,
    city: string,
    postcode: string,
    state: string,
    country: string,
    address: string,
    customer_account: string | null,
    metadata: JSON,
  },
  
  product_details: {
    product_id: number,
    product_name: string,
    short_code: string,
    authorization_method: 'license-key' | 'user',
    metadata: JSON,
  },
  
  metadata: JSON,
  
  trial_days?: number,
  floating_in_use_devices?: number,
  floating_users?: number,
  max_consumptions?: number,
  total_consumptions?: number,
  allow_unlimited_consumptions?: boolean,
  reset_consumption?: boolean,
  consumption_period?: string | null

} & ({

  // for key-based licenses:
  license_key: string,

} | {

  // for user-based licenses:
  user: {
    id: number,
    email: string,
    first_name: string,
    last_name: string,
    phone_number: string,
    is_initial_password: boolean,
    max_activations: number,
    allow_unlimited_activations: boolean,
    total_activations: number
  }
});
```

</details>

### Response Signatures

The response object contains the following digital signatures:

* license\_signature: see the [Response Signature](/license-api/license-api-authorization/response-signature) article
* license\_signature\_v2: see the [Response Signature v2](/license-api/license-api-authorization/response-signature-v2) article
* offline\_signature: can be used for activating/deactivating using the offline method, see [Deactivate License (Offline Method)](/license-api/license-activation-and-deactivation/deactivate-license-offline-method)

### License Authorization Method

There are two authorization methods for products:

### Key-based product licenses

* Client provides license\_key in requests for key-based products.
* Response includes license\_key and product\_details.authorization\_method = "license\_key".

### User-based product licenses

* Client provides username in requests for user-based products.
* Response includes product\_details.authorization\_method = "user" and a user object with license user details.

### License Types

The license\_type property defines one of these types:

* perpetual
* time-limited
* subscription
* consumption

For more information see: [License Types](/license-entitlements/license-types)

### Errors

If an error occurs, HTTP status will be 400 or higher and the body will use this format:

<details>

<summary><strong>Error schema (TypeScript + JSON Schema)</strong></summary>

```typescript
{
  status: number,
  code: string,
  message: string
}
```

**JSON Schema**

```json
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "status": { "type": "number" },
    "code": { "type": "string" },
    "message": { "type": "string" }
  },
  "required": [
    "status",
    "code",
    "message"
  ],
  "additionalProperties": false
}
```

</details>

#### List of exceptions

* missing\_headers (400): Some headers are missing
* unknown\_product (400): Provided product was not found
* license\_not\_found (400): License with the provided license user not found
* license\_not\_enabled (400): The license is not enabled
* license\_not\_active (400): The license is not active
* device\_not\_found (400): An active device matching the hardware\_id not found
* blacklisted (400): This device is blacklisted
* license\_not\_enough\_consumptions (400): Not enough consumptions left
* floating\_not\_available (400): No available slots for floating license
* product\_version\_not\_supported (400): License product version does not support the app\_ver sent in the request
* tpm\_invalid\_public\_key (400): The TPM public key sent is not valid
* tpm\_signature\_required (400): The TPM signature is missing from the header and TPM usage is enforced
* tpm\_signature\_missmatch (400): The TPM signature/algorithm is not ES256 and the signature can't be verified against the TPM public key


# Check Bundle

### Endpoint

* Method: `GET`
* Path: `/api/v4/check_bundle`
* Description: Checks the status and validity of a license bundle.

### Authentication

See [License API Authorization](/license-api/license-api-authorization).

#### Required headers

* `Date` (string) — RFC7231 GMT date string
* `Authorization` (string) — signature or bearer token

#### Optional headers

* `licensespring-tpm-signature`  (string) — TPM licensing signature and algorithm
  * `licensespring-tpm-signature: algorithm="ES256",signature=""`

#### Recommended headers

* `Accept: application/json`

### Request

#### Query parameters

Required:

* `hardware_id` (string)
* `product` (string) — Bundle product short code

One of:

* `license_key` (string)
* `username` (string)

Optional:

* `license_id` (number)
* `include_expired_features` (boolean)
* `env` (string)
* `os_ver` (string)
* `hostname` (string)
* `os_hostname` (string)
* `ip` (string)
* `ip_local` (string)
* `app_ver` (string)
* `sdk_ver` (string)
* `mac_address` (string)

### Examples

{% tabs %}
{% tab title="curl" %}

```bash
curl --location --request GET '/api/v4/check_bundle?hardware_id=string&product=string&license_key=string' \
--header 'Accept: application/json' \
--header 'Date: string' \
--header 'Authorization: string'
```

{% endtab %}

{% tab title="nodejs" %}

```javascript
var request = require('request');
var options = {
  method: 'GET',
  url: '/api/v4/check_bundle?hardware_id=string&product=string&license_key=string',
  headers: {
    'Accept': 'application/json',
    'Date': 'string',
    'Authorization': 'string'
  }
};
request(options, function (error, response) {
  if (error) throw new Error(error);
  console.log(response.body);
});
```

{% endtab %}

{% tab title="javascript (fetch)" %}

```javascript
var myHeaders = new Headers();
myHeaders.append("Accept", "application/json");
myHeaders.append("Date", "string");
myHeaders.append("Authorization", "string");

var requestOptions = {
  method: 'GET',
  headers: myHeaders,
  redirect: 'follow'
};

fetch("/api/v4/check_bundle?hardware_id=string&product=string&license_key=string", requestOptions)
  .then(response => response.text())
  .then(result => console.log(result))
  .catch(error => console.log('error', error));
```

{% endtab %}

{% tab title="python" %}

```python
import requests

url = "/api/v4/check_bundle?hardware_id=string&product=string&license_key=string"

headers = {
  "Accept": "application/json",
  "Date": "string",
  "Authorization": "string"
}

response = requests.get(url, headers=headers)
print(response.text)
```

{% endtab %}

{% tab title="ruby" %}

```ruby
require "uri"
require "net/http"

url = URI("/api/v4/check_bundle?hardware_id=string&product=string&license_key=string")

http = Net::HTTP.new(url.host, url.port)
request = Net::HTTP::Get.new(url)
request["Accept"] = "application/json"
request["Date"] = "string"
request["Authorization"] = "string"

response = http.request(request)
puts response.read_body
```

{% endtab %}
{% endtabs %}

***

### Schema

#### Request Query Parameters

<details>

<summary><strong>Request schema (TypeScript + JSON Schema)</strong></summary>

**TypeScript**

```typescript
type BundleCheckQueryParameters = ({

  // for key-based licenses:
  license_key: string

} | {

  // for user-based licenses:
  username: string

}) & {

  // required properties:
  hardware_id: string
  product: string

  // optional properties:
  license_id?: number | undefined
  is_vm?: boolean | undefined
  vm_info?: string | undefined
  os_ver?: string | undefined
  hostname?: string | undefined
  os_hostname?: string | undefined
  ip?: string | undefined
  ip_local?: string | undefined
  app_ver?: string | undefined
  sdk_ver?: string | undefined
  mac_address?: string | undefined
  include_expired_features?: boolean | undefined
}
```

**JSON Schema**

```json
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "array",
  "items": {
    "allOf": [
      {
        "oneOf": [
          {
            "type": "object",
            "properties": {
              "license_key": { "type": "string" }
            },
            "required": ["license_key"],
            "additionalProperties": false
          },
          {
            "type": "object",
            "properties": {
              "username": { "type": "string" }
            },
            "required": ["username"],
            "additionalProperties": false
          }
        ]
      },
      {
        "type": "object",
        "properties": {
          "hardware_id": { "type": "string" },
          "product": { "type": "string" },
          "license_id": { "type": "number" },
          "is_vm": { "type": "boolean" },
          "vm_info": { "type": "string" },
          "os_ver": { "type": "string" },
          "hostname": { "type": "string" },
          "os_hostname": { "type": "string" },
          "ip": { "type": "string" },
          "ip_local": { "type": "string" },
          "app_ver": { "type": "string" },
          "sdk_ver": { "type": "string" },
          "mac_address": { "type": "string" },
          "include_expired_features": { "type": "boolean" }
        },
        "required": ["hardware_id", "product"],
        "additionalProperties": false
      }
    ]
  }
}
```

</details>

***

#### Response Body

<details>

<summary><strong>Response schema (TypeScript + JSON Schema)</strong></summary>

**TypeScript**

```typescript
type BundleCheckResponseBody = BundleCheckResponse[];

type BundleCheckResponse = {
  id: number,
  allow_grace_period: boolean,
  allow_overages: boolean,
  allow_unlimited_activations: boolean,
  borrowed_until: string | null,
  can_borrow: boolean,
  channel: string,
  device_id: number,
  enable_maintenance_period: boolean
  environment: string,
  eula_link: string,
  floating_timeout: number,
  grace_period: number,
  hash_md5: string,
  installation_file: string,
  is_air_gapped: boolean,
  is_borrowed: boolean,
  is_expired: boolean,
  is_floating_cloud: boolean,
  is_floating: boolean,
  is_hardware_key_auth: boolean,
  is_tpm_auth: boolean,
  license_active: boolean,
  license_enabled: boolean,
  license_signature: string,
  license_signature_v2: string,
  offline_signature: string,
  license_type: string,
  maintenance_period: string | null,
  max_activations: number,
  max_borrow_time: number,
  max_license_users: number,
  max_overages: number,
  max_transfers: number,
  order_store_id: string,
  prevent_vm: boolean,
  release_date: string,
  release_notes_link: string,
  requires_version: string,
  size: string,
  start_date: string | null,
  times_activated: number,
  transfer_count: number,
  validity_period: string | null, // string is in DateISO8601UTC format
  version: string,
  company: { id: number },
  
  product_features: ({
    id: number,
    code: string,
    name: string,
    expiry_date: string,
    metadata: JSON,
    feature_type: 'activation' | 'consumption',
    is_floating: boolean,
    is_floating_cloud: boolean,

    // the following properties are only present if is_floating=true or is_floating_cloud=true
    floating_users: number,
    floating_timeout: number,

    // the following properties are only present if feature_type=consumption
    max_consumption: number,
    allow_unlimited_consumptions: boolean,
    total_consumptions: number,
    allow_overages: number,
    max_overages: number,
    reset_consumption: boolean,
    consumption_period: 'daily' | 'weekly' | 'monthly' | 'annualy' | null,
  })[],
  
  custom_fields: ({
    name: string,
    data_type: 'numer' | 'text' | 'date/time',
    value: string,
  })[],
  
  customer: {
    email: string,
    company_name: string,
    reference: string,
    phone: string,
    first_name: string,
    last_name: string,
    city: string,
    postcode: string,
    state: string,
    country: string,
    address: string,
    customer_account: string | null,
    metadata: JSON,
  },
  
  product_details: {
    product_id: number,
    product_name: string,
    short_code: string,
    authorization_method: 'license-key' | 'user',
    metadata: JSON,
  },
  
  metadata: JSON,
  
  // the following property is only present if is_trial=true
  trial_days: number,
  
  // the following properties is only present if is_floating=true or is_floating_cloud=true
  floating_in_use_devices: number,
  floating_users: number,
  
  // the following properties are only present if license_type='consumption'
  max_consumptions: number,
  total_consumptions: number,
  allow_unlimited_consumptions: boolean,
  reset_consumption: boolean,
  consumption_period: string | null, // string is in DateISO8601UTC format
  
} & ({

  // for key-based licenses:
  license_key: string,

} | {

  // for user-based licenses:
  user: {
    id: number,
    email: string,
    first_name: string,
    last_name: string,
    phone_number: string,
    is_initial_password: boolean,
    max_activations: number,
    allow_unlimited_activations: boolean,
    total_activations: number
  }
});
```

**JSON Schema**

```json
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "allOf": [
    {
      "oneOf": [
        {
          "type": "object",
          "properties": {
            "license_key": { "type": "string" }
          },
          "required": ["license_key"],
          "additionalProperties": false
        },
        {
          "type": "object",
          "properties": {
            "user": {
              "type": "object",
              "properties": {
                "id": { "type": "number" },
                "email": { "type": "string" },
                "first_name": { "type": "string" },
                "last_name": { "type": "string" },
                "phone_number": { "type": "string" },
                "is_initial_password": { "type": "boolean" },
                "max_activations": { "type": "number" },
                "allow_unlimited_activations": { "type": "boolean" },
                "total_activations": { "type": "number" }
              },
              "required": ["id", "email", "first_name", "last_name", "phone_number", "is_initial_password", "max_activations", "allow_unlimited_activations", "total_activations"],
              "additionalProperties": false
            }
          },
          "required": ["user"],
          "additionalProperties": false
        }
      ]
    },
    {
      "type": "object",
      "properties": {
        "id": { "type": "number" },
        "allow_grace_period": { "type": "boolean" },
        "allow_overages": { "type": "boolean" },
        "allow_unlimited_activations": { "type": "boolean" },
        "borrowed_until": { "type": ["string", "null"] },
        "can_borrow": { "type": "boolean" },
        "channel": { "type": "string" },
        "device_id": { "type": "number" },
        "enable_maintenance_period": { "type": "boolean" },
        "environment": { "type": "string" },
        "eula_link": { "type": "string" },
        "floating_timeout": { "type": "number" },
        "grace_period": { "type": "number" },
        "hash_md5": { "type": "string" },
        "installation_file": { "type": "string" },
        "is_air_gapped": { "type": "boolean" },
        "is_borrowed": { "type": "boolean" },
        "is_expired": { "type": "boolean" },
        "is_floating_cloud": { "type": "boolean" },
        "is_floating": { "type": "boolean" },
        "is_hardware_key_auth": { "type": "boolean" },
        "is_tpm_auth": { "type": "boolean" },
        "license_active": { "type": "boolean" },
        "license_enabled": { "type": "boolean" },
        "license_signature": { "type": "string" },
        "license_signature_v2": { "type": "string" },
        "offline_signature": { "type": "string" },
        "license_type": { "type": "string" },
        "maintenance_period": { "type": ["string", "null"] },
        "max_activations": { "type": "number" },
        "max_borrow_time": { "type": "number" },
        "max_license_users": { "type": "number" },
        "max_overages": { "type": "number" },
        "max_transfers": { "type": "number" },
        "order_store_id": { "type": "string" },
        "prevent_vm": { "type": "boolean" },
        "release_date": { "type": "string" },
        "release_notes_link": { "type": "string" },
        "requires_version": { "type": "string" },
        "size": { "type": "string" },
        "start_date": { "type": ["string", "null"] },
        "times_activated": { "type": "number" },
        "transfer_count": { "type": "number" },
        "validity_period": { "type": ["string", "null"] },
        "version": { "type": "string" },
        "company": { "type": "object", "properties": {"id": { "type": "number" } } },
        "product_features": {
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "id": { "type": "number" },
              "code": { "type": "string" },
              "name": { "type": "string" },
              "expiry_date": { "type": "string" },
              "metadata": {},
              "feature_type": { "type": "string", "enum": ["activation", "consumption"] },
              "is_floating": { "type": "boolean" },
              "is_floating_cloud": { "type": "boolean" },
              "floating_users": { "type": "number" },
              "floating_timeout": { "type": "number" },
              "max_consumption": { "type": "number" },
              "allow_unlimited_consumptions": { "type": "boolean" },
              "total_consumptions": { "type": "number" },
              "allow_overages": { "type": "number" },
              "max_overages": { "type": "number" },
              "reset_consumption": { "type": "boolean" },
              "consumption_period": {
                "type": ["string", "null"],
                "enum": ["daily", "weekly", "monthly", "annualy", null]
              }
            },
            "required": ["id", "code", "name", "expiry_date", "metadata", "feature_type", "is_floating", "is_floating_cloud"]
          }
        },
        "custom_fields": {
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "name": { "type": "string" },
              "data_type": { "type": "string", "enum": ["numer", "text", "date/time"] },
              "value": { "type": "string" }
            },
            "required": ["name", "data_type", "value"]
          }
        },
        "customer": {
          "type": "object",
          "properties": {
            "email": { "type": "string" },
            "company_name": { "type": "string" },
            "reference": { "type": "string" },
            "phone": { "type": "string" },
            "first_name": { "type": "string" },
            "last_name": { "type": "string" },
            "city": { "type": "string" },
            "postcode": { "type": "string" },
            "state": { "type": "string" },
            "country": { "type": "string" },
            "address": { "type": "string" },
            "customer_account": { "type": ["string", "null"] },
            "metadata": {}
          },
          "required": ["email", "company_name", "reference", "phone", "first_name", "last_name", "city", "postcode", "state", "country", "address", "metadata"]
        },
        "product_details": {
          "type": "object",
          "properties": {
            "product_id": { "type": "number" },
            "product_name": { "type": "string" },
            "short_code": { "type": "string" },
            "authorization_method": { "type": "string", "enum": ["license-key", "user"] },
            "metadata": {}
          },
          "required": ["product_id", "product_name", "short_code", "authorization_method", "metadata"]
        },
        "metadata": {},
        "trial_days": { "type": "number" },
        "floating_in_use_devices": { "type": "number" },
        "floating_users": { "type": "number" },
        "max_consumptions": { "type": "number" },
        "total_consumptions": { "type": "number" },
        "allow_unlimited_consumptions": { "type": "boolean" },
        "reset_consumption": { "type": "boolean" },
        "consumption_period": { "type": ["string", "null"] }
      },
      "required": [
        "id",
        "allow_grace_period",
        "allow_overages",
        "allow_unlimited_activations",
        "can_borrow",
        "channel",
        "device_id",
        "enable_maintenance_period",
        "environment",
        "eula_link",
        "floating_timeout",
        "grace_period",
        "hash_md5",
        "installation_file",
        "is_air_gapped",
        "is_borrowed",
        "is_expired",
        "is_floating_cloud",
        "is_floating",
        "is_hardware_key_auth",
        "is_tpm_auth",
        "license_active",
        "license_enabled",
        "license_signature",
        "license_type",
        "max_activations",
        "max_borrow_time",
        "max_license_users",
        "max_overages",
        "max_transfers",
        "order_store_id",
        "prevent_vm",
        "release_date",
        "release_notes_link",
        "requires_version",
        "size",
        "times_activated",
        "transfer_count",
        "version",
        "product_features",
        "custom_fields",
        "customer",
        "product_details",
        "metadata"
      ],
      "additionalProperties": false
    }
  ]
}
```

</details>

***

#### Response Signatures

The response objects contain the following digital signatures:

* license\_signature — see [Response Signature](/license-api/license-api-authorization/response-signature)
* license\_signature\_v2 — see [Response Signature v2](/license-api/license-api-authorization/response-signature-v2)
* offline\_signature — see [Deactivate Bundle *Offline Method*](/license-api/license-activation-and-deactivation/deactivate-bundle-offline-method)

***

### License Authorization Method

There are two types of product licenses based on how the client application authorizes itself to interact with a license:

#### Key-based product licenses

Each license assigned to the product requires the client to provide a license key in order to activate, check or deactivate the license (property license\_key in the request). The license response object will contain the license\_key property, and product\_details.authorization\_method will be "license\_key".

#### User-based product licenses

Each license assigned to the product has a corresponding "license user" instead of a license key. Requires username to be provided. In the license response object, product\_details.authorization\_method will be "user" and the response object will contain the user object with information on the license user.

***

### License Types

The license\_type property defines one of the 4 types of licenses based on their duration and how the license is used:

* perpetual: "perpetual"
* time-limited: "time-limited"
* subscription: "subscription"
* consumption: "consumption"

For more information see: [License Types](/license-entitlements/license-types)

***

### Errors

If an error occurs, the response will have an HTTP status code of 400 or higher, and the response body will contain an error description in the following format:

<details>

<summary><strong>Error schema (TypeScript + JSON Schema)</strong></summary>

```typescript
{
  status: number,
  code: string,
  message: string
}
```

**JSON Schema**

```json
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "status": { "type": "number" },
    "code": { "type": "string" },
    "message": { "type": "string" }
  },
  "required": [
    "status",
    "code",
    "message"
  ],
  "additionalProperties": false
}
```

</details>

#### List of exceptions

<details>

<summary>missing_headers (400)</summary>

Some headers are missing

</details>

<details>

<summary>unknown_product (400)</summary>

Provided product was not found

</details>

<details>

<summary>license_not_found (400)</summary>

License with the provided license user not found

</details>

<details>

<summary>license_not_enabled (400)</summary>

The license is not enabled

</details>

<details>

<summary>license_not_active (400)</summary>

The license is not active

</details>

<details>

<summary>device_not_found (400)</summary>

An active device matching the hardware\_id not found

</details>

<details>

<summary>blacklisted (400)</summary>

This device is blacklisted

</details>

<details>

<summary>license_not_enough_consumptions (400)</summary>

Not enough consumptions left

</details>

<details>

<summary>floating_not_available (400)</summary>

No available slots for floating license

</details>

<details>

<summary>tpm_invalid_public_key (400)</summary>

The TPM public key sent is not valid

</details>

<details>

<summary>tpm_signature_required (400)</summary>

The TPM signature is missing from the header and TPM usage is enforced

</details>

<details>

<summary>tpm_signature_missmatch (400)</summary>

The TPM signature/algorithm is not ES256 and the signature can't be verified against the TPM public key

</details>


# License Feature Check

Use this endpoint to check a license device against a license feature. Once checked, the device will be connected to the feature. If the feature is floating, the device will also take one floating slot if available.

### Endpoint

* Method: `GET`
* Path: `/api/v4/check_license_feature`
* Description: Checks a license device against a license feature.

### Authentication

See [License API Authorization](/license-api/license-api-authorization).

#### Required headers

* `Date` (string) — RFC7231 GMT date string
* `Authorization` (string)

#### Optional headers

* `licensespring-tpm-signature`  (string) — TPM licensing signature and algorithm
  * `licensespring-tpm-signature: algorithm="ES256",signature=""`

#### Recommended headers

* `Accept: application/json`

### Request

### Query parameters (TypeScript)

```typescript
type LicenseFeatureCheckRequestParams = ({

  // for key-based licenses:
  license_key: string

} | {

  // for user-based licenses:
  username: string

}) & {

  // required properties:
  hardware_id: string
  product: string
  feature: string

  // optional properties:
  license_id?: number | undefined
}
```

JSON Schema

```json
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "allOf": [
    {
      "oneOf": [
        {
          "type": "object",
          "properties": {
            "license_key": { "type": "string" }
          },
          "required": ["license_key"],
          "additionalProperties": false
        },
        {
          "type": "object",
          "properties": {
            "username": { "type": "string" }
          },
          "required": ["username"],
          "additionalProperties": false
        }
      ]
    },
    {
      "type": "object",
      "properties": {
        "hardware_id": { "type": "string" },
        "product": { "type": "string" },
        "feature": { "type": "string" },
        "license_id": { "type": ["number", "null"] }
      },
      "required": ["hardware_id", "product", "feature"],
      "additionalProperties": false
    }
  ]
}
```

#### Query parameters summary

Required:

* `hardware_id` (string) — Unique hardware ID generated for the client device
* `product` (string) — Product short code
* `feature` (string) — Feature code

One of:

* `license_key` (string) — Required if product is key-based
* `username` (string) — Required if product is user-based

Optional:

* `license_id` (number) — Targets a specific license ID

### Examples

{% tabs %}
{% tab title="curl" %}

```bash
curl --location --request GET '/api/v4/check_license_feature?hardware_id=string&product=string&feature=string&license_key=string' \
--header 'Accept: application/json' \
--header 'Date: string' \
--header 'Authorization: string'
```

{% endtab %}

{% tab title="nodejs" %}

```javascript
var request = require('request');
var options = {
  method: 'GET',
  url: '/api/v4/check_license_feature?hardware_id=string&product=string&feature=string&license_key=string',
  headers: {
    'Accept': 'application/json',
    'Date': 'string',
    'Authorization': 'string'
  }
};
request(options, function (error, response) {
  if (error) throw new Error(error);
  console.log(response.body);
});
```

{% endtab %}

{% tab title="javascript (fetch)" %}

```javascript
var myHeaders = new Headers();
myHeaders.append("Accept", "application/json");
myHeaders.append("Date", "string");
myHeaders.append("Authorization", "string");

var requestOptions = {
  method: 'GET',
  headers: myHeaders,
  redirect: 'follow'
};

fetch("/api/v4/check_license_feature?hardware_id=string&product=string&feature=string&license_key=string", requestOptions)
  .then(response => response.text())
  .then(result => console.log(result))
  .catch(error => console.log('error', error));
```

{% endtab %}

{% tab title="python" %}

```python
import requests

url = "/api/v4/check_license_feature?hardware_id=string&product=string&feature=string&license_key=string"

headers = {
  "Accept": "application/json",
  "Date": "string",
  "Authorization": "string"
}

response = requests.get(url, headers=headers)
print(response.text)
```

{% endtab %}

{% tab title="ruby" %}

```ruby
require "uri"
require "net/http"

url = URI("/api/v4/check_license_feature?hardware_id=string&product=string&feature=string&license_key=string")

http = Net::HTTP.new(url.host, url.port)
request = Net::HTTP::Get.new(url)
request["Accept"] = "application/json"
request["Date"] = "string"
request["Authorization"] = "string"

response = http.request(request)
puts response.read_body
```

{% endtab %}
{% endtabs %}

### Response

### Response body (TypeScript)

```typescript
type LicenseFeatureCheckResponseBody = {
    allow_negative_consumptions: boolean,
    allow_overages: boolean,
    allow_unlimited_consumptions: boolean,
    code: string,
    consumption_period: string,
    expiry_date: null | string,
    feature_type: string,
    id: number,
    is_floating_cloud: boolean,
    is_floating: boolean,
    license_id: number,
    max_consumption: number,
    max_overages: number,
    metadata: JSON,
    name: string,
    reset_consumption: boolean,
    total_consumptions: number,
    
    // the following property is only present on floating and floating cloud features:
    floating_in_use_devices?: number,
}
```

JSON Schema

```json
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "allow_negative_consumptions": { "type": "boolean" },
    "allow_overages": { "type": "boolean" },
    "allow_unlimited_consumptions": { "type": "boolean" },
    "code": { "type": "string" },
    "consumption_period": { "type": "string" },
    "expiry_date": { "type": ["string", "null"] },
    "feature_type": { "type": "string" },
    "id": { "type": "number" },
    "is_floating_cloud": { "type": "boolean" },
    "is_floating": { "type": "boolean" },
    "license_id": { "type": "number" },
    "max_consumption": { "type": "number" },
    "max_overages": { "type": "number" },
    "metadata": { "type": "object" },
    "name": { "type": "string" },
    "reset_consumption": { "type": "boolean" },
    "total_consumptions": { "type": "number" },
    "floating_in_use_devices": { "type": "number" }
  },
  "required": [
    "allow_negative_consumptions",
    "allow_overages",
    "allow_unlimited_consumptions",
    "code",
    "consumption_period",
    "expiry_date",
    "feature_type",
    "id",
    "is_floating_cloud",
    "is_floating",
    "license_id",
    "max_consumption",
    "max_overages",
    "metadata",
    "name",
    "reset_consumption",
    "total_consumptions"
  ],
  "additionalProperties": false
}
```

Notes:

* floating\_in\_use\_devices — number of devices currently in use on this license feature (present only on floating and floating cloud features)
* license\_id — id of the license

### Errors

If an error occurs, the response will have an HTTP status code of 400 or higher. The response body will contain an error description in the following format:

```typescript
{
  status: number,
  code: string,
  message: string
}
```

JSON Schema

```json
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "status": { "type": "number" },
    "code": { "type": "string" },
    "message": { "type": "string" }
  },
  "required": [
    "status",
    "code",
    "message"
  ],
  "additionalProperties": false
}
```

### List of exceptions

* missing\_headers (400): Some headers are missing
* unknown\_product (400): Provided product was not found
* license\_not\_found (400): License with the provided license user not found
* license\_not\_enabled (400): The license is not enabled
* license\_not\_active (400): The license is not active
* device\_not\_found (400): An active device matching the hardware\_id not found
* blacklisted (400): This device is blacklisted
* invalid\_license\_feature\_code (400): License feature code: ' + param + ' is invalid or not assigned to the license.
* floating\_not\_available (400): No available slots for floating license.
* tpm\_invalid\_public\_key (400): The TPM public key sent is not valid
* tpm\_signature\_required (400): The TPM signature is missing from the header and TPM usage is enforced
* tpm\_signature\_missmatch (400): The TPM signature/algorithm is not ES256 and the signature can't be verified against the TPM public key


# Consumption

A consumption is a type of agreement that governs the use of a product or service. There are two main types of consumption licenses: those that include consumptions on the **license** and those that cover the consumption of **features**.

{% hint style="info" %}
`allow_unlimited_consumption` always has priority over `max_overages`, i.e. if `allow_unlimited_consumption` is set to **true** then `max_overages` doesn't have any effect.
{% endhint %}


# Add Consumption

### Endpoint

* Method: `POST`
* Path: `/api/v4/add_consumption`
* Description: Adds 1 or more consumptions to a license (consumption-type licenses only).

### Authentication

See [License API Authorization](/license-api/license-api-authorization).

#### Required headers

* `Date` (string) — RFC7231 GMT date string
* `Authorization` (string)

#### Optional headers

* `licensespring-tpm-signature`  (string) — TPM licensing signature and algorithm
  * `licensespring-tpm-signature: algorithm="ES256",signature=""`

#### Recommended headers

* `Accept: application/json`
* `Content-Type: application/json`

### Request

#### Body parameters

Required:

* `hardware_id` (string)
* `product` (string)

One of:

* `license_key` (string)
* `username` (string)

Optional:

* `license_id` (number)
* `consumptions` (number) — Defaults to `1`. Can be negative if allowed.
* `max_overages` (number)
* `allow_overages` (boolean)
* `event` (array of strings) — `consumption_add` (default) or `offline_floating_consumptions_sync` (mutually exclusive)

### Examples

{% tabs %}
{% tab title="curl" %}

```bash
curl --location --request POST '/api/v4/add_consumption' \
--header 'Accept: application/json' \
--header 'Content-Type: application/json' \
--header 'Date: string' \
--header 'Authorization: string' \
--data-raw '{
  "hardware_id": "some-unique-id",
  "product": "XY",
  "license_key": "AAAA-BBBB-CCCC-DDDD",
  "consumptions": 1
}'
```

{% endtab %}

{% tab title="nodejs" %}

```javascript
var request = require('request');
var options = {
  method: 'POST',
  url: '/api/v4/add_consumption',
  headers: {
    'Accept': 'application/json',
    'Content-Type': 'application/json',
    'Date': 'string',
    'Authorization': 'string'
  },
  body: JSON.stringify({
    hardware_id: 'some-unique-id',
    product: 'XY',
    license_key: 'AAAA-BBBB-CCCC-DDDD',
    consumptions: 1
  })
};
request(options, function (error, response) {
  if (error) throw new Error(error);
  console.log(response.body);
});
```

{% endtab %}

{% tab title="javascript (fetch)" %}

```javascript
var myHeaders = new Headers();
myHeaders.append("Accept", "application/json");
myHeaders.append("Content-Type", "application/json");
myHeaders.append("Date", "string");
myHeaders.append("Authorization", "string");

var raw = JSON.stringify({
  hardware_id: "some-unique-id",
  product: "XY",
  license_key: "AAAA-BBBB-CCCC-DDDD",
  consumptions: 1
});

var requestOptions = {
  method: 'POST',
  headers: myHeaders,
  body: raw,
  redirect: 'follow'
};

fetch("/api/v4/add_consumption", requestOptions)
  .then(response => response.text())
  .then(result => console.log(result))
  .catch(error => console.log('error', error));
```

{% endtab %}

{% tab title="python" %}

```python
import requests

url = "/api/v4/add_consumption"

payload = {
  "hardware_id": "some-unique-id",
  "product": "XY",
  "license_key": "AAAA-BBBB-CCCC-DDDD",
  "consumptions": 1
}

headers = {
  "Accept": "application/json",
  "Content-Type": "application/json",
  "Date": "string",
  "Authorization": "string"
}

response = requests.post(url, json=payload, headers=headers)
print(response.text)
```

{% endtab %}

{% tab title="ruby" %}

```ruby
require "uri"
require "net/http"
require "json"

url = URI("/api/v4/add_consumption")

http = Net::HTTP.new(url.host, url.port)
request = Net::HTTP::Post.new(url)
request["Accept"] = "application/json"
request["Content-Type"] = "application/json"
request["Date"] = "string"
request["Authorization"] = "string"
request.body = JSON.dump({
  hardware_id: "some-unique-id",
  product: "XY",
  license_key: "AAAA-BBBB-CCCC-DDDD",
  consumptions: 1
})

response = http.request(request)
puts response.read_body
```

{% endtab %}
{% endtabs %}

### Schema

#### Request Body

<details>

<summary><strong>Request schema (TypeScript + JSON Schema)</strong></summary>

**TypeScript**

```typescript
type AddConsumptionRequestBody = ({

  // required for key-based products:
  license_key: string,

} | {
  // required for user-based products:
  username: string,

}) & {
  // required parameters:
  product: string,
  hardware_id: string,

  // optional parameters:
  license_id?: string | undefined,
  consumptions?: number | undefined,
  max_overages?: number | undefined,
  allow_overages?: boolean | undefined,
  event?: ('consumption_add' | 'offline_floating_consumptions_sync')[] | undefined,
}
```

**JSON Schema**

```json
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "allOf": [
    {
      "oneOf": [
        {
          "type": "object",
          "properties": {
            "license_key": { "type": "string" }
          },
          "required": ["license_key"],
          "additionalProperties": false
        },
        {
          "type": "object",
          "properties": {
            "username": { "type": "string" }
          },
          "required": ["username"],
          "additionalProperties": false
        }
      ]
    },
    {
      "type": "object",
      "properties": {
        "hardware_id": { "type": "string" },
        "product": { "type": "string" },
        "license_id": { "type": "number" },
        "consumptions": { "type": "number" },
        "max_overages": { "type": "number" },
        "allow_overages": { "type": "boolean" },
        "event": {
          "type": "array",
          "items": { "type": "string", "enum": ["consumption_add", "offline_floating_consumptions_sync"] },
          "minItems": 1,
          "not": {
            "allOf": [
              { "contains": { "const": "consumption_add" } },
              { "contains": { "const": "offline_floating_consumptions_sync" } }
            ]
          }
        }
      },
      "required": ["hardware_id", "product"],
      "additionalProperties": false
    }
  ]
}
```

</details>

#### Response Body

<details>

<summary><strong>Response schema (TypeScript + JSON Schema)</strong></summary>

**TypeScript**

```typescript
type AddConsumptionResponseBody = {
  id: number,
  max_consumptions: number,
  total_consumptions: number,
  allow_unlimited_consumptions: boolean,
  allow_negative_consumptions: boolean,
  allow_overages: boolean,
  max_overages: number,
  reset_consumption: boolean,
  consumption_period: 'daily' | 'weekly' | 'monthly' | 'annually' | null,
}
```

**JSON Schema**

```json
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "id": { "type": "number" },
    "max_consumptions": { "type": "number" },
    "total_consumptions": { "type": "number" },
    "allow_unlimited_consumptions": { "type": "boolean" },
    "allow_negative_consumptions": { "type": "boolean" },
    "allow_overages": { "type": "boolean" },
    "max_overages": { "type": "number" },
    "reset_consumption": { "type": "boolean" },
    "consumption_period": { "type": [ "string", "null" ], "enum": [ "daily", "weekly", "monthly", "annually", null ] }
  },
  "required": [
    "id",
    "max_consumptions",
    "total_consumptions",
    "allow_unlimited_consumptions",
    "allow_negative_consumptions",
    "allow_overages",
    "max_overages",
    "reset_consumption",
    "consumption_period"
  ],
  "additionalProperties": false
}
```

</details>

### License Authorization Method

There are two types of product licenses based on how the client application authorizes itself to interact with a license:

* Key-based product licenses: client interactions with the license have to be authorized using a `license_key`
* User-based product licenses: the license has a corresponding "license user" instead of a license key. Client interactions with the license have to be authorized using a `username`

### Errors

If an error occurs, the response will have an HTTP status code of 400 or higher, and the response body will contain an error description in the following format:

<details>

<summary><strong>Error schema (TypeScript + JSON Schema)</strong></summary>

**TypeScript**

```typescript
{
  status: number,
  code: string,
  message: string
}
```

**JSON Schema**

```json
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "status": { "type": "number" },
    "code": { "type": "string" },
    "message": { "type": "string" }
  },
  "required": [
    "status",
    "code",
    "message"
  ],
  "additionalProperties": false
}
```

</details>

#### List of exceptions

<details>

<summary><strong>unknown_product (400)</strong></summary>

Provided product was not found

</details>

<details>

<summary><strong>license_not_found (400)</strong></summary>

License with the provided license user not found

</details>

<details>

<summary><strong>license_not_active (400)</strong></summary>

The license is not active

</details>

<details>

<summary><strong>license_not_enabled (400)</strong></summary>

The license is not enabled

</details>

<details>

<summary><strong>license_not_consumption (400)</strong></summary>

The license needs to be of consumption type

</details>

<details>

<summary><strong>license_not_enough_consumptions (400)</strong></summary>

Not enough consumptions left

</details>

<details>

<summary><strong>negative_consumptions_not_allowed (400)</strong></summary>

Negative consumptions not allowed

</details>

<details>

<summary><strong>device_not_found (400)</strong></summary>

An active device matching the hardware\_id not found

</details>

<details>

<summary><strong>blacklisted (400)</strong></summary>

This device is blacklisted

</details>

<details>

<summary>tpm_invalid_public_key (400)</summary>

The TPM public key sent is not valid

</details>

<details>

<summary>tpm_signature_required (400)</summary>

The TPM signature is missing from the header and TPM usage is enforced

</details>

<details>

<summary>tpm_signature_missmatch (400)</summary>

The TPM signature/algorithm is not ES256 and the signature can't be verified against the TPM public key

</details>




---

[Next Page](/llms-full.txt/1)

