Select programming language for code examples

linkAPI Reference

The Keygen API is organized around REST principles. All requests must be made over TLS/SSL. We only support TLS 1.2, for security reasons. All API request and response bodies, including errors, are encoded in JSON format.

The API has predictable, resource-oriented URLs, and uses standard HTTP response codes to indicate API errors. We use built-in HTTP features, like authentication and HTTP verbs, which are understood by off-the-shelf HTTP clients. We support cross-origin resource sharing, allowing you to interact securely with our API from any client-facing software application.

code Get Started with our API

linkYour Keygen Account

In order to make requests to our licensing API, you will need to use your Keygen account's unique ID, or its slug that you chose during sign up. Every API request will utilize your account's ID or slug within the URL path, i.e. /v1/accounts/{ID} or /v1/accounts/{SLUG}. Your account's ID and slug can be used interchangably as URL params.

You can find your account's ID and slug in your account settings, under the "Current Account" section.

Account Permissions

By default, your Keygen account will allow public user registration, user token generation (authentication), and will also allow users to create their own licenses and machines while authenticated. This is referred to as an "unprotected" account, allowing client-side resource management (i.e. by your users), while you respond to those events using webhooks.

For example, you can allow users to create and delete their licenses and machine activations, while you respond to those events to handle billing, e.g. charge them for new licenses, update their subscription to match their license and machine count, etc.

If you do not want your users to be able to create and manage their own licenses and machines client-side (which may necessitate listening for webhook events) then you should set your account to "protected", which will require admin authentication to create and manage all resources, aside from machine activation and deactivation.

Setting your account to "protected" is the recommended approach when using Keygen 100% server-side. You can change your account permissions from your account settings page.

See the security section for more tips.

linkAccepted Content Types

Our JSON API accepts the following content types specified via the HTTP Content-Type header:

  • application/vnd.api+json
  • application/json

In addition, our JSON API is able to respond with the following content types specified via the HTTP Accept header:

  • application/vnd.api+json
  • application/json

By default our API will respond with application/vnd.api+json, unless asked otherwise through the standard HTTP Accept header. All other content types will be rejected with a 400 status code.

linkClient Libraries

At the moment we're focused on our HTTP API endpoints. As time goes by we'll be featuring here some more of our open-source clients for different languages and frameworks, as well as those contributed by the community.

We're still exploring what an official client library from Keygen would look like, how we could ensure cross-platform interoperability, and how to build it securely. The more pieces applications implementing Keygen share, the harder it will be to prevent software cracking, i.e. with an official client library, a well-made crack for one application runs the risk of working across multiple applications using Keygen.

For now, we recommend using an HTTP library to directly interface with our API from your application code, or to discreetly integrate Keygen server-side.

Open Source Integrations

If you've written an open source client library, SDK, or other integration yourself, please let us know. We will feature your name along with a link to the client library, unless asked to do otherwise.

Language
Node electron-builder by develar and mmaietta
We will collaborate with project maintainers to keep open source libraries up-to-date as our systems evolve. If you would like us to add documentation examples for a specific language, or if you would like us to add an open-source library you created, please email [email protected].

Alternative Integrations

We use the following open-source HTTP libraries for code examples:

Language
Node node-fetch by bitinn
Python Requests by psf
Swift Alamofire
C# RestSharp
Kotlin Unirest by mashape
Java Unirest by mashape
C++ C++ REST SDK by Microsoft
Shell cURL

You may of course choose to use a different HTTP library, but these are the libaries that you will see used throughout our documentation.

linkVersioning

When we introduce breaking changes to the API, we will release a new numbered version, e.g. we will go from v1 to v2. The current version is v1. All changes will be detailed within our changelog.

linkAPI Stability

We will not introduce breaking changes into the API without bumping the current version. You can rest assured that the endpoints you're utilizing within your product are stable.

Keep in mind that stable does not mean complete. We could potentially add new resources and actions, but existing resources will remain unchanged.

linkBackwards Compatibility

What do we consider to be “backwards-compatible” changes?

  • Adding new API resources
  • Adding new optional request parameters to existing API endpoints
  • Adding new properties to existing API resources and responses
  • Changing the order of properties in existing API responses
  • Changing the length or format of resource IDs, future tokens, future auto-generated license keys, or other opaque strings (this includes adding or removing prefixes)
  • You can safely assume resource IDs we generate will never exceed 255 characters (but you should be able to handle IDs of up to that length)
  • Adding new event types (your webhook listeners should gracefully handle unfamiliar events types)

linkSecurity

Below you will find various security tips you may find useful.

linkSecret API Tokens

If one of your admin API tokens become compromised, you can quickly revoke it from your admin dashboard.

In Client-facing Software

Do not embed your admin or product API tokens within client- or end-user-facing code. If you're using Keygen client-side, do not embed your admin or product tokens within your product's source code. Doing so exposes your tokens and opens your account up to attackers, regardless of how obfuscated the token is.

Instead, you can perform license and machine creation requests client-side while authenticated as one of your users, or by using an activation token which belongs to the license you're activating a machine for.

If you're using Keygen server-side, keep your secret API tokens safe.

In Version Control

We recommend that you do not store your API tokens in version control. It is recommended that you store tokens in an environment file that is never checked into the repository, or directly within the ENV on non-public devices.

linkPublic IDs and Keys

Embedding account, product and policy IDs directly into your product i.e. client-side code is perfectly safe. Inlining your public key is also perfect safe. In fact, you won't be able to communicate with Keygen's API or verify signatures unless you include some of these values, and managing e.g. feature licenses without being able to embed policy IDs wouldn't be feasible.

With that in mind, it is recommended that you use your account ID over your account slug, as unlike your account slug, your ID is unchangable.

Resource IDs are standard v4 UUIDs, resembling the following format:

1fddcec8-8dd3-4d8d-9b16-215cac0f9b52

An account's RSA public key will resemble the following:

-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzPAseDYupK78ZUaSbGw7
YyUCCeKo/1XqTACOcmTTHHGgeHacLK2j9UrbTlhW5h8Vyo0iUEHrY1Kgf4wwiGgF
h0Yc+oDWDhq1bIertI03AE420LbpUf6OTioX+nY0EInxXF3J7aAdx/R/nYgRJrLZ
9ATWaQVSgf3vtxCtCwUeKxKZI41GA/9KHTcCmd3BryAQ1piYPr+qrEGf2NDJgr3W
vVrMtnjeoordAaCTyYKtfm56WGXeXr43dfdejBuIkI5kqSzwVyoxhnjE/Rj6xks8
ffH+dkAPNwm0IpxXJerybjmPWyv7iyXEUN8CKG+6430D7NoYHp/c991ZHQBUs59g
vwIDAQAB
-----END PUBLIC KEY-----

linkAccount Permissions

If you do not want your users to be able to create and manage their own licenses and machines (which could necessitate listening for webhook events) then you should set your account to protected, which will require admin authentication to create and manage all resources, aside from token creation and license validation. Setting your account to protected can be done within your account settings.

Alternatively, you can set individual licenses policies to protected (which by default is inherited from the account's current protected state), which will only disallow users from creating and managing licenses which implement that particular policy and their associated machines, rather than all resources e.g. public user creation, licenses that implement other unprotected policies, etc. You can also set a license to protected, which will disallow machine activation/deactivation; similar to policies, the license's protected attribute is inherited from its policy.

By default, your account is unprotected, which allows user registration as well as the ability for users to manage their own resources.

linkValidation Permissions

If you only need to validate license keys, then you do not need to implement user creation or authentication. The license validate-key action does not require authentication. If you're doing anything else e.g. validating multiple license resources per-user, tracking machines, etc., user authentication or an activation token will be required in order to perform these API operations client-side.

You can add required validation scopes to your policies to e.g. require a machine fingerprint, etc., which helps implement certain license models without the need for any additional server-side logic such as a webhook handler.

linkCrack Prevention

Although all applications are susceptible to cracking (given enough motivation), there are certain things you can do to make cracking harder. Even applications written in a compiled language, such as C, are susceptible to software cracking.

Efforts beyond the prevention measures below have diminishing returns for most software products and time is better spent on legitimate users. Reach out to [email protected] if you need additional anti-cracking measures, we'd be happy to chat with you.

Signature Verification

To help prevent tampering and other types of attacks such as a man-in-the-middle and replay attacks, you may choose to implement license key signature verification, and response signature verification. Both of these verifications harden your software by ensuring the given data originated from Keygen and that the values have not been modified.

The easiest one of those to implement is response signature verification, which can also be used offline with license validation caching.

User Agent Header

We recommend that you supply a custom User-Agent header with all API requests from your software application. You should include your organization name, the application's name, the running version number of the application, the operating system name, in addition to any other information you may find valuable.

Application-Name/1.33.7 (Org-Name) darwin/10.15.5 (macOS Catalina) Apache-HttpClient/4.5.5 (Java/1.8.0_201)

We will use this value to automatically detect cracking attempts. We will notify you of all findings and help you secure your application.

We are currently running a 'pilot' for an AI/ML crack detection product. If this is something your organization is interested in, please reach out to us: [email protected]. We'd love to see if you're a good fit.

linkResponse Codes

Our API uses conventional HTTP response codes to indicate the success or failure of an API request. In general, codes in the 2xx range indicate success, codes in the 4xx range indicate an error that failed given the information provided (e.g., a required parameter was omitted, a validation failed, etc.), and codes in the 5xx range indicate an error with our servers (these are rare).

Code Status Meaning
200 OK Everything worked as expected.
201 Created The resource was created successfully.
202 Accepted The request has been accepted for processing.
204 No Content Everything worked as expected, but there was nothing to respond with.
303 See Other The request was successful. Follow the Location header via GET for more information.
307 Temporary Redirect The request was successful. Follow the Location header and replay the request.
400 Bad Request The request was unacceptable, often due to missing or unpermitted parameters.
401 Unauthorized No valid API token provided.
403 Forbidden The authenticated entity does not have permission to complete the request.
404 Not Found The requested resource doesn't exist.
409 Conflict The request could not be completed because the resource already exists.
422 Unprocessable Entity A validation error occurred on the resource.
429 Too Many Requests Too many requests hit the API too quickly. We recommend an exponential backoff of your requests.
5xx Server Errors Something went wrong on our end. (These are rare.)

linkErrors

Below you will find the various attributes for request errors. When one or more error occurs, an errors property will be included in the response payload. The data property will not be included when an error occurs.

linkAttributes

  • linkdata.attributes.title

    string

    A short, human-readable summary of the problem.

  • linkdata.attributes.detail

    string

    A more detailed human-readable explanation of the problem.

  • linkdata.attributes.code

    string

    A unique, unchanging machine-readable error code. This may or may not be included in the error payload, depending on the type of error.

  • linkdata.attributes.source

    object<string, any>

    A Object containing references to the source of the error. This may or may not be included in the error payload, depending on the type of error.

  • linkdata.attributes.source.pointer

    string

    A pointer to the problem data, e.g. "/data" for the primary data, "/data/attributes/email" for a specific attribute, or "/data/relationships/user" for a problem with a relationship. This may or may not be included in the error payload, depending on the type of error.

  • linkdata.attributes.source.parameter

    string

    A string indicating which URI query parameter caused the error. This may or may not be included in the error payload, depending on the type of error.

Example error

{
"errors": [
{
"title": "Unprocessable entity",
"detail": "must be a valid email",
"code": "EMAIL_INVALID",
"source": {
"pointer": "/data/attributes/email"
}
},
]
}

linkAuthentication

Requests are authenticated using a bearer token in an Authorization header. Within the header, you will utilize a Token resource's token attribute, like so,

Authorization: Bearer {TOKEN}

If you want to interact with Keygen's API client-side, then you should do so using our user resources, which we created for this purpose. You can create users with email and password credentials, which can be used during token generation to authenticate a user.

After a user is authenticated, they can interact with our API in a way that is limited to only their user profile.

There are a couple other token types in addition to admin/user authentication tokens:

  • Activation tokens: authenticating as a license is useful when you're not utilizing our user resources for authentication, but you still want to perform client-side machine activation. Activation tokens allow a limited number of machine activations and deactivations. To create a new activation token, please see the License token relationship.
  • Product tokens: authenticating as a product is useful in server-side environments. These tokens allow full management of resources associated with the given product. To create a new product token, please see the Product token relationship.

All user tokens have a 2 week expiry and can be regenerated as needed during that timeframe. Admin and product tokens do not expire, and should only be used server-side. By default, activation tokens do not expire, but you may set an expiry during creation if desired.

Admin and product tokens should only be used server-side, this is because they allow full near management of your Keygen account. This means that if an attacker were to obtain a product token from within your code, no matter how obfuscated, they will be able to create and manage licenses at-will—including those of your other customers.

Most API endpoints will require authentication, and access to resources depends on the token bearer's authorization and role.

You can manage tokens using the Tokens resource.

Security Warning

Tokens should be treated as passwords. Different tokens carry different privileges depending on the bearer of the token, so be sure to keep them secret! Do not share your admin or product API tokens in publicly accessible areas such as GitHub, client-facing code, and so forth. If in doubt, please regenerate or revoke the offending token(s).

linkAuthorization

Access to certain resources is dependent upon a token bearer's role. Most of the time you will be authenticating as one of your users, which will allow access to a small subset of resources available to your account. In other cases, as when you are using a server-side integration, you may be authenticating as a product, or even an admin; in these cases, you will have access to a wider range of resources.

Resource attributes and relationships marked with a "protected" badge are only allowed to be specified if the authenticated bearer is an admin of the account, or a product that owns the resource. Attributes and relationships marked with a "read only" badge cannot be modified.

Many resource endpoints are automatically scoped according to the token bearer's role. For example, listing all licenses while authenticated as a product will only list licenses associated with that particular product. Attempting to access resources that the bearer does not have access to will respond with a 403 forbidden error.

Never hard-code authentication tokens within your client-facing product – doing so could leave your product open to major exploitations by allowing a malicious user the ability to fully manage your account's resources. The only time you should be using your admin or product token(s) directly is if you are working with Keygen server-side.

Here's a quick summary of the different authorization roles:

Role Administrator Authentication Authorization
None No No Unauthenticated users can create a new user profile (unless your account is protected), and validate license keys using the validate-key action. No other endpoints are accessisible to unauthenticated users.
User No Yes Authenticated users may access certain resource endpoints, but all resources that are returned will be scoped to their user profile, e.g. when a user makes a request to list all licenses, only the licenses which are associated with their user profile will be returned.
License No Yes Authenticated licenses may perform machine activations and deactivations through activation tokens. They may also validate the license. They cannot perform any other request.
Product Yes Yes Authenticated products may access resources for their account that are associated with that particular product. All resources that are returned will be scoped to the product, e.g. when a product makes a request to list all licenses, only the licenses which are associated with the product will be returned.
Support Agent Yes Yes Authenticated support agents may access some resources for their account. They can read the following resources: products, policies, users, licenses, machines. They can update the following resources: licenses, machines. They cannot delete resources.
Sales Agent Yes Yes Authenticated sales agents may access some resources for their account. They can read the following resources: products, policies, users, licenses, machines. They can create the following resources: policies, licenses, machines. They can update the following resources: policies, licenses, machines. They can delete the following resources: licenses, machines. They cannot delete any other resources.
Developer Yes Yes Authenticated developers may access all resources for their account, minus account billing information.
Admin Yes Yes Authenticated admin users may access all resources for their account.

linkRate Limiting

For client-side requests, i.e. made unauthenticated, or with a user or activation token, a single IP address can make a maximum burst of 60 requests per 30 second window and 500 requests per 5 minute window, which allows for 1 request/sec with short bursts of up to 3-5 requests/sec. For server-side environments, i.e. requests authenticated with admin and product tokens, these limits are higher, based on your server-side usage.

We may add additional rate limit windows in the future, which will be included within the X-RateLimit-Window header with a specific name, e.g. 30s.

If we see patterns of abuse, this limit may be lowered or the IP may be temporarily blacklisted. In rare cases of significant abuse, the IP may be permanently blacklisted from our API.

When rate limited, a 429 HTTP status code will be given. You can also check the returned HTTP headers of any API request to see your current rate limit status for the closest window:

Header Description
X-RateLimit-Window The current rate limiting window that is closest to being reached, percentage-wise.
X-RateLimit-Count The number of requests that have been performed within the current rate limit window.
X-RateLimit-Limit The maximum number of requests that the IP is permitted to make for the current window.
X-RateLimit-Remaining The number of requests remaining in the current rate limit window.
X-RateLimit-Reset The time at which the current rate limit window resets in UTC epoch seconds.
Retry-After Indicates how long, in seconds, to wait before making a new request. Only included when rate limited.

linkAvoiding the rate limiter

Sometimes, you may find yourself occasionally needing to send API requests in high volume, causing rate limiting errors. Here are a few things to try:

  1. Slow down. If you're sending a large sequence of requests, add a small delay of 1–200ms between each request and see if that improves things. This is especially important when paginating large datasets.
  2. Add jitter. In cases where you're running a cron job or invoking requests at a set interval across multiple machines, adding in some "jitter" can help reduce a thundering herd problem. In this case, "jitter" can simply be random delay value of 10ms to 10m, to help spread out the licensing requests across time.
  3. Add caching. In order to reduce request volume, you can cache previous responses for a set duration, to avoid hitting the network with superfluous requests. You can store response signatures to verify cache integrity.
  4. Obey the headers. If all else fails, you can look at the rate limiting headers above and derive a delay from the reset timestamp once your remaining count falls below a predefined threshold.
Looking for more customized rate limit? Reach out to our support team and we can discuss a custom rate limit that will work with your requirements. Most customized rate limits require an Ent tier or above.

Example response / 429 Too Many Requests

X-RateLimit-Window: 5m
X-RateLimit-Count: 501
X-RateLimit-Limit: 500
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1490973281
{
"errors": [
{
"title": "Too many requests",
"detail": "Throttle limit has been reached for your IP address.",
"code": "TOO_MANY_REQUESTS"
}
]
}

linkRelationships

Throughout the API, many resources will have a relationships Object containing another resource which they are associated with. When creating or modifying a relationship, you will be introduced to what's called a "resource identifier object", or linkage for short.

A "resource identifier object" is an object that identifies an individual resource. The linkage object must contain a type and an id of the individual resource for the relationship.

Example resource identifier object

{
"data": {
"type": "policies",
"id": "76805397-9b46-4dcf-ad90-177a0f0969e2"
}
}

linkMetadata

Many resources including products, users, licenses, machines and policies have a metadata attribute. You can use this attribute to attach key-value data to the given resource.

Metadata is useful for storing additional, structured information on an object. As an example, you could store your user's zipcode, their id within your own database (if applicable), or their Stripe customer_id for billing purposes.

Since the metadata attribute is simply a key-value store (object), all write operations will overwrite the entire object, so be sure to merge existing data on your end when performing updates.

Do not attach sensitive billing information directly to resources using the metadata attribute. Doing so is a violation of our terms of use. If you're using Stripe, you need only attach their Stripe customer_id, or a temporary secure representation of a card (such as a stripe_token), which can then be used to look up their information server-side when required.

You may specify up to 64 metadata keys, with key names up to 256 characters in length and with values up to 512 characters in length.

Key Transformation

Please note: all keys will be transformed into lower camelcase (exampleKey), so we recommend using lower camelcase when possible. For example, example_key will be transformed into exampleKey, ExampleKey will also be transformed into exampleKey, etc.

Example Use Cases

Here's a few example metadata use cases:

Use Case
Link IDs Attach your system's unique IDs to a Keygen object, for easy lookups. For example, add an order number for a license, Stripe customer and subscription IDs, or the customer's user ID in your system.
License entitlements Store information about what features or actions a license is allowed to perform.
Customer details Annotate a user by storing additional details such as company name for later use.

Example request

const fetch = require("node-fetch")
 
const response = await fetch("https://api.keygen.sh/v1/accounts/{ACCOUNT}/licenses/a5a154d2-f026-40fa-bc8d-a7e3ca415298", {
method: "PATCH",
headers: {
"Content-Type": "application/vnd.api+json",
"Accept": "application/vnd.api+json",
"Authorization": "Bearer {TOKEN}"
},
body: JSON.stringify({
"data": {
"type": "licenses",
"attributes": {
"metadata": {
"customerEmail": "[email protected]",
"customerId": "cust_a7e3ca415298f0",
"isPro": true
}
}
}
})
})
 
const { data, errors } = await response.json()
import requests
import json
 
res = requests.patch(
"https://api.keygen.sh/v1/accounts/{ACCOUNT}/licenses/a5a154d2-f026-40fa-bc8d-a7e3ca415298",
headers={
"Content-Type": "application/vnd.api+json",
"Accept": "application/vnd.api+json",
"Authorization": "Bearer {TOKEN}"
},
data=json.dumps({
"data": {
"type": "licenses",
"attributes": {
"metadata": {
"customerEmail": "[email protected]",
"customerId": "cust_a7e3ca415298f0",
"isPro": true
}
}
}
})
).json()
import SwiftyJSON
import Alamofire
 
Alamofire.request("https://api.keygen.sh/v1/accounts/{ACCOUNT}/licenses/a5a154d2-f026-40fa-bc8d-a7e3ca415298",
method: .patch,
headers: [
"Content-Type": "application/vnd.api+json",
"Accept": "application/vnd.api+json",
"Authorization": "Bearer {TOKEN}"
],
parameters: [
"data": [
"type": "licenses",
"attributes": [
"metadata": [
"customerEmail": "[email protected]",
"customerId": "cust_a7e3ca415298f0",
"isPro": true
]
]
]
],
encoding: JSONEncoding.default
).responseJSON { response in
let json = JSON(data: response.data!)
}
using RestSharp;
 
var client = new RestClient("https://api.keygen.sh/v1/accounts/{ACCOUNT}");
var request = new RestRequest(
"licenses/a5a154d2-f026-40fa-bc8d-a7e3ca415298",
Method.PATCH
);
 
request.AddHeader("Content-Type", "application/vnd.api+json");
request.AddHeader("Accept", "application/vnd.api+json");
request.AddHeader("Authorization", "Bearer {TOKEN}");
 
request.AddJsonBody(new {
data = new {
type = "licenses",
attributes = new {
metadata = new {
customerEmail = "[email protected]",
customerId = "cust_a7e3ca415298f0",
isPro = true
}
}
}
});
 
var response = client.Execute(request);
import com.mashape.unirest.http.exceptions.*
import com.mashape.unirest.http.*
import org.json.*
 
val body = JSONObject(mapOf(
"data" to mapOf(
"type" to "licenses",
"attributes" to mapOf(
"metadata" to mapOf(
"customerEmail" to "[email protected]",
"customerId" to "cust_a7e3ca415298f0",
"isPro" to true
)
)
)
))
 
val res = Unirest.patch("https://api.keygen.sh/v1/accounts/{ACCOUNT}/licenses/a5a154d2-f026-40fa-bc8d-a7e3ca415298")
.header("Authorization", "Bearer {TOKEN}")
.header("Content-Type", "application/vnd.api+json")
.header("Accept", "application/vnd.api+json")
.body(body)
.asJson()
import com.mashape.unirest.http.exceptions.*;
import com.mashape.unirest.http.*;
import org.json.*;
 
import static java.util.Map.ofEntries;
import static java.util.Map.entry;
 
JSONObject body = new JSONObject(ofEntries(
entry("data", ofEntries(
entry("type", "licenses"),
entry("attributes", ofEntries(
entry("metadata", ofEntries(
entry("customerEmail", "[email protected]"),
entry("customerId", "cust_a7e3ca415298f0"),
entry("isPro", true)
))
))
))
));
 
HttpResponse<JsonNode> res = Unirest.patch("https://api.keygen.sh/v1/accounts/{ACCOUNT}/licenses/a5a154d2-f026-40fa-bc8d-a7e3ca415298")
.header("Authorization", "Bearer {TOKEN}")
.header("Content-Type", "application/vnd.api+json")
.header("Accept", "application/vnd.api+json")
.body(body)
.asJson();
#include <iostream>
#include <string>
#include <cpprest/http_client.h>
#include <cpprest/filestream.h>
 
using namespace std;
using namespace web;
using namespace web::http;
using namespace web::http::client;
using namespace web::json;
using namespace utility;
 
http_client client("https://api.keygen.sh/v1/accounts/{ACCOUNT}");
http_request req;
 
value metadata;
metadata["customerEmail"] = value::string("[email protected]");
metadata["customerId"] = "cust_a7e3ca415298f0";
metadata["isPro"] = true;
 
value attrs;
attrs["metadata"] = metadata;
 
value data;
data["type"] = value::string("licenses");
data["attributes"] = attrs;
 
value body;
body["data"] = data;
 
req.headers().add("Authorization", "Bearer {TOKEN}");
req.headers().add("Content-Type", "application/vnd.api+json");
req.headers().add("Accept", "application/json");
 
req.set_request_uri("/licenses/a5a154d2-f026-40fa-bc8d-a7e3ca415298");
req.set_method(methods::PATCH);
req.set_body(body.serialize());
 
client.request(req)
.then([](http_response res)
{
auto data = res.extract_json().get();
})
.wait();
curl -X PATCH https://api.keygen.sh/v1/accounts/{ACCOUNT}/licenses/a5a154d2-f026-40fa-bc8d-a7e3ca415298 \
-H 'Content-Type: application/vnd.api+json' \
-H 'Accept: application/vnd.api+json' \
-H 'Authorization: Bearer {TOKEN}' \
-d '{
"data": {
"type": "licenses",
"attributes": {
"metadata": {
"customerEmail": "[email protected]",
"customerId": "cust_a7e3ca415298f0",
"isPro": true
}
}
}
}'

linkPagination

All top-level API resources have support for bulk fetches via "list" API methods. For instance you can list users, list licenses, and list machines. These list API methods share a common format, taking an optional page query parameter.

Our API utilizes a page-based strategy via the page[size] and page[number] parameters. All results are returned in reverse chronological order.

linkQuery Parameters

  • linkpage

    object<string, integer>

    Object containing page size and page number.

  • linkpage[size]

    integer

    The page size. Must be a number between 1 and 100.

  • linkpage[number]

    integer

    The page number.

Example request

const fetch = require("node-fetch")
 
const response = await fetch("https://api.keygen.sh/v1/accounts/{ACCOUNT}/users?page[size]=25&page[number]=2", {
method: "GET",
headers: {
"Accept": "application/vnd.api+json",
"Authorization": "Bearer {TOKEN}"
}
})
 
const { data, links, errors } = await response.json()
import requests
import json
 
res = requests.get(
"https://api.keygen.sh/v1/accounts/{ACCOUNT}/users?page[size]=25&page[number]=2",
headers={
"Accept": "application/vnd.api+json",
"Authorization": "Bearer {TOKEN}"
}
).json()
import SwiftyJSON
import Alamofire
 
Alamofire.request("https://api.keygen.sh/v1/accounts/{ACCOUNT}/users?page[size]=25&page[number]=2",
headers: [
"Accept": "application/vnd.api+json",
"Authorization": "Bearer {TOKEN}"
]
).responseJSON { response in
let json = JSON(data: response.data!)
}
using RestSharp;
 
var client = new RestClient("https://api.keygen.sh/v1/accounts/{ACCOUNT}");
var request = new RestRequest("users", Method.GET);
 
request.AddHeader("Accept", "application/vnd.api+json");
request.AddHeader("Authorization", "Bearer {TOKEN}");
 
request.AddParameter("page[size]", 25);
request.AddParameter("page[number]", 2);
 
var response = client.Execute(request);
import com.mashape.unirest.http.exceptions.*
import com.mashape.unirest.http.*
 
val res = Unirest.get("https://api.keygen.sh/v1/accounts/{ACCOUNT}/users")
.header("Authorization", "Bearer {TOKEN}")
.header("Accept", "application/vnd.api+json")
.queryString("page[size]", 25)
.queryString("page[number]", 2)
.asJson()
import com.mashape.unirest.http.exceptions.*;
import com.mashape.unirest.http.*;
 
HttpResponse<JsonNode> res = Unirest.get("https://api.keygen.sh/v1/accounts/{ACCOUNT}/users")
.header("Authorization", "Bearer {TOKEN}")
.header("Accept", "application/vnd.api+json")
.queryString("page[size]", 25)
.queryString("page[number]", 2)
.asJson();
#include <iostream>
#include <string>
#include <cpprest/http_client.h>
#include <cpprest/filestream.h>
 
using namespace std;
using namespace web;
using namespace web::http;
using namespace web::http::client;
using namespace utility;
 
http_client client("https://api.keygen.sh/v1/accounts/{ACCOUNT}");
http_request req;
 
req.headers().add("Authorization", "Bearer {TOKEN}");
req.headers().add("Accept", "application/json");
 
uri_builder uri("/users");
uri.append_query("page[size]", 25);
uri.append_query("page[number]", 2);
 
req.set_request_uri(uri.to_uri());
req.set_method(methods::GET);
 
client.request(req)
.then([](http_response res) {
auto data = res.extract_json().get();
})
.wait();
curl 'https://api.keygen.sh/v1/accounts/{ACCOUNT}/users?page[size]=25&page[number]=2' -g \
-H 'Accept: application/vnd.api+json' \
-H 'Authorization: Bearer {TOKEN}'

Example response

{
"data": [
{
"id": "6ed498fe-5f5f-49af-82ee-cb7400cc4522",
"type": "users",
"links": {
"self": "/v1/accounts/{ACCOUNT}/users/6ed498fe-5f5f-49af-82ee-cb7400cc4522"
},
"attributes": {
"fullName": "Marty McFly",
"firstName": "Marty",
"lastName": "McFly",
"email": "[email protected]",
"role": "user",
"metadata": {},
"created": "2017-01-02T19:48:50.077Z",
"updated": "2017-01-02T19:48:50.077Z"
},
"relationships": {
"account": {
"links": {
"related": "/v1/accounts/{ACCOUNT}"
},
"data": {
"type": "accounts",
"id": "{ACCOUNT}"
}
},
"products": {
"links": {
"related": "/v1/accounts/{ACCOUNT}/users/a5a154d2-f026-40fa-bc8d-a7e3ca415298/products"
}
},
"licenses": {
"links": {
"related": "/v1/accounts/{ACCOUNT}/users/a5a154d2-f026-40fa-bc8d-a7e3ca415298/licenses"
}
},
"machines": {
"links": {
"related": "/v1/accounts/{ACCOUNT}/users/a5a154d2-f026-40fa-bc8d-a7e3ca415298/machines"
}
},
"tokens": {
"links": {
"related": "/v1/accounts/{ACCOUNT}/users/a5a154d2-f026-40fa-bc8d-a7e3ca415298/tokens"
}
}
}
},
],
"links": {
"self": "/v1/accounts/{ACCOUNT}/users?page[size]=25&page[number]=2",
"prev": "/v1/accounts/{ACCOUNT}/users?page[size]=25&page[number]=1",
"next": "/v1/accounts/{ACCOUNT}/users?page[size]=25&page[number]=3",
"first": "/v1/accounts/{ACCOUNT}/users?page[size]=25&page[number]=1",
"last": "/v1/accounts/{ACCOUNT}/users?page[size]=25&page[number]=4",
"meta": {
"pages": 4,
"total": 98
}
}
}

linkIdempotency

Some resources, such as webhook events may contain an idempotency token inside of a meta object. In the case of webhook events, these tokens allow you to safely retry events without accidentally responding to the same event multiple times.

linkExample scenario

When a user creates a new license, you would want to make sure that the license.created webhook event that you're listening for would only be acted upon once, regardless of how many times you retry the event, to guarantee you only charge your user for a single license.

You can accomplish this by logging the idempotency token (to a database, for example, Redis) and ignoring future webhook events that come through with an identical token, signaling a retried event.

Although retrying a webhook event creates a new resource, the idempotency token will stay the same throughout the event's lifetime.

Example resource

A failed webhook event resource.

{
"data": {
"id": "2bc99fe1-f315-4877-ac5f-542240c4e883",
"type": "webhook-events",
"meta": {
"idempotencyToken": "e8e0fbb598e8bcfd0e94ceb79199edc79e6ab53f4a4bbb32d7aede7964e7c3v2",
},
"links": {
"self": "/v1/accounts/{ACCOUNT}/webhook-events/2bc99fe1-f315-4877-ac5f-542240c4e883"
},
"attributes": {
"endpoint": "https://example.com/webhooks",
"payload": "{\"data\":{…}}",
"event": "license.created",
"status": "failed",
"lastResponseCode": 500,
"lastResponseBody": "Internal Server Error",
"created": "2017-01-02T20:26:53.464Z",
"updated": "2017-01-02T20:26:53.464Z"
},
"relationships": {
"account": {
"links": {
"related": "/v1/accounts/{ACCOUNT}"
},
"data": {
"type": "accounts",
"id": "{ACCOUNT}"
}
}
}
}
}

Example request

Retry the above failed webhook event resource.

const fetch = require("node-fetch")
 
const response = await fetch("https://api.keygen.sh/v1/accounts/{ACCOUNT}/webhook-events/2bc99fe1-f315-4877-ac5f-542240c4e883/actions/retry", {
method: "POST",
headers: {
"Accept": "application/vnd.api+json",
"Authorization": "Bearer {TOKEN}"
}
})
 
const { data, errors } = await response.json()
import requests
import json
 
res = requests.post(
"https://api.keygen.sh/v1/accounts/{ACCOUNT}/webhook-events/2bc99fe1-f315-4877-ac5f-542240c4e883/actions/retry",
headers={
"Accept": "application/vnd.api+json",
"Authorization": "Bearer {TOKEN}"
}
).json()
import SwiftyJSON
import Alamofire
 
Alamofire.request("https://api.keygen.sh/v1/accounts/{ACCOUNT}/webhook-events/2bc99fe1-f315-4877-ac5f-542240c4e883/actions/retry",
method: .post,
headers: [
"Accept": "application/vnd.api+json",
"Authorization": "Bearer {TOKEN}"
]
).responseJSON { response in
let json = JSON(data: response.data!)
}
using RestSharp;
 
var client = new RestClient("https://api.keygen.sh/v1/accounts/{ACCOUNT}");
var request = new RestRequest(
"webhook-events/2bc99fe1-f315-4877-ac5f-542240c4e883/actions/retry",
Method.POST
);
 
request.AddHeader("Accept", "application/vnd.api+json");
request.AddHeader("Authorization", "Bearer {TOKEN}");
 
var response = client.Execute(request);
import com.mashape.unirest.http.exceptions.*
import com.mashape.unirest.http.*
 
val res = Unirest.post("https://api.keygen.sh/v1/accounts/{ACCOUNT}/webhook-events/2bc99fe1-f315-4877-ac5f-542240c4e883/actions/retry")
.header("Authorization", "Bearer {TOKEN}")
.header("Accept", "application/vnd.api+json")
.asJson()
import com.mashape.unirest.http.exceptions.*;
import com.mashape.unirest.http.*;
 
HttpResponse<JsonNode> res = Unirest.post("https://api.keygen.sh/v1/accounts/{ACCOUNT}/webhook-events/2bc99fe1-f315-4877-ac5f-542240c4e883/actions/retry")
.header("Authorization", "Bearer {TOKEN}")
.header("Accept", "application/vnd.api+json")
.asJson();
#include <iostream>
#include <string>
#include <cpprest/http_client.h>
#include <cpprest/filestream.h>
 
using namespace std;
using namespace web;
using namespace web::http;
using namespace web::http::client;
using namespace utility;
 
http_client client("https://api.keygen.sh/v1/accounts/{ACCOUNT}");
http_request req;
 
req.headers().add("Authorization", "Bearer {TOKEN}");
req.headers().add("Accept", "application/json");
 
req.set_request_uri("/webhook-events/2bc99fe1-f315-4877-ac5f-542240c4e883/actions/retry");
req.set_method(methods::POST);
 
client.request(req)
.then([](http_response res) {
auto data = res.extract_json().get();
})
.wait();
curl -X POST https://api.keygen.sh/v1/accounts/{ACCOUNT}/webhook-events/2bc99fe1-f315-4877-ac5f-542240c4e883/actions/retry \
-H 'Accept: application/vnd.api+json' \
-H 'Authorization: Bearer {TOKEN}'

Example response

Notice that it's a new resource, yet the idempotency token matches the original event.

{
"data": {
"id": "1bda5fd5-6d82-49f5-b5b8-71bb432a32bb",
"type": "webhook-events",
"meta": {
"idempotencyToken": "e8e0fbb598e8bcfd0e94ceb79199edc79e6ab53f4a4bbb32d7aede7964e7c3v2",
},
"links": {
"self": "/v1/accounts/{ACCOUNT}/webhook-events/1bda5fd5-6d82-49f5-b5b8-71bb432a32bb"
},
"attributes": {
"endpoint": "https://example.com/webhooks",
"payload": "{\"data\":{…}}",
"event": "license.created",
"status": "queued",
"lastResponseCode": null,
"lastResponseBody": null,
"created": "2017-01-02T20:26:53.464Z",
"updated": "2017-01-02T20:26:53.464Z"
},
"relationships": {
"account": {
"links": {
"related": "/v1/accounts/{ACCOUNT}"
},
"data": {
"type": "accounts",
"id": "{ACCOUNT}"
}
}
}
}
}

linkChecking connectivity

You can detect network connectivity and check if our licensing servers are accessible for the current network environment by sending a GET request to the ping endpoint. The API endpoint will respond with a 200 HTTP status code.

Definition

https://api.keygen.sh/v1/ping

Example request

Ping our licensing servers.

const fetch = require("node-fetch")
 
const response = await fetch("https://api.keygen.sh/v1/ping", { method: "GET" })
import requests
 
res = requests.get("https://api.keygen.sh/v1/ping")
import SwiftyJSON
import Alamofire
 
Alamofire.request("https://api.keygen.sh/v1/ping", method: .get)
using RestSharp;
 
var client = new RestClient("https://api.keygen.sh");
var request = new RestRequest("/v1/ping", Method.GET);
var response = client.Execute(request);
import com.mashape.unirest.http.exceptions.*
import com.mashape.unirest.http.*
 
val res = Unirest.get("https://api.keygen.sh/v1/ping")
import com.mashape.unirest.http.exceptions.*;
import com.mashape.unirest.http.*;
 
HttpResponse<JsonNode> res = Unirest.get("https://api.keygen.sh/v1/ping");
#include <iostream>
#include <string>
#include <cpprest/http_client.h>
#include <cpprest/filestream.h>
 
using namespace std;
using namespace web;
using namespace web::http;
using namespace web::http::client;
using namespace utility;
 
http_client client("https://api.keygen.sh");
http_request req;
 
req.set_request_uri("/v1/ping");
req.set_method(methods::GET);
 
client.request(req).wait();
curl -X GET https://api.keygen.sh/v1/ping

Example response / 200 OK

No content

linkCryptography

Each Keygen account is equipped with multiple unique public/private keypairs**, which are used for example, in signing response payloads using RSA-SHA256, and which can also be used to sign or encrypt license keys for added security and offline-capable license verification using cryptographic schemes. The algorithm used for license key signing and encryption will depend on the cryptographic scheme that the particular policy implements.

You can find your account's public keys within your dashboard settings page, which you can use to verify response payloads, webhooks and license keys. Private keys are kept securely encrypted on our servers and never shared.

To see an example of cryptographic validation, check out our example on GitHub. It will show you how to utilize your account's public key to validate various cryptographic license key schemes.

linkCryptographic keys

Below you will find information on various license key encryption and signature schemes. These can be used verified in offline or air-gapped environments, or to increase security and confidence for embedded key datasets. Most embedded datasets have a default, but they can be changed to include more data, or less data. The sky's the limit.

Once a license key is created, it cannot be changed, i.e. license keys are immutable. This means the dataset that you choose to embed into a license key cannot be changed, and changes to the license object itself have no effect on the embedded dataset.

Please take this into account when crafting your embedded dataset. If you plan on renewing licenses after or before an expiration date, you may wish to embed the amount of time a license is allowed to be used offline, e.g. a numeric duration such as 1 year, instead of a hard expiration date.

If you need to change the dataset, e.g. to extend an embedded expiration date after renewal, a new license key will need to be created. This is not a limitation of cryptographic keys or of Keygen, but a feature of cryptography which actually shows how keys are 100% tamper-proof — any data modification invalidates the signature.

Cryptographic keys can be used as a typical 'license file.' You can pack them up as a text file and distribute them to your users. The integrity of the file can then be verified by verifying the key's signature, or decrypting the key value. Some businesses prefer this over distributing the relatively large cryptographic key in text form.

linkED25519_SIGN

Sign license keys with your account's Ed25519 signing key. This is our recommended signing scheme, due to its smaller signature size, and higher security level, when compared to 2048-bit RSA.

key/emVrZUBrZXlnZW4uZXhhbXBsZQ==.D1pk7hqD_KNV9UNpl1IDQqpa4qaBjFBNrtP74yEM7v0xCmLRrDhZdSHGx47TN2RKARfGIigX9tE4yVOjhQvfAQ==

The final signed key consists of the following format:

SIGNING_PREFIX = "key"
SIGNING_DATA = "{SIGNING_PREFIX}/{BASE64_DATASET}"
BASE64_SIGNATURE = ed25519_sign(SIGNING_DATA)
"{SIGNING_DATA}.{BASE64_SIGNATURE}"

An embedded dataset may be given during license creation via the key attribute. This dataset will be used to create the key signature, and it will be encoded for easier transfer. It can be decoded within your software and its authenticity can be verified cryptographically. The default dataset is a JSON object, as follows:

{
"account": {
"id": "fa4e22c6-436f-4134-b976-79df910acf69"
},
"product": {
"id": "2c035cde-aa1b-47c1-bdbe-df0b29d8c5fb"
},
"policy": {
"id": "6e29580f-a3eb-49e3-8820-f6e03ae9230b",
"duration": null
},
"user": null,
"license": {
"id": "fd293be1-f605-422d-a022-37946f58c0fd",
"created": "2021-03-22T12:46:18.217Z",
"expiry": null
}
}

The user property may be null depending on the license's user relationship. The license's expiry and policy's duration attributes may also be null if the license does not expire.

linkRSA_2048_PKCS1_PSS_SIGN_V2

Sign license keys with your account's 2048-bit RSA private key using RSA PKCS1-PSS padding, with a SHA256 digest, max salt length, and a SHA256 MGF1. We recommend verifying with a salt length of auto when available.

key/eyJhY2NvdW50Ijp7ImlkIjoiZmE0ZTIyYzYtNDM2Zi00MTM0LWI5NzYtNzlkZjkxMGFjZjY5In0sInByb2R1Y3QiOnsiaWQiOiIyYzAzNWNkZS1hYTFiLTQ3YzEtYmRiZS1kZjBiMjlkOGM1ZmIifSwicG9saWN5Ijp7ImlkIjoiNmUyOTU4MGYtYTNlYi00OWUzLTg4MjAtZjZlMDNhZTkyMzBiIiwiZHVyYXRpb24iOm51bGx9LCJ1c2VyIjpudWxsLCJsaWNlbnNlIjp7ImlkIjoiZmQyOTNiZTEtZjYwNS00MjJkLWEwMjItMzc5NDZmNThjMGZkIiwiY3JlYXRlZCI6IjIwMjEtMDMtMjJUMTI6NDY6MTguMjE3WiIsImV4cGlyeSI6bnVsbH19.ZIXTH2HhILamEedSXoFSn9EFloOOUtPulq37T7YnJJolRF18-NaMb7L22ozG2v1NtioX5ZK20iSmGnTy6dLsPjaK3PpM1gL4BAD4yQuU979EuyAz7j169lpiyBtx7ytOTIJ_hr1aAMotl97BY8mBQAn-ASctNnmLqeYD0IvVniMBPes64FM5GPfyK8IIdfX23XRue3HW1nHiPBFvmiaH-_WSY81cl6fQYdrmbGJn0FSP9QYK2-CCsw1OzE_q47YmH5-Z_-VzfFnEBWLsrSvlz3HRlD5N6QhT_kl4jkN-7hou76vS1-P_DN-VD5rNXQSxKxPnbFvtSIX3rk0WCSw3rQ==

The final signed key consists of the following format:

SIGNING_PREFIX = "key"
SIGNING_DATA = "{SIGNING_PREFIX}/{BASE64_DATASET}"
BASE64_SIGNATURE = rsa_pkcs1_pss_sign(SIGNING_DATA)
"{SIGNING_DATA}.{BASE64_SIGNATURE}"

An embedded dataset may be given during license creation via the key attribute. This dataset will be used to create the key signature, and it will be encoded for easier transfer. It can be decoded within your software and its authenticity can be verified cryptographically. The default dataset is a JSON object, as follows:

{
"account": {
"id": "fa4e22c6-436f-4134-b976-79df910acf69"
},
"product": {
"id": "2c035cde-aa1b-47c1-bdbe-df0b29d8c5fb"
},
"policy": {
"id": "6e29580f-a3eb-49e3-8820-f6e03ae9230b",
"duration": null
},
"user": null,
"license": {
"id": "fd293be1-f605-422d-a022-37946f58c0fd",
"created": "2021-03-22T12:46:18.217Z",
"expiry": null
}
}

The user property may be null depending on the license's user relationship. The license's expiry and policy's duration attributes may also be null if the license does not expire.

linkRSA_2048_PKCS1_SIGN_V2

Sign license keys with your account's 2048-bit RSA private key using RSA PKCS1 v1.5 padding, with a SHA256 digest.

key/eyJhY2NvdW50Ijp7ImlkIjoiZmE0ZTIyYzYtNDM2Zi00MTM0LWI5NzYtNzlkZjkxMGFjZjY5In0sInByb2R1Y3QiOnsiaWQiOiIyYzAzNWNkZS1hYTFiLTQ3YzEtYmRiZS1kZjBiMjlkOGM1ZmIifSwicG9saWN5Ijp7ImlkIjoiNmUyOTU4MGYtYTNlYi00OWUzLTg4MjAtZjZlMDNhZTkyMzBiIiwiZHVyYXRpb24iOjMxNTU2OTUyfSwidXNlciI6eyJpZCI6ImE0OTliYjkzLTk5MDItNGI1Mi04YTA0LTc2OTQ0YWQ3ZjY2MCIsImVtYWlsIjoidXNlckBrZXlnZW4uZXhhbXBsZSJ9LCJsaWNlbnNlIjp7ImlkIjoiZjJjNjU2MDItOTJhOC00MTFmLTlmZWYtZWRhMGE2NjJmOWI2IiwiY3JlYXRlZCI6IjIwMjEtMDMtMjJUMTI6NTE6MjYuMTI5WiIsImV4cGlyeSI6IjIwMjItMDMtMjJUMTI6NTE6MjYuMTI5WiJ9fQ==.Jfs9Xu3_Osqf0dSQpI766WHVG9IacWbIzcQHJrPQR1zwriwjW0v1aR6AYTDOHXfNp81O33kWK23MOf-kkC5U9Gkiq_7ca7FdHLgzLGjZ3216QigxCweOWAHzit37kvaDvXinyS3ceGvDI6tt_FldcoaByHJXTovKjsQzRYaGJmI9r8wfgBor5nQ6NCTm3v6lYA9Ae_6Rs3AR7DgMM2kEr0GAhDNja_VyrZthsrRsU5r5ELo4AlVCdzdhNCD8KzA4PPrD_KsCaKGjQak1sd0kXYx-IOr5vXJpiuhgyGdGyKVSKOejO_2TbY2oxuVGNg6aWGxv0hEiXJuChAzAI7_NmA==

This key consists of the following format:

SIGNING_PREFIX = "key"
SIGNING_DATA = "{SIGNING_PREFIX}/{BASE64_DATASET}"
BASE64_SIGNATURE = rsa_pkcs1_sign(SIGNING_DATA)
"{SIGNING_DATA}.{BASE64_SIGNATURE}"

An embedded dataset may be given during license creation via the key attribute. This dataset will be used to create the key signature, and it will be encoded for easier transfer. It can be decoded within your software and its authenticity can be verified cryptographically. The default dataset is a JSON object, as follows:

{
"account": {
"id": "fa4e22c6-436f-4134-b976-79df910acf69"
},
"product": {
"id": "2c035cde-aa1b-47c1-bdbe-df0b29d8c5fb"
},
"policy": {
"id": "6e29580f-a3eb-49e3-8820-f6e03ae9230b",
"duration": 31556952
},
"user": {
"id": "a499bb93-9902-4b52-8a04-76944ad7f660",
"email": "[email protected]"
},
"license": {
"id": "f2c65602-92a8-411f-9fef-eda0a662f9b6",
"created": "2021-03-22T12:51:26.129Z",
"expiry": "2022-03-22T12:51:26.129Z"
}
}

The user property may be null depending on the license's user relationship. The license's expiry and policy's duration attributes may also be null if the license does not expire.

linkRSA_2048_PKCS1_ENCRYPT

Encrypt license keys with your account's 2048-bit RSA private key using RSA PKCS1 v1.5 padding.

S-Pg5rnVDE-7hG5Ep0RlL_6yah04aDqo9zhQR04VFurWJyq9GrQdODh90DlkzNBeFoypt6tD1_QxUecw24XqLQrhu3wRBiD4sDxWKIWX5PehyLSdGxD-U_zpZ5yVDw1Cexp9bivIWcV_Ld7Og5H56cN092iFjlctJ-HI1IPuk6W1hYxWlVGchasOshMyQdE-BLsW7PaeYqUHC5jQdo3huHX17p0GcaRMepvXzBbqjABJyUFZIwXue-ApadK2H0ActISG-zMJGF7uqpNrFCKMYISDWPAkmYSa_jZHIvR7uoTGNlaAcWwVhQDKt7XH1eVaZxZZtvQrTvoH72Mcv5-0kA==

This key consists of the following format:

BASE64_CIPHERTEXT = rsa_pkcs1_encrypt(BASE64_DATASET)
"{BASE64_CIPHERTEXT}"

An embedded dataset may be given during license creation via the key attribute. The dataset must contain no more than 245 bytes (please note this is byte length not string length). The default dataset is a JSON object, as follows:

{
"id": "4040b22a-73df-48b5-8f90-bacacd2b6602",
"created": "2021-03-15T19:27:50.440Z",
"expiry": "2022-03-15T19:27:50.440Z",
"duration": 31556952
}

The expiry and duration attributes may be null if the license does not expire.

linkRSA_2048_JWT_RS256

Encode a license claims payload into a JWT using the RS256 algorithm.

eyJhbGciOiJSUzI1NiJ9.eyJqdGkiOiJjMGM4ZGMyMy03MDI1LTRmNjQtYWUwOC1iMzg4OWZlYmEwM2IiLCJpc3MiOiJodHRwczovL2tleWdlbi5zaCIsImF1ZCI6ImJmOWI1MjNmLWRkNjUtNDhhMi05NTEyLWZiNjZiYTZjMzcxNCIsInN1YiI6IjU0YjI5ODU2LWE0MjgtNGQyNy05NjY2LTliNjRlMWJmODZlZSIsImlhdCI6MTYxNTgzNzEyOCwibmJmIjoxNjE1ODM3MTI4LCJleHAiOjE2NDczNzMxMjh9.MSoZ4dqwTU0TIS_oOXMT20ViVoYDhLVn7dbOmo5Y3LAoy2DIt54jGL2PXl2G-Ov3DBSGhkp-nYbO6miigoXSPhx9okVnoyM78qbVUZVLmxcEiQ3SUrL_9oUhJul3DleQSFGvYPhGGtmK1H0xZuN5yodBY5OY6w2OJ5omOnq6hE8NdxLgZSwUb2POmtUlzUzx0kSqiG2tt1NW-j5OnGGgy1nqMDj1Cv5suoplmnCEajYd1x2w6G0Gmb9tg_iZbBkfh4tlidltK9uCiNWnHvsg3S-X3biSWQeRXP-2_BRdGAyrI5cvwZbWHPr8QdSsHTnOqUwCQ22A5YTEAGiSYp9lvQ

A custom JWT claims payload may be given during license creation via the key attribute. The default claims are as follows:

{
"jti": "c0c8dc23-7025-4f64-ae08-b3889feba03b",
"iss": "https://keygen.sh",
"aud": "bf9b523f-dd65-48a2-9512-fb66ba6c3714",
"sub": "54b29856-a428-4d27-9666-9b64e1bf86ee",
"iat": 1615837128,
"nbf": 1615837128,
"exp": 1647373128
}

Where aud is the account ID and sub is the license ID. The exp attribute may be omitted if the license does not expire.

linkRSA_2048_PKCS1_PSS_SIGN

Deprecated: use RSA_2048_PKCS1_PSS_SIGN_V2. Sign license keys with your account's 2048-bit RSA private key using RSA PKCS1-PSS padding, with a SHA256 digest, max salt length, and a SHA256 MGF1.

eyJhY2NvdW50Ijp7ImlkIjoiZmE0ZTIyYzYtNDM2Zi00MTM0LWI5NzYtNzlkZjkxMGFjZjY5In0sInByb2R1Y3QiOnsiaWQiOiIyYzAzNWNkZS1hYTFiLTQ3YzEtYmRiZS1kZjBiMjlkOGM1ZmIifSwicG9saWN5Ijp7ImlkIjoiNmUyOTU4MGYtYTNlYi00OWUzLTg4MjAtZjZlMDNhZTkyMzBiIiwiZHVyYXRpb24iOjMxNTU2OTUyfSwidXNlciI6eyJpZCI6ImE0OTliYjkzLTk5MDItNGI1Mi04YTA0LTc2OTQ0YWQ3ZjY2MCIsImVtYWlsIjoidXNlckBrZXlnZW4uZXhhbXBsZSJ9LCJsaWNlbnNlIjp7ImlkIjoiOGIwZmU2MGQtMGJjOS00Zjk4LWEwNDctZTZmOGNhMWQyNThjIiwiY3JlYXRlZCI6IjIwMjEtMDMtMjJUMTI6NTI6NTAuNjgyWiIsImV4cGlyeSI6IjIwMjItMDMtMjJUMTI6NTI6NTAuNjgyWiJ9fQ==.l2ZyC7Hc-0XrEtkhldI5c1tsLt_AY8eeW96PdeR1C3Z8F6X7xrdtXBum5UDCR2dIEf552eJY91l3cVwmFVvLTqNUWGFRDzoKHVH8w2ddwocWXSHRevBLCbfj6BLMLxBCFZOiCxQdEsoA93JcsuZmijwvphG3s26F3u5MRz5wIciE6Q4a9adKnCBhcPYP4B_ZfUwXmImMsTkqlP7x5jr8yIViFSFkEiiH4tp_xQySWtMpKBuV18LWq07KrIorHT8mrSET7EzHUNRrbE7x9J2lol5-aR8A2-7_rmM042sqvhS6EQOYqxTigPpAWILK8pT3AqnJ8o2WnZI-bks8Lbc3Mg==

This key consists of the following format:

BASE64_SIGNATURE = rsa_pkcs1_pss_sign(BASE64_DATASET)
"{BASE64_DATASET}.{BASE64_SIGNATURE}"

An embedded dataset may be given during license creation via the key attribute. The default dataset is a JSON object, as follows:

{
"account": {
"id": "fa4e22c6-436f-4134-b976-79df910acf69"
},
"product": {
"id": "2c035cde-aa1b-47c1-bdbe-df0b29d8c5fb"
},
"policy": {
"id": "6e29580f-a3eb-49e3-8820-f6e03ae9230b",
"duration": 31556952
},
"user": {
"id": "a499bb93-9902-4b52-8a04-76944ad7f660",
"email": "[email protected]"
},
"license": {
"id": "8b0fe60d-0bc9-4f98-a047-e6f8ca1d258c",
"created": "2021-03-22T12:52:50.682Z",
"expiry": "2022-03-22T12:52:50.682Z"
}
}

The user property may be null depending on the license's user relationship. The license's expiry and policy's duration attributes may also be null if the license does not expire.

linkRSA_2048_PKCS1_SIGN

Deprecated: use RSA_2048_PKCS1_SIGN_V2. Sign license keys with your account's 2048-bit RSA private key using RSA PKCS1 v1.5 padding, with a SHA256 digest.

Here is an example of a cryptographically signed key using RSA_2048_PKCS1_PSS_SIGN:

eyJhY2NvdW50Ijp7ImlkIjoiZmE0ZTIyYzYtNDM2Zi00MTM0LWI5NzYtNzlkZjkxMGFjZjY5In0sInByb2R1Y3QiOnsiaWQiOiIyYzAzNWNkZS1hYTFiLTQ3YzEtYmRiZS1kZjBiMjlkOGM1ZmIifSwicG9saWN5Ijp7ImlkIjoiNmUyOTU4MGYtYTNlYi00OWUzLTg4MjAtZjZlMDNhZTkyMzBiIiwiZHVyYXRpb24iOjMxNTU2OTUyfSwidXNlciI6eyJpZCI6ImE0OTliYjkzLTk5MDItNGI1Mi04YTA0LTc2OTQ0YWQ3ZjY2MCIsImVtYWlsIjoidXNlckBrZXlnZW4uZXhhbXBsZSJ9LCJsaWNlbnNlIjp7ImlkIjoiZTI2NDdkMzAtYmYyOS00N2U0LTliYjktZWU4NzRmNDI2YjI2IiwiY3JlYXRlZCI6IjIwMjEtMDMtMjJUMTI6NTM6MjAuNDE0WiIsImV4cGlyeSI6IjIwMjItMDMtMjJUMTI6NTM6MjAuNDE0WiJ9fQ==.QmWjSqJ8yfvTcf3T0sKWUXrImaFudHVR8_WLatIVNfAciJW7__70RtOyZ2ZnN4WvyDEb37Df-hal5-zKugrS9a9OCXP_NbNusQALdSqigQH-Jkekd_X0Xnp6F2v6z9SQVgrt2_kMxQ9m2WzFXVn6R_SfW-ey_aGcX3JW7th8CsX5rGr93sb7DjR7-femwS-rGLy_t3cyqf-kYP6XX0krv4BUJi5vYcEC9MnTNDkoTervk6nczAjQhybJmD5aah2c9opbRT4R0k0wCosEYkNrsdnmj1uB2AshcmpmhemnmTxjmNvKvYO3gfgGbfIDSXSzRQhzErVwN_wLr1XANmzQYQ==

This key consists of the following format:

BASE64_SIGNATURE = rsa_pkcs1_sign(BASE64_DATASET)
"{BASE64_DATASET}.{BASE64_SIGNATURE}"

An embedded dataset may be given during license creation via the key attribute. The default dataset is a JSON object, as follows:

{
"account": {
"id": "fa4e22c6-436f-4134-b976-79df910acf69"
},
"product": {
"id": "2c035cde-aa1b-47c1-bdbe-df0b29d8c5fb"
},
"policy": {
"id": "6e29580f-a3eb-49e3-8820-f6e03ae9230b",
"duration": 31556952
},
"user": {
"id": "a499bb93-9902-4b52-8a04-76944ad7f660",
"email": "[email protected]"
},
"license": {
"id": "e2647d30-bf29-47e4-9bb9-ee874f426b26",
"created": "2021-03-22T12:53:20.414Z",
"expiry": "2022-03-22T12:53:20.414Z"
}
}

The user property may be null depending on the license's user relationship. The license's expiry and policy's duration attributes may also be null if the license does not expire.

linkSignatures

You may utilize your account's public key to verify that API responses and webhook requests all originated from Keygen, as well as to verify a license key is "authentic" i.e. that it was signed using your Keygen account's private key.

You can find your account's public key within your dashboard settings page, which you can use to verify response payloads and license keys. Private keys are kept securely encrypted on our servers and are never, under any circumstances, shared with you or any other third-party.

linkLicense Signatures

License signature verification is useful for checking if a given license key is authentic, especially in offline environments, where access to Keygen's API to fully validate the license is not available. For more information on license key cryptography and signatures, see the info below, view the cryptography section and review the various policy schemes available.

Here are a few examples of cryptographically verifying a license's authenticity:

The signed or encrypted contents of the key are base64url encoded using RFC 4648, a URL-safe version of base64 which is supported in most programming languages. This base64url encoding scheme is different than normal base64 encoding. Most programming languages will have a separate function for decoding URL base64 encoded values, but if not, you can simply replace all "-" chars with "+", and replace "_" with "/", e.g. tr '-_' '+/' to convert the encoded string from base64url encoding to standard base64 encoding.

Example RSA_2048_PKCS1_SIGN_V2 verification using Python

from Crypto.PublicKey import RSA
from Crypto.Signature import PKCS1_v1_5
from Crypto.Hash import SHA256
import base64
 
# This should be replaced with your Keygen account's public key (note: all newlines and whitespace must be *exact*)
PUBLIC_KEY = \
"""-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzPAseDYupK78ZUaSbGw7
YyUCCeKo/1XqTACOcmTTHHGgeHacLK2j9UrbTlhW5h8Vyo0iUEHrY1Kgf4wwiGgF
h0Yc+oDWDhq1bIertI03AE420LbpUf6OTioX+nY0EInxXF3J7aAdx/R/nYgRJrLZ
9ATWaQVSgf3vtxCtCwUeKxKZI41GA/9KHTcCmd3BryAQ1piYPr+qrEGf2NDJgr3W
vVrMtnjeoordAaCTyYKtfm56WGXeXr43dfdejBuIkI5kqSzwVyoxhnjE/Rj6xks8
ffH+dkAPNwm0IpxXJerybjmPWyv7iyXEUN8CKG+6430D7NoYHp/c991ZHQBUs59g
vwIDAQAB
-----END PUBLIC KEY-----"""
 
# This should be the license key that you're cryptographically verifying
LICENSE_KEY = \
"""key/eyJhY2NvdW50Ijp7ImlkIjoiYmY5YjUyM2YtZGQ2NS00OGEyLTk1MTItZmI2NmJhNmMzNzE0In0sInByb2R1Y3QiOnsiaWQiOiI5NTYxYzdkMC1mYzczLTRjOTQtYTZlZC0xY2M3MmEzZTAzNzYifSwicG9saWN5Ijp7ImlkIjoiOTRiYTA5YjYtMzI5ZC00NWNjLTg4ZjctNzM5N2Y2YzgwNDg4In0sInVzZXIiOnsiaWQiOiIxYmEwYzk4Yy0wMGRhLTQ2MjEtOTljNS1jMWU1ZjVkZjk1NzUiLCJlbWFpbCI6InVzZXJAYXBwLmV4YW1wbGUifSwibGljZW5zZSI6eyJpZCI6IjE2YmI3YmM2LTBlNDItNDYyZS04OWE1LWVmNzdjNDYwNzJjYiIsImNyZWF0ZWQiOiIyMDIxLTAzLTE2VDEzOjE4OjE3LjU1M1oiLCJleHBpcnkiOiIyMDIyLTAzLTE2VDEzOjE4OjE3LjU1M1oifX0=.K0VwYlN3LVdRniqL5lpDDqticp1-LhmW-WEyRmK7rhGHFhBMThHMp1qjzpdSHaV4bKpTFQgnmj5p-Bw7n9nKnYZ35kmc_2bnid-69q0G6sA2iXEtS3I5717cLArN7HL5l-lBTYZwSTbq_C_NnnpvxQ2k8aMSOwP9ZkV8SECee9VOEniDD7URZIYMf06JVzSsfprAaEcXEGaSK_LXw5y0cy0o9hcmPC50JdWPPxQtj13DKt0Yc_0CkyHvVfZO8h_hdRf5JSCW9UZumUrHk_wqQf3j-DBfmOytKTidmsIaQqE2KyoJh_-U1T1z4fx44wYDFTLEncr4uWX3zzUTf_cVIA=="""
 
# Split license key to obtain signing data and signature, then parse data
# and decode base64url encoded values
signing_data, enc_sig = LICENSE_KEY.split(".")
signing_prefix, enc_key = signing_data.split("/")
 
key = base64.urlsafe_b64decode(enc_key)
sig = base64.urlsafe_b64decode(enc_sig)
 
# Verify the key's signature
pub_key = RSA.importKey(PUBLIC_KEY)
verifier = PKCS1_v1_5.new(pub_key)
digest = SHA256.new(data="key/" + enc_key)
 
print(
verifier.verify(digest, sig)
)

linkResponse Signatures

Response signature verification is useful for a variety of scenarios where verifying that a response came from Keygen's servers is vital, such as:

  1. Preventing man-in-the-middle attacks. Verifying responses using your public keys will ensure that only responses signed using your private key are accepted. We are the only ones in possession of your account's private keys, so you can rest assured the response was from us and that it has not been altered.
  2. Preventing spoofing attacks. For example, a bad actor could redirect requests to a local licensing server under their control, which defaults to sending "valid" responses. Much like with preventing man-in-the-middle attacks, cryptographic signatures give confidence that the response was from Keygen's servers.
  3. Preventing replay attacks. For example, a bad actor could "record" web traffic between Keygen and your software, and "replay" valid responses, such as replaying responses that occurred before their trial license expires, in order to use your software with an expired license. If the signature is valid, but the response date is older than 5 minutes, we recommend rejecting the response (granted the local time is sychronized using NTP).
  4. Verifying the authenticity of cached data in offline environments. For example, when you perform a license validation request and cache the response for later offline-use, you would want to verify the response signature to ensure that the cache data has not been tampered with.
  5. Verifying the authenticity of webhook events sent to your endpoints. Use request signatures to check if a webhook event was sent from us before processessing. This is especially critical when webhooks are used to automate things like billing.
We do not sign certain error payloads - please keep this in mind during implementation. We will always sign successful responses (2xx–3xx) and errors that occur while authenticated. Certain error responses, such as a bad request error due to a malformed request, or an internal server error, or a request to an invalid account, will not include the Keygen-Signature header.

Relevant response headers

Header Description
Keygen-Signature The signature result for the response. Depending on the Keygen-Accept-Signature algorithm, this may be signed using Ed25519, RSA-PSS-SHA256 or RSA-SHA256. Default is Ed25519.
Digest The base64 encoded SHA-256 digest of the response body. This header is used in the signing data.
Date The date of the response. This header is used in the signing data.

The header format

Below is the format for the Keygen-Signature header, according to the draft RFC. We have added newlines for readability.

Host: api.keygen.sh
Date: Wed, 09 Jun 2021 16:08:15 GMT
Digest: sha-256=827Op2un8OT9KJuN1siRs5h6mxjrUh4LJag66dQjnIM=
Keygen-Signature: keyid="bf9b523f-dd65-48a2-9512-fb66ba6c3714",
algorithm="ed25519",
signature="KhgcM+Ywv+DnQj4gE+DqWfNTM2TG5wfRuFQZ/zW48ValZuCHEu1h95Uyldqe7I85sS/QliCiRAF5QfW8ZN2vAw==",
headers="(request-target) host date digest"

The signature header is made up of 4 components:

Description
keyid The ID of the private key used to sign the response. For the time being, this is your account ID.
algorithm The algorithm used to sign the response. Supported algorithms are: ed25519, rsa-pss-sha256, and rsa-sha256.
signature The base64 encoded signature of the signing data.
headers The headers used to build the signing data.

Verifying response signatures

To verify the response signature, the signing data must be reconstructed. The signing data consists of the following 4 components:

Request target

The first component is what is known as the (request-target). This is formed using the lowercased HTTP method used for the request as well as the request path, and any accompanying query parameters.

For example, it may look something like:

post /v1/accounts/keygen/licenses/actions/validate-key

Or if you include any query parameters in the request, it would look like:

get /v1/accounts/keygen/licenses?page[size]=10&page[number]=1

Host

The Host that the request was sent to. For requests sent to us, this will always be api.keygen.sh. For example, for an API request to Keygen:

POST https://api.keygen.sh/v1/accounts/keygen/licenses/actions/validate-key
...
Host: api.keygen.sh

For a webhook event request sent to you, this will be your webhook endpoint's host.

POST https://webhooks.some-app.example/keygen
...
Host: webhooks.some-app.example

Date

The datetime at which the server sent the response. This will conform to the W3C’s Date header format. The full header from us will look something like:

Date: Wed, 09 Jun 2021 16:08:15 GMT

Digest

To obtain a digest for a response from our API, take the response body and run it through SHA-256. Then take this value and get its base64 value. For webhooks, this will be a digest of the request body.

The full Digest header from us will look something like:

Digest: sha-256=827Op2un8OT9KJuN1siRs5h6mxjrUh4LJag66dQjnIM=

When reconstructing the signing data, do not use the digest header we send. You should calculate your own SHA-256 digest of the request or response body, and then compare your encoded hash digest to the digest header we send.

When the request or response body is empty, such as with a 204 No Content response, you should still hash the body as if it were an empty string.

Remember to prefix the encoded digest with sha-256=.

Reconstructing the signing data

The first step is to construct a signature string based on the following template using all of the components you have already determined:

(request-target): get /v1/accounts/keygen/licenses?limit=1\nhost: api.keygen.sh\ndate: Wed, 09 Jun 2021 16:08:15 GMT\ndigest: sha-256=827Op2un8OT9KJuN1siRs5h6mxjrUh4LJag66dQjnIM=

For this particular example, the signing data was as follows, formatted for readability:

(request-target): get /v1/accounts/keygen/licenses?limit=1\n
host: api.keygen.sh\n
date: Wed, 09 Jun 2021 16:08:15 GMT\n
digest: sha-256=827Op2un8OT9KJuN1siRs5h6mxjrUh4LJag66dQjnIM=

Some things to pay special attention to:

  • The order of components must be (request-target) host date digest
  • Each component must be delimited by a newline character (\n)
  • Each component name is lowercased i.e. host: not Host:
  • The (request-target) HTTP method is lowercased
  • The (request-target) URI path must match exactly what was sent
  • The encoded digest is prefixed with sha-256=
  • There is no trailing newline character

Verifying the signing data

Once you've reconstructed the signing data, you can now verify it using your chosen algorithm. In this Python example, we'll use the default algorithm, Ed25519.

import ed25519
import hashlib
import base64
 
# Example of a typical "response" object
response = {
'status': 200,
'body': """{"data":[{"id":"63ac9241-0bff-4a64-83bb-df6aec781b0e","type":"licenses","attributes":{"name":"Ed25519 License","key":"key/eyJhY2NvdW50Ijp7ImlkIjoiYmY5YjUyM2YtZGQ2NS00OGEyLTk1MTItZmI2NmJhNmMzNzE0In0sInByb2R1Y3QiOnsiaWQiOiI5NTYxYzdkMC1mYzczLTRjOTQtYTZlZC0xY2M3MmEzZTAzNzYifSwicG9saWN5Ijp7ImlkIjoiNTQ2ZTc0OGUtZjhmYS00ODBjLWJjMDItNjYzMjdjOGZkMGZmIiwiZHVyYXRpb24iOm51bGx9LCJ1c2VyIjpudWxsLCJsaWNlbnNlIjp7ImlkIjoiNjNhYzkyNDEtMGJmZi00YTY0LTgzYmItZGY2YWVjNzgxYjBlIiwiY3JlYXRlZCI6IjIwMjEtMDYtMDFUMTU6MTM6NTMuMjUzWiIsImV4cGlyeSI6bnVsbH19.4ctbpwScfuuxkcynfPbmDrfwJojEHBc7ixgdSy9OKZtIRWEatzbWez3P1UwMhf7fMHXffIdUg5Nb41zqqjRqAA==","expiry":null,"uses":0,"suspended":false,"scheme":"ED25519_SIGN","encrypted":false,"strict":false,"floating":false,"concurrent":false,"protected":false,"maxMachines":1,"maxCores":null,"maxUses":null,"requireCheckIn":false,"lastValidated":"2021-06-04T17:00:58.680Z","lastCheckIn":null,"nextCheckIn":null,"metadata":{},"created":"2021-06-01T15:13:53.253Z","updated":"2021-06-04T17:00:58.680Z"},"relationships":{"account":{"links":{"related":"/v1/accounts/bf9b523f-dd65-48a2-9512-fb66ba6c3714"},"data":{"type":"accounts","id":"bf9b523f-dd65-48a2-9512-fb66ba6c3714"}},"product":{"links":{"related":"/v1/accounts/bf9b523f-dd65-48a2-9512-fb66ba6c3714/licenses/63ac9241-0bff-4a64-83bb-df6aec781b0e/product"},"data":{"type":"products","id":"9561c7d0-fc73-4c94-a6ed-1cc72a3e0376"}},"policy":{"links":{"related":"/v1/accounts/bf9b523f-dd65-48a2-9512-fb66ba6c3714/licenses/63ac9241-0bff-4a64-83bb-df6aec781b0e/policy"},"data":{"type":"policies","id":"546e748e-f8fa-480c-bc02-66327c8fd0ff"}},"user":{"links":{"related":"/v1/accounts/bf9b523f-dd65-48a2-9512-fb66ba6c3714/licenses/63ac9241-0bff-4a64-83bb-df6aec781b0e/user"},"data":null},"machines":{"links":{"related":"/v1/accounts/bf9b523f-dd65-48a2-9512-fb66ba6c3714/licenses/63ac9241-0bff-4a64-83bb-df6aec781b0e/machines"},"meta":{"cores":0,"count":0}},"tokens":{"links":{"related":"/v1/accounts/bf9b523f-dd65-48a2-9512-fb66ba6c3714/licenses/63ac9241-0bff-4a64-83bb-df6aec781b0e/tokens"}},"entitlements":{"links":{"related":"/v1/accounts/bf9b523f-dd65-48a2-9512-fb66ba6c3714/licenses/63ac9241-0bff-4a64-83bb-df6aec781b0e/entitlements"}}},"links":{"self":"/v1/accounts/bf9b523f-dd65-48a2-9512-fb66ba6c3714/licenses/63ac9241-0bff-4a64-83bb-df6aec781b0e"}}]}""",
'headers': {
'keygen-signature': 'keyid="bf9b523f-dd65-48a2-9512-fb66ba6c3714", algorithm="ed25519", signature="KhgcM+Ywv+DnQj4gE+DqWfNTM2TG5wfRuFQZ/zW48ValZuCHEu1h95Uyldqe7I85sS/QliCiRAF5QfW8ZN2vAw==", headers="(request-target) host date digest"',
'digest': 'sha-256=827Op2un8OT9KJuN1siRs5h6mxjrUh4LJag66dQjnIM=',
'date': 'Wed, 09 Jun 2021 16:08:15 GMT',
}
}
 
# In a real scenario, we would parse the signature param from
# the `keygen-signature` header. But for brevity...
response_sig = 'KhgcM+Ywv+DnQj4gE+DqWfNTM2TG5wfRuFQZ/zW48ValZuCHEu1h95Uyldqe7I85sS/QliCiRAF5QfW8ZN2vAw=='
response_body = response['body'].encode()
 
# Sign the response body using SHA-256
digest_bytes = hashlib.sha256(response_body).digest()
enc_digest = base64.b64encode(digest_bytes).decode()
 
# Reconstruct the signing data
signing_data = \
'(request-target): get /v1/accounts/keygen/licenses?limit=1\n' \
'host: api.keygen.sh\n' \
'date: ' + response['headers']['date'] + '\n' \
'digest: sha-256=' + enc_digest
 
# Verify the response signature
hex_verify_key = '799efc7752286e6c3815b13358d98fc0f0b566764458adcb48f1be2c10a55906'
verify_key = ed25519.VerifyingKey(hex_verify_key.encode(), encoding='hex')
try:
verify_key.verify(response_sig, signing_data.encode(), encoding='base64')
 
print('signature is good')
except ed25519.BadSignatureError:
print('signature is bad')

Changing the signing algorithm

We understand that not all programming languages have good support for our preferred signing algorithm, Ed25519. In these cases, you may provide a Keygen-Accept-Signature header to specify one of the following signing algorithms:

Description
ed25519 Sign using 128-bit Ed25519. This is the default signing algorithm.
rsa-pss-sha256 Sign using 2048-bit RSA PKCS1 with a SHA256 digest and PSS padding, a SHA256 MGF1 function and max salt length.
rsa-sha256 Sign using 2048-bit RSA PKCS1 with a SHA256 digest.

For example, to use rsa-sha256, you would provide the following header:

Keygen-Accept-Signature: algorithm="rsa-sha256"

This would sign the response using your account's 2048-bit RSA private key.

Webhook signatures

A signature header will always be included with webhook events delivered to your webhook endpoints. Use this to verify that the webhook is from us.

Code examples

To see an example of signature verification, check out our example on GitHub. It will show you how to utilize your account's public key to verify that the response was signed using your account's private key.

Other code examples for verifying response signatures:

Example response signature

Host: api.keygen.sh
Date: Wed, 09 Jun 2021 16:08:15 GMT
Digest: sha-256=827Op2un8OT9KJuN1siRs5h6mxjrUh4LJag66dQjnIM=
Keygen-Signature: keyid="bf9b523f-dd65-48a2-9512-fb66ba6c3714",
algorithm="ed25519",
signature="KhgcM+Ywv+DnQj4gE+DqWfNTM2TG5wfRuFQZ/zW48ValZuCHEu1h95Uyldqe7I85sS/QliCiRAF5QfW8ZN2vAw==",
headers="(request-target) host date digest"
{
"data": [
]
}

linkTesting

When implementing a testing strategy for your licensing integration, we recommend that you fully mock our APIs. This is especially important for CI/CD systems, to prevent unneeded load on our servers. Mocking our APIs will also allow you to more easily stay within your account's daily request limits.

Most popular programming languages will have at least 1 mocking library for HTTP APIs, but feel free to reach out to us for recommendations.

linkSandbox

A sandbox environment is coming soon. If you're in need of a sandbox account, please reach out.

For the time being, you can create a separate account as a sandbox, or you can create a separate "test" product. Creating a separate product allows you to easily delete the "test" product, which will clear all test licenses and its other associated test resources.

linkTokens

linkThe token object

Keygen authenticates your API requests using tokens. Below you will find the various attributes for the token resource, as well as the token resource's relationships. The actual token string is hashed before being stored in our databases, thus is only available directly after generating/regenerating a token. Tokens with an expiry will automatically be deleted 90 days after their expiry, unless renewed.

Admin and product tokens should not be included in any client-facing code, as they offer full access to all of your account's resources. You can authenticate as one of your users or use an activation token to perform client-side machine activations. Admin and product tokens should only be used server-side.

linkAttributes

  • linkdata.attributes.kind

    stringread only

    The kind of token, based on its bearer.

    Options

    • activation-token: An activation token with permission to activate and deactivate the machines for a given license.
    • product-token: An internal administrative token with permission to manage the entire product and its resources, usually for server-side use.
    • user-token: A normal user of one or more of your products, with limited permission to manage their own resources.
    • support-token: An internal administrative user of your Keygen account, with a limited subset of permissions.
    • sales-token: An internal administrative user of your Keygen account, with a limited subset of permissions.
    • developer-token: An internal administrative user of your Keygen account, with permission to manage most resources.
    • admin-token: An internal administrative user of your Keygen account, with permission to manage the entire account.
  • linkdata.attributes.token

    stringread only

    The raw token of the token. This attribute is only available to read directly after token generation. This is the value you will use to authenticate with when sending requests to our API.

  • linkdata.attributes.expiry

    timestamp (ISO8601 format)read only

    The timestamp for when the token expires. Requests using an expired token will be rejected.

  • linkdata.attributes.maxActivations

    integer

    The maximum amount of machine activations this token may perform. This attribute is only applicable to activation tokens.

  • linkdata.attributes.activations

    integerread only

    The number of machine activations that have been performed by this token. This attribute is only applicable to activation tokens.

  • linkdata.attributes.maxDeactivations

    integer

    The maximum amount of machine deactivations this token may perform. This attribute is only applicable to activation tokens.

  • linkdata.attributes.deactivations

    integerread only

    The number of machine deactivations that have been performed by this token. This attribute is only applicable to activation tokens.

  • linkdata.attributes.created

    timestamp (ISO8601 format)read only

    When the token was created.

  • linkdata.attributes.updated

    timestamp (ISO8601 format)read only

    When the token was last updated.

linkRelationships

  • linkdata.relationships.account

    individual

    The account that the token belongs to.

  • linkdata.relationships.bearer

    individual

    The bearer of the token.

Example object

{
"data": {
"id": "6a7562be-b302-43d2-a550-30d6026247aa",
"type": "tokens",
"attributes": {
"kind": "user-token",
"token": "user-57c1cdc2a084f0ebd46850e5742b1f0bca47d1ca5f5262f6c5969fec8dbd530dv3",
"expiry": "2022-03-15T19:27:50.440Z",
"created": "2017-01-02T20:26:53.464Z",
"updated": "2017-01-02T20:26:53.464Z"
},
"relationships": {
"account": {
"links": {
"related": "/v1/accounts/{ACCOUNT}"
},
"data": {
"type": "accounts",
"id": "{ACCOUNT}"
}
},
"bearer": {
"links": {
"related": "/v1/accounts/{ACCOUNT}/users/a5a154d2-f026-40fa-bc8d-a7e3ca415298"
},
"data": {
"type": "users",
"id": "a5a154d2-f026-40fa-bc8d-a7e3ca415298"
}
}
}
}
}

linkGenerate a token

Generate a new token resource for a user. To generate a token for a product, see the product token relationship. To generate an activation token, see the license token relationship. By default, user tokens expire in 2 weeks, and admin tokens do not expire. A custom expiry may be provided in the token generate request.

Admin and product tokens should not be included in any client-facing code, as they offer full access to all of your account's resources. You can authenticate as one of your users or use an activation token to perform client-side machine activations. Admin and product tokens should only be used server-side.

linkAuthentication

  • linkBasic

    required

    The email and password of the user. Credentials MUST use a colon (i.e. ":") to separate the email and password (i.e. "EMAIL:PASSWORD"), and those credentials MUST then be base64 encoded.

linkURL Parameters

  • link:account

    string, required

    The identifier (UUID) or slug of your Keygen account.

linkAttributes

  • linkdata.attributes.expiry

    timestamp (ISO8601 format), optional

    The timestamp for when the token expires. Requests using an expired token will be rejected.

linkReturns

A 201 Created response will be returned along with the new token object.

Upon error, an errors object will be returned along with an HTTP status code indicating the type of error. When an error occurs, the data property will not be included.

Definition

https://api.keygen.sh/v1/accounts/{ACCOUNT}/tokens

Example request

const fetch = require("node-fetch")
 
const credentials = new Buffer("{EMAIL}:{PASSWORD}").toString("base64")
const response = await fetch("https://api.keygen.sh/v1/accounts/{ACCOUNT}/tokens", {
method: "POST",
headers: {
"Accept": "application/vnd.api+json",
"Authorization": `Basic ${credentials}`
}
})
 
const { data, errors } = await response.json()
import requests
import json
 
res = requests.post(
"https://api.keygen.sh/v1/accounts/{ACCOUNT}/tokens",
headers={ "Accept": "application/vnd.api+json" },
auth=("{EMAIL}", "{PASSWORD}")
).json()
import SwiftyJSON
import Alamofire
 
let credentials = Data("{EMAIL}:{PASSWORD}".utf8).base64EncodedString()
 
Alamofire.request("https://api.keygen.sh/v1/accounts/{ACCOUNT}/tokens",
method: .post,
headers: [
"Accept": "application/vnd.api+json",
"Authorization": "Basic \(credentials)"
]
).responseJSON { response in
let json = JSON(data: response.data!)
}
using RestSharp;
using System;
using System.Text;
 
var credentials = Convert.ToBase64String(Encoding.UTF8.GetBytes("{EMAIL}:{PASSWORD}"));
 
var client = new RestClient("https://api.keygen.sh/v1/accounts/{ACCOUNT}");
var request = new RestRequest("tokens", Method.POST);
 
request.AddHeader("Accept", "application/vnd.api+json");
request.AddHeader("Authorization", $"Basic {credentials}");
 
var response = client.Execute(request);
import com.mashape.unirest.http.exceptions.*
import com.mashape.unirest.http.*
 
val res = Unirest.post("https://api.keygen.sh/v1/accounts/{ACCOUNT}/tokens")
.header("Accept", "application/vnd.api+json")
.basicAuth("{EMAIL}", "{PASSWORD}")
.asJson()
import com.mashape.unirest.http.exceptions.*;
import com.mashape.unirest.http.*;
 
HttpResponse<JsonNode> res = Unirest.post("https://api.keygen.sh/v1/accounts/{ACCOUNT}/tokens")
.header("Accept", "application/vnd.api+json")
.basicAuth("{EMAIL}", "{PASSWORD}")
.asJson();
#include <iostream>
#include <string>
#include <cpprest/http_client.h>
#include <cpprest/filestream.h>
 
using namespace std;
using namespace web;
using namespace web::http;
using namespace web::http::client;
using namespace utility;
 
http_client_config config;
credentials cred("{EMAIL}", "{PASSWORD}");
config.set_credentials(cred);
 
http_client client("https://api.keygen.sh/v1/accounts/{ACCOUNT}", config);
http_request req;
 
req.headers().add("Accept", "application/json");
 
req.set_request_uri("/tokens");
req.set_method(methods::POST);
 
client.request(req)
.then([](http_response res)
{
auto data = res.extract_json().get();
})
.wait();
curl -X POST https://api.keygen.sh/v1/accounts/{ACCOUNT}/tokens \
-H 'Accept: application/vnd.api+json' \
-u "{EMAIL}:{PASSWORD}"

Example response / 201 Created

{
"data": {
"id": "6a7562be-b302-43d2-a550-30d6026247aa",
"type": "tokens",
"attributes": {
"kind": "user-token",
"token": "user-f4869386e3b6b39d1f42949131f97a39b42f9a74c553ba7244bbed9d1f79f106v3",
"expiry": "2022-03-15T19:27:50.440Z",
"created": "2017-01-02T20:26:53.464Z",
"updated": "2017-01-02T20:26:53.464Z"
},
"relationships": {
"account": {
"links": {
"related": "/v1/accounts/{ACCOUNT}"
},
"data": {
"type": "accounts",
"id": "{ACCOUNT}"
}
},
"bearer": {
"links": {
"related": "/v1/accounts/{ACCOUNT}/users/a5a154d2-f026-40fa-bc8d-a7e3ca415298"
},
"data": {
"type": "users",
"id": "a5a154d2-f026-40fa-bc8d-a7e3ca415298"
}
}
}
}
}

linkRetrieve a token

Retrieves the details of an existing token.

linkAuthentication

  • linkBearer

    required

    An authentication token with privileges to view the resource: either an admin or the token owner.

linkURL Parameters

  • link:account

    string, required

    The identifier (UUID) or slug of your Keygen account.

  • link:id

    string, required

    The identifier (UUID) of the token to be retrieved.

linkReturns

A 200 OK response will be returned along with a token object.

Upon error, an errors object will be returned along with an HTTP status code indicating the type of error. When an error occurs, the data property will not be included.

Definition

https://api.keygen.sh/v1/accounts/{ACCOUNT}/tokens/{ID}

Example request

const fetch = require("node-fetch")
 
const response = await fetch("https://api.keygen.sh/v1/accounts/{ACCOUNT}/tokens/6a7562be-b302-43d2-a550-30d6026247aa", {
method: "GET",
headers: {
"Accept": "application/vnd.api+json",
"Authorization": "Bearer {TOKEN}"
}
})
 
const { data, errors } = await response.json()
import requests
import json
 
res = requests.get(
"https://api.keygen.sh/v1/accounts/{ACCOUNT}/tokens/6a7562be-b302-43d2-a550-30d6026247aa",
headers={
"Accept": "application/vnd.api+json",
"Authorization": "Bearer {TOKEN}"
}
).json()
import SwiftyJSON
import Alamofire
 
Alamofire.request("https://api.keygen.sh/v1/accounts/{ACCOUNT}/tokens/6a7562be-b302-43d2-a550-30d6026247aa",
headers: [
"Accept": "application/vnd.api+json",
"Authorization": "Bearer {TOKEN}"
]
).responseJSON { response in
let json = JSON(data: response.data!)
}
using RestSharp;
 
var client = new RestClient("https://api.keygen.sh/v1/accounts/{ACCOUNT}");
var request = new RestRequest(
"tokens/6a7562be-b302-43d2-a550-30d6026247aa",
Method.GET
);
 
request.AddHeader("Accept", "application/vnd.api+json");
request.AddHeader("Authorization", "Bearer {TOKEN}");
 
var response = client.Execute(request);
import com.mashape.unirest.http.exceptions.*
import com.mashape.unirest.http.*
 
val res = Unirest.get("https://api.keygen.sh/v1/accounts/{ACCOUNT}/tokens/6a7562be-b302-43d2-a550-30d6026247aa")
.header("Authorization", "Bearer {TOKEN}")
.header("Accept", "application/vnd.api+json")
.asJson()
import com.mashape.unirest.http.exceptions.*;
import com.mashape.unirest.http.*;
 
HttpResponse<JsonNode> res = Unirest.get("https://api.keygen.sh/v1/accounts/{ACCOUNT}/tokens/6a7562be-b302-43d2-a550-30d6026247aa")
.header("Authorization", "Bearer {TOKEN}")
.header("Accept", "application/vnd.api+json")
.asJson();
#include <iostream>
#include <string>
#include <cpprest/http_client.h>
#include <cpprest/filestream.h>
 
using namespace std;
using namespace web;
using namespace web::http;
using namespace web::http::client;
using namespace utility;
 
http_client client("https://api.keygen.sh/v1/accounts/{ACCOUNT}");
http_request req;
 
req.headers().add("Authorization", "Bearer {TOKEN}");
req.headers().add("Accept", "application/json");
 
req.set_request_uri("/tokens/6a7562be-b302-43d2-a550-30d6026247aa");
req.set_method(methods::GET);
 
client.request(req)
.then([](http_response res) {
auto data = res.extract_json().get();
})
.wait();
curl https://api.keygen.sh/v1/accounts/{ACCOUNT}/tokens/6a7562be-b302-43d2-a550-30d6026247aa \
-H 'Accept: application/vnd.api+json' \
-H 'Authorization: Bearer {TOKEN}'

Example response / 200 OK

{
"data": {
"id": "6a7562be-b302-43d2-a550-30d6026247aa",
"type": "tokens",
"attributes": {
"kind": "admin-token",
"expiry": "2022-03-15T19:27:50.440Z",
"created": "2017-01-02T20:26:53.464Z",
"updated": "2017-01-02T20:26:53.464Z"
},
"relationships": {
"account": {
"links": {
"related": "/v1/accounts/{ACCOUNT}"
},
"data": {
"type": "accounts",
"id": "{ACCOUNT}"
}
},
"bearer": {
"links": {
"related": "/v1/accounts/{ACCOUNT}/users/a5a154d2-f026-40fa-bc8d-a7e3ca415298"
},
"data": {
"type": "users",
"id": "a5a154d2-f026-40fa-bc8d-a7e3ca415298"
}
}
}
}
}

linkRegenerate a token

Regenerate an existing token resource. This will replace the token attribute with a new secure token, and extend the token's expiry by 2 weeks from the current time.

linkAuthentication

  • linkBearer

    required

    An authentication token with privileges to manage the resource: either an admin or the token owner.

linkURL Parameters

  • link:account

    string, required

    The identifier (UUID) or slug of your Keygen account.

  • link:id

    string, optional

    The identifier (UUID) of the token to be regenerated. If ommited, the token used to authenticate will be regenerated.

linkReturns

A 200 OK response will be returned along with the regenerated token object.

Upon error, an errors object will be returned along with an HTTP status code indicating the type of error. When an error occurs, the data property will not be included.

Definition

https://api.keygen.sh/v1/accounts/{ACCOUNT}/tokens/{ID}

Example request

const fetch = require("node-fetch")
 
const response = await fetch("https://api.keygen.sh/v1/accounts/{ACCOUNT}/tokens/6a7562be-b302-43d2-a550-30d6026247aa", {
method: "PUT",
headers: {
"Accept": "application/vnd.api+json",
"Authorization": "Bearer {TOKEN}"
}
})
 
const { data, errors } = await response.json()
import requests
import json
 
res = requests.put(
"https://api.keygen.sh/v1/accounts/{ACCOUNT}/tokens/6a7562be-b302-43d2-a550-30d6026247aa",
headers={
"Accept": "application/vnd.api+json",
"Authorization": "Bearer {TOKEN}"
}
).json()
import SwiftyJSON
import Alamofire
 
Alamofire.request("https://api.keygen.sh/v1/accounts/{ACCOUNT}/tokens/6a7562be-b302-43d2-a550-30d6026247aa",
method: .put,
headers: [
"Accept": "application/vnd.api+json",
"Authorization": "Bearer {TOKEN}"
]
).responseJSON { response in
let json = JSON(data: response.data!)
}
using RestSharp;
 
var client = new RestClient("https://api.keygen.sh/v1/accounts/{ACCOUNT}");
var request = new RestRequest(
"tokens/6a7562be-b302-43d2-a550-30d6026247aa",
Method.PUT
);
 
request.AddHeader("Accept", "application/vnd.api+json");
request.AddHeader("Authorization", "Bearer {TOKEN}");
 
var response = client.Execute(request);
import com.mashape.unirest.http.exceptions.*
import com.mashape.unirest.http.*
 
val res = Unirest.put("https://api.keygen.sh/v1/accounts/{ACCOUNT}/tokens/6a7562be-b302-43d2-a550-30d6026247aa")
.header("Authorization", "Bearer {TOKEN}")
.header("Accept", "application/vnd.api+json")
.asJson()
import com.mashape.unirest.http.exceptions.*;
import com.mashape.unirest.http.*;
 
HttpResponse<JsonNode> res = Unirest.put("https://api.keygen.sh/v1/accounts/{ACCOUNT}/tokens/6a7562be-b302-43d2-a550-30d6026247aa")
.header("Authorization", "Bearer {TOKEN}")
.header("Accept", "application/vnd.api+json")
.asJson();
#include <iostream>
#include <string>
#include <cpprest/http_client.h>
#include <cpprest/filestream.h>
 
using namespace std;
using namespace web;
using namespace web::http;
using namespace web::http::client;
using namespace utility;
 
http_client client("https://api.keygen.sh/v1/accounts/{ACCOUNT}");
http_request req;
 
req.headers().add("Authorization", "Bearer {TOKEN}");
req.headers().add("Accept", "application/json");
 
req.set_request_uri("/tokens/6a7562be-b302-43d2-a550-30d6026247aa");
req.set_method(methods::PUT);
 
client.request(req)
.then([](http_response res) {
auto data = res.extract_json().get();
})
.wait();
curl -X PUT https://api.keygen.sh/v1/accounts/{ACCOUNT}/tokens/6a7562be-b302-43d2-a550-30d6026247aa \
-H 'Accept: application/vnd.api+json' \
-H 'Authorization: Bearer {TOKEN}'

Example response / 200 OK

{
"data": {
"id": "6a7562be-b302-43d2-a550-30d6026247aa",
"type": "tokens",
"attributes": {
"kind": "user-token",
"token": "user-72abcf7e9bce107df746fab6751f875aad7801d8edc7b9b9afb2f97ee53b59e7v3",
"expiry": "2022-03-15T19:27:50.440Z",
"created": "2017-01-02T20:26:53.464Z",
"updated": "2017-01-02T20:26:53.464Z"
},
"relationships": {
"account": {
"links": {
"related": "/v1/accounts/{ACCOUNT}"
},
"data": {
"type": "accounts",
"id": "{ACCOUNT}"
}
},
"bearer": {
"links": {
"related": "/v1/accounts/{ACCOUNT}/users/a5a154d2-f026-40fa-bc8d-a7e3ca415298"
},
"data": {
"type": "users",
"id": "a5a154d2-f026-40fa-bc8d-a7e3ca415298"
}
}
}
}
}

linkRevoke a token

Permanently revokes a token. It cannot be undone. This action also immediately invalidates all sessions using the given token.

linkAuthentication

  • linkBearer

    required

    An authentication token with privileges to manage the resource: either an admin or the token owner.

linkURL Parameters

  • link:account

    string, required

    The identifier (UUID) or slug of your Keygen account.

  • link:id

    string, required

    The identifier (UUID) of the token to be revoked.

linkReturns

A 204 No Content response will be returned.

Upon error, an errors object will be returned along with an HTTP status code indicating the type of error. When an error occurs, the data property will not be included.

Definition

https://api.keygen.sh/v1/accounts/{ACCOUNT}/tokens/{ID}

Example request

const fetch = require("node-fetch")
 
const response = await fetch("https://api.keygen.sh/v1/accounts/{ACCOUNT}/tokens/6a7562be-b302-43d2-a550-30d6026247aa", {
method: "DELETE",
headers: {
"Accept": "application/vnd.api+json",
"Authorization": "Bearer {TOKEN}"
}
})
import requests
 
res = requests.delete(
"https://api.keygen.sh/v1/accounts/{ACCOUNT}/tokens/6a7562be-b302-43d2-a550-30d6026247aa",
headers={
"Accept": "application/vnd.api+json",
"Authorization": "Bearer {TOKEN}"
}
)
import SwiftyJSON
import Alamofire
 
Alamofire.request("https://api.keygen.sh/v1/accounts/{ACCOUNT}/tokens/6a7562be-b302-43d2-a550-30d6026247aa",
method: .delete,
headers: [
"Accept": "application/vnd.api+json",
"Authorization": "Bearer {TOKEN}"
]
).responseJSON { response in
let status = response.response?.statusCode
}
using RestSharp;
 
var client = new RestClient("https://api.keygen.sh/v1/accounts/{ACCOUNT}");
var request = new RestRequest(
"tokens/6a7562be-b302-43d2-a550-30d6026247aa",
Method.DELETE
);
 
request.AddHeader("Accept", "application/vnd.api+json");
request.AddHeader("Authorization", "Bearer {TOKEN}");
 
var response = client.Execute(request);
import com.mashape.unirest.http.exceptions.*
import com.mashape.unirest.http.*
 
val res = Unirest.delete("https://api.keygen.sh/v1/accounts/{ACCOUNT}/tokens/6a7562be-b302-43d2-a550-30d6026247aa")
.header("Authorization", "Bearer {TOKEN}")
.header("Accept", "application/vnd.api+json")
.asJson()
import com.mashape.unirest.http.exceptions.*;
import com.mashape.unirest.http.*;
 
HttpResponse<JsonNode> res = Unirest.delete("https://api.keygen.sh/v1/accounts/{ACCOUNT}/tokens/6a7562be-b302-43d2-a550-30d6026247aa")
.header("Authorization", "Bearer {TOKEN}")
.header("Accept", "application/vnd.api+json")
.asJson();
#include <iostream>
#include <string>
#include <cpprest/http_client.h>
#include <cpprest/filestream.h>
 
using namespace std;
using namespace web;
using namespace web::http;
using namespace web::http::client;
using namespace utility;
 
http_client client("https://api.keygen.sh/v1/accounts/{ACCOUNT}");
http_request req;
 
req.headers().add("Authorization", "Bearer {TOKEN}");
req.headers().add("Accept", "application/json");
 
req.set_request_uri("/tokens/6a7562be-b302-43d2-a550-30d6026247aa");
req.set_method(methods::DELETE);
 
client.request(req)
.then([](http_response res) {
auto status = res.status_code();
})
.wait();
curl -X DELETE https://api.keygen.sh/v1/accounts/{ACCOUNT}/tokens/6a7562be-b302-43d2-a550-30d6026247aa \
-H 'Accept: application/vnd.api+json' \
-H 'Authorization: Bearer {TOKEN}'

Example response / 204 No Content

No content

linkList all tokens

Returns a list of tokens. The tokens are returned sorted by creation date, with the most recent tokens appearing first. Resources are automatically scoped to the authenticated bearer e.g. when authenticated as a product, only tokens that belong to the specific product will be listed.

linkAuthentication

  • linkBearer

    required

    An authentication token with privileges to view the resources.

linkURL Parameters

  • link:account

    string, required

    The identifier (UUID) or slug of your Keygen account.

linkFilters

  • linklimit

    integer, default is10

    A limit on the number of tokens to be returned. Limit must be a number between 1 and 100.

    https://api.keygen.sh/v1/accounts/{ACCOUNT}/tokens?limit=25
  • linkpage

    object<string, integer>

    Object containing page size and page number. Page size must be a number between 1 and 100

    https://api.keygen.sh/v1/accounts/{ACCOUNT}/tokens?page[size]=15&page[number]=2

linkReturns

A 200 OK response will be returned along with a list of token objects.

Upon error, an errors object will be returned along with an HTTP status code indicating the type of error. When an error occurs, the data property will not be included.

Definition

https://api.keygen.sh/v1/accounts/{ACCOUNT}/tokens{FILTERS}

Example request

const fetch = require("node-fetch")
 
const response = await fetch("https://api.keygen.sh/v1/accounts/{ACCOUNT}/tokens?limit=15", {
method: "GET",
headers: {
"Accept": "application/vnd.api+json",
"Authorization": "Bearer {TOKEN}"
}
})
 
const { data, errors } = await response.json()
import requests
import json
 
res = requests.get(
"https://api.keygen.sh/v1/accounts/{ACCOUNT}/tokens?limit=15",
headers={
"Accept": "application/vnd.api+json",
"Authorization": "Bearer {TOKEN}"
}
).json()
import SwiftyJSON
import Alamofire
 
Alamofire.request("https://api.keygen.sh/v1/accounts/{ACCOUNT}/tokens?limit=15",
headers: [
"Accept": "application/vnd.api+json",
"Authorization": "Bearer {TOKEN}"
]
).responseJSON { response in
let json = JSON(data: response.data!)
}
using RestSharp;
 
var client = new RestClient("https://api.keygen.sh/v1/accounts/{ACCOUNT}");
var request = new RestRequest("tokens", Method.GET);
 
request.AddHeader("Accept", "application/vnd.api+json");
request.AddHeader("Authorization", "Bearer {TOKEN}");
 
request.AddParameter("limit", 15);
 
var response = client.Execute(request);
import com.mashape.unirest.http.exceptions.*
import com.mashape.unirest.http.*
 
val res = Unirest.get("https://api.keygen.sh/v1/accounts/{ACCOUNT}/tokens")
.header("Authorization", "Bearer {TOKEN}")
.header("Accept", "application/vnd.api+json")
.queryString("limit", 15)
.asJson()
import com.mashape.unirest.http.exceptions.*;
import com.mashape.unirest.http.*;
 
HttpResponse<JsonNode> res = Unirest.get("https://api.keygen.sh/v1/accounts/{ACCOUNT}/tokens")
.header("Authorization", "Bearer {TOKEN}")
.header("Accept", "application/vnd.api+json")
.queryString("limit", 15)
.asJson();
#include <iostream>
#include <string>
#include <cpprest/http_client.h>
#include <cpprest/filestream.h>
 
using namespace std;
using namespace web;
using namespace web::http;
using namespace web::http::client;
using namespace utility;
 
http_client client("https://api.keygen.sh/v1/accounts/{ACCOUNT}");
http_request req;
 
req.headers().add("Authorization", "Bearer {TOKEN}");
req.headers().add("Accept", "application/json");
 
uri_builder uri("/tokens");
uri.append_query("limit", 15);
 
req.set_request_uri(uri.to_uri());
req.set_method(methods::GET);
 
client.request(req)
.then([](http_response res) {
auto data = res.extract_json().get();
})
.wait();
curl https://api.keygen.sh/v1/accounts/{ACCOUNT}/tokens?limit=15 -g \
-H 'Accept: application/vnd.api+json' \
-H 'Authorization: Bearer {TOKEN}'

Example response / 200 OK

{
"data": [
{
"id": "6a7562be-b302-43d2-a550-30d6026247aa",
"type": "tokens",
"attributes": {
"kind": "admin-token",
"expiry": "2022-03-15T19:27:50.440Z",
"created": "2017-01-02T20:26:53.464Z",
"updated": "2017-01-02T20:26:53.464Z"
},
"relationships": {
"account": {
"links": {
"related": "/v1/accounts/{ACCOUNT}"
},
"data": {
"type": "accounts",
"id": "{ACCOUNT}"
}
},
"bearer": {
"links": {
"related": "/v1/accounts/{ACCOUNT}/users/a5a154d2-f026-40fa-bc8d-a7e3ca415298"
},
"data": {
"type": "users",
"id": "a5a154d2-f026-40fa-bc8d-a7e3ca415298"
}
}
}
},
]
}

linkProducts

linkThe product object

Below you will find the various attributes for the product resource, as well as the product resource's relationships.

linkAttributes

  • linkdata.attributes.name

    string

    The name of the product.

  • linkdata.attributes.url

    string

    A related URL for the product e.g. the marketing website, company website, etc.

  • linkdata.attributes.platforms

    array<string>

    An array of platforms the product supports.

  • linkdata.attributes.metadata

    object<string, scalar>

    Object containing product metadata.

  • linkdata.attributes.created

    timestamp (ISO8601 format)read only

    When the product was created.

  • linkdata.attributes.updated

    timestamp (ISO8601 format)read only

    When the product was last updated.

linkRelationships

  • linkdata.relationships.account

    individual

    The account that the product belongs to.

  • linkdata.relationships.policies

    collection

    The policies that are associated with the product.

  • linkdata.relationships.licenses

    collection

    The licenses that are associated with the product.

  • linkdata.relationships.machines

    collection

    The machines that are associated with the product.

  • linkdata.relationships.users

    collection

    The users that own a license for the product.

  • linkdata.relationships.tokens

    collection

    The authentication tokens of the product.

Example object

{
"data": {
"id": "31339351-f7f5-4bdd-8346-5d8399a1ac07",
"type": "products",
"links": {
"self": "/v1/accounts/{ACCOUNT}/products/31339351-f7f5-4bdd-8346-5d8399a1ac07"
},
"attributes": {
"name": "Product Hunt",
"url": "https://producthunt.com",
"platforms": ["iOS", "Android"],
"metadata": {},
"created": "2017-01-02T20:26:53.464Z",
"updated": "2017-01-02T20:26:53.464Z"
},
"relationships": {
"account": {
"links": {
"related": "/v1/accounts/{ACCOUNT}"
},
"data": {
"type": "accounts",
"id": "{ACCOUNT}"
}
},
"policies": {
"links": {
"related": "/v1/accounts/{ACCOUNT}/products/31339351-f7f5-4bdd-8346-5d8399a1ac07/policies"
}
},
"licenses": {
"links": {
"related": "/v1/accounts/{ACCOUNT}/products/31339351-f7f5-4bdd-8346-5d8399a1ac07/licenses"
}
},
"machines": {
"links": {
"related": "/v1/accounts/{ACCOUNT}/products/31339351-f7f5-4bdd-8346-5d8399a1ac07/machines"
}
},
"users": {
"links": {
"related": "/v1/accounts/{ACCOUNT}/products/31339351-f7f5-4bdd-8346-5d8399a1ac07/users"
}
},
"tokens": {
"links": {
"related": "/v1/accounts/{ACCOUNT}/products/31339351-f7f5-4bdd-8346-5d8399a1ac07/tokens"
}
}
}
}
}

linkCreate a product

Creates a new product resource.

linkAuthentication

  • linkBearer

    required

    An authentication token with admin privileges.

linkURL Parameters

  • link:account

    string, required

    The identifier (UUID) or slug of your Keygen account.

linkAttributes

  • linkdata.attributes.name

    string, required

    The name of the product.

  • linkdata.attributes.url

    string, optional

    A related URL for the product e.g. the marketing website, company website, etc. Must be a valid URL.

  • linkdata.attributes.platforms

    array<string>, optional

    An array of platforms the product supports.

  • linkdata.attributes.metadata

    object<string, scalar>, optional

    Object containing product metadata.

linkReturns

A 201 Created response will be returned along with the new product object.

Upon error, an errors object will be returned along with an HTTP status code indicating the type of error. When an error occurs, the data property will not be included.

Definition

https://api.keygen.sh/v1/accounts/{ACCOUNT}/products

Example request

const fetch = require("node-fetch")
 
const response = await fetch("https://api.keygen.sh/v1/accounts/{ACCOUNT}/products", {
method: "POST",
headers: {
"Content-Type": "application/vnd.api+json",
"Accept": "application/vnd.api+json",
"Authorization": "Bearer {TOKEN}"
},
body: JSON.stringify({
"data": {
"type": "products",
"attributes": {
"name": "Product Hunt",
"url": "https://producthunt.com",
"platforms": ["iOS", "Android"]
}
}
})
})
 
const { data, errors } = await response.json()
import requests
import json
 
res = requests.post(
"https://api.keygen.sh/v1/accounts/{ACCOUNT}/products",
headers={
"Content-Type": "application/vnd.api+json",
"Accept": "application/vnd.api+json",
"Authorization": "Bearer {TOKEN}"
},
data=json.dumps({
"data": {
"type": "products",
"attributes": {
"name": "Product Hunt",
"url": "https://producthunt.com",
"platforms": ["iOS", "Android"]
}
}
})
).json()
import SwiftyJSON
import Alamofire
 
Alamofire.request("https://api.keygen.sh/v1/accounts/{ACCOUNT}/products",
method: .post,
headers: [
"Content-Type": "application/vnd.api+json",
"Accept": "application/vnd.api+json",
"Authorization": "Bearer {TOKEN}"
],
parameters: [
"data": [
"type": "products",
"attributes": [
"name": "Product Hunt",
"url": "https://producthunt.com",
"platforms": ["iOS", "Android"]
]
]
],
encoding: JSONEncoding.default
).responseJSON { response in
let json = JSON(data: response.data!)
}
using RestSharp;
 
var client = new RestClient("https://api.keygen.sh/v1/accounts/{ACCOUNT}");
var request = new RestRequest("products", Method.POST);
 
request.AddHeader("Content-Type", "application/vnd.api+json");
request.AddHeader("Accept", "application/vnd.api+json");
request.AddHeader("Authorization", "Bearer {TOKEN}");
 
request.AddJsonBody(new {
data = new {
type = "products",
attributes = new {
name = "Product Hunt",
url = "https://producthunt.com",
platforms = new[] { "iOS", "Android" }
}
}
});
 
var response = client.Execute(request);
import com.mashape.unirest.http.exceptions.*
import com.mashape.unirest.http.*
import org.json.*
 
val body = JSONObject(mapOf(
"data" to mapOf(
"type" to "products",
"attributes" to mapOf(
"name" to "Product Hunt",
"url" to "https://producthunt.com",
"platforms" to listOf("iOS", "Android")
)
)
))
 
val res = Unirest.post("https://api.keygen.sh/v1/accounts/{ACCOUNT}/products")
.header("Authorization", "Bearer {TOKEN}")
.header("Content-Type", "application/vnd.api+json")
.header("Accept", "application/vnd.api+json")
.body(body)
.asJson()
import com.mashape.unirest.http.exceptions.*;
import com.mashape.unirest.http.*;
import org.json.*;
 
import static java.util.Map.ofEntries;
import static java.util.Map.entry;
import static java.util.List.of;
 
JSONObject body = new JSONObject(ofEntries(
entry("data", ofEntries(
entry("type", "products"),
entry("attributes", ofEntries(
entry("name", "Product Hunt"),
entry("url", "https://producthunt.com"),
entry("platforms", of("iOS", "Android"))
))
))
));
 
HttpResponse<JsonNode> res = Unirest.post("https://api.keygen.sh/v1/accounts/{ACCOUNT}/products")
.header("Authorization", "Bearer {TOKEN}")
.header("Content-Type", "application/vnd.api+json")
.header("Accept", "application/vnd.api+json")
.body(body)
.asJson();
#include <iostream>
#include <string>
#include <cpprest/http_client.h>
#include <cpprest/filestream.h>
 
using namespace std;
using namespace web;
using namespace web::http;
using namespace web::http::client;
using namespace web::json;
using namespace utility;
 
http_client client("https://api.keygen.sh/v1/accounts/{ACCOUNT}");
http_request req;
 
value platforms;
platforms[0] = value::string("iOS");
platforms[1] = value::string("Android");
 
value attrs;
attrs["name"] = value::string("Product Hunt");
attrs["url"] = value::string("https://producthunt.com");
attrs["platforms"] = platforms;
 
value data;
data["type"] = value::string("products");
data["attributes"] = attrs;
 
value body;
body["data"] = data;
 
req.headers().add("Authorization", "Bearer {TOKEN}");
req.headers().add("Content-Type", "application/vnd.api+json");
req.headers().add("Accept", "application/json");
 
req.set_request_uri("/products");
req.set_method(methods::POST);
req.set_body(body.serialize());
 
client.request(req)
.then([](http_response res)
{
auto data = res.extract_json().get();
})
.wait();
curl -X POST https://api.keygen.sh/v1/accounts/{ACCOUNT}/products \
-H 'Content-Type: application/vnd.api+json' \
-H 'Accept: application/vnd.api+json' \
-H 'Authorization: Bearer {TOKEN}' \
-d '{
"data": {
"type": "products",
"attributes": {
"name": "Product Hunt",
"url": "https://producthunt.com",
"platforms": ["iOS", "Android"]
}
}
}'

Example response / 201 Created

{
"data": {
"id": "31339351-f7f5-4bdd-8346-5d8399a1ac07",
"type": "products",
"links": {
"self": "/v1/accounts/{ACCOUNT}/products/31339351-f7f5-4bdd-8346-5d8399a1ac07"
},
"attributes": {
"name": "Product Hunt",
"url": "https://producthunt.com",
"platforms": ["iOS", "Android"],
"metadata": {},
"created": "2017-01-02T20:26:53.464Z",
"updated": "2017-01-02T20:26:53.464Z"
},
"relationships": {
"account": {
"links": {
"related": "/v1/accounts/{ACCOUNT}"
},
"data": {
"type": "accounts",
"id": "{ACCOUNT}"
}
},
"policies": {
"links": {
"related": "/v1/accounts/{ACCOUNT}/products/31339351-f7f5-4bdd-8346-5d8399a1ac07/policies"
}
},
"licenses": {
"links": {
"related": "/v1/accounts/{ACCOUNT}/products/31339351-f7f5-4bdd-8346-5d8399a1ac07/licenses"
}
},
"machines": {
"links": {
"related": "/v1/accounts/{ACCOUNT}/products/31339351-f7f5-4bdd-8346-5d8399a1ac07/machines"
}
},
"users": {
"links": {
"related": "/v1/accounts/{ACCOUNT}/products/31339351-f7f5-4bdd-8346-5d8399a1ac07/users"
}
},
"tokens": {
"links": {
"related": "/v1/accounts/{ACCOUNT}/products/31339351-f7f5-4bdd-8346-5d8399a1ac07/tokens"
}
}
}
}
}

linkRetrieve a product

Retrieves the details of an existing product.

linkAuthentication

  • linkBearer

    required

    An authentication token with privileges to view the resource: either an admin or the product.

linkURL Parameters

  • link:account

    string, required

    The identifier (UUID) or slug of your Keygen account.

  • link:id

    string, required

    The identifier (UUID) of the product to be retrieved.

linkReturns

A 200 OK response will be returned along with a product object.

Upon error, an errors object will be returned along with an HTTP status code indicating the type of error. When an error occurs, the data property will not be included.

Definition

https://api.keygen.sh/v1/accounts/{ACCOUNT}/products/{ID}

Example request

const fetch = require("node-fetch")
 
const response = await fetch("https://api.keygen.sh/v1/accounts/{ACCOUNT}/products/31339351-f7f5-4bdd-8346-5d8399a1ac07", {
method: "GET",
headers: {
"Accept": "application/vnd.api+json",
"Authorization": "Bearer {TOKEN}"
}
})
 
const { data, errors } = await response.json()
import requests
import json
 
res = requests.get(
"https://api.keygen.sh/v1/accounts/{ACCOUNT}/products/31339351-f7f5-4bdd-8346-5d8399a1ac07",
headers={
"Accept": "application/vnd.api+json",
"Authorization": "Bearer {TOKEN}"
}
).json()
import SwiftyJSON
import Alamofire
 
Alamofire.request("https://api.keygen.sh/v1/accounts/{ACCOUNT}/products/31339351-f7f5-4bdd-8346-5d8399a1ac07",
headers: [
"Accept": "application/vnd.api+json",
"Authorization": "Bearer {TOKEN}"
]
).responseJSON { response in
let json = JSON(data: response.data!)
}
using RestSharp;
 
var client = new RestClient("https://api.keygen.sh/v1/accounts/{ACCOUNT}");
var request = new RestRequest(
"products/31339351-f7f5-4bdd-8346-5d8399a1ac07",
Method.GET
);
 
request.AddHeader("Accept", "application/vnd.api+json");
request.AddHeader("Authorization", "Bearer {TOKEN}");
 
var response = client.Execute(request);
import com.mashape.unirest.http.exceptions.*
import com.mashape.unirest.http.*
 
val res = Unirest.get("https://api.keygen.sh/v1/accounts/{ACCOUNT}/products/31339351-f7f5-4bdd-8346-5d8399a1ac07")
.header("Authorization", "Bearer {TOKEN}")
.header("Accept", "application/vnd.api+json")
.asJson()
import com.mashape.unirest.http.exceptions.*;
import com.mashape.unirest.http.*;
 
HttpResponse<JsonNode> res = Unirest.get("https://api.keygen.sh/v1/accounts/{ACCOUNT}/products/31339351-f7f5-4bdd-8346-5d8399a1ac07")
.header("Authorization", "Bearer {TOKEN}")
.header("Accept", "application/vnd.api+json")
.asJson();
#include <iostream>
#include <string>
#include <cpprest/http_client.h>
#include <cpprest/filestream.h>
 
using namespace std;
using namespace web;
using namespace web::http;
using namespace web::http::client;
using namespace utility;
 
http_client client("https://api.keygen.sh/v1/accounts/{ACCOUNT}");
http_request req;
 
req.headers().add("Authorization", "Bearer {TOKEN}");
req.headers().add("Accept", "application/json");
 
req.set_request_uri("/products/31339351-f7f5-4bdd-8346-5d8399a1ac07");
req.set_method(methods::GET);
 
client.request(req)
.then([](http_response res) {
auto data = res.extract_json().get();
})
.wait();
curl https://api.keygen.sh/v1/accounts/{ACCOUNT}/products/31339351-f7f5-4bdd-8346-5d8399a1ac07 \
-H 'Accept: application/vnd.api+json' \
-H 'Authorization: Bearer {TOKEN}'

Example response / 200 OK

{
"data": {
"id": "31339351-f7f5-4bdd-8346-5d8399a1ac07",
"type": "products",
"links": {
"self": "/v1/accounts/{ACCOUNT}/products/31339351-f7f5-4bdd-8346-5d8399a1ac07"
},
"attributes": {
"name": "Product Hunt",
"url": "https://producthunt.com",
"platforms": ["iOS", "Android"],
"metadata": {},
"created": "2017-01-02T20:26:53.464Z",
"updated": "2017-01-02T20:26:53.464Z"
},
"relationships": {
"account": {
"links": {
"related": "/v1/accounts/{ACCOUNT}"
},
"data": {
"type": "accounts",
"id": "{ACCOUNT}"
}
},
"policies": {
"links": {
"related": "/v1/accounts/{ACCOUNT}/products/31339351-f7f5-4bdd-8346-5d8399a1ac07/policies"
}
},
"licenses": {
"links": {
"related": "/v1/accounts/{ACCOUNT}/products/31339351-f7f5-4bdd-8346-5d8399a1ac07/licenses"
}
},
"machines": {
"links": {
"related": "/v1/accounts/{ACCOUNT}/products/31339351-f7f5-4bdd-8346-5d8399a1ac07/machines"
}
},
"users": {
"links": {
"related": "/v1/accounts/{ACCOUNT}/products/31339351-f7f5-4bdd-8346-5d8399a1ac07/users"
}
},
"tokens": {
"links": {
"related": "/v1/accounts/{ACCOUNT}/products/31339351-f7f5-4bdd-8346-5d8399a1ac07/tokens"
}
}
}
}
}

linkUpdate a product

Updates the specified product resource by setting the values of the parameters passed. Any parameters not provided will be left unchanged.

linkAuthentication

  • linkBearer

    required

    An authentication token with privileges to manage the resource: either an admin or the product.

linkURL Parameters

  • link:account

    string, required

    The identifier (UUID) or slug of your Keygen account.

  • link:id

    string, required

    The identifier (UUID) of the product to be updated.

linkAttributes

  • linkdata.attributes.name

    string, optional

    The name of the product.

  • linkdata.attributes.url

    string, optional

    A related URL for the product e.g. the marketing website, company website, etc. Must be a valid URL.

  • linkdata.attributes.platforms

    array<string>, optional

    An array of platforms the product supports.

  • linkdata.attributes.metadata

    object<string, scalar>, optional

    Object containing product metadata.

linkReturns

A 200 OK response will be returned along with the updated product object.

Upon error, an errors object will be returned along with an HTTP status code indicating the type of error. When an error occurs, the data property will not be included.

Definition

https://api.keygen.sh/v1/accounts/{ACCOUNT}/products/{ID}

Example request

const fetch = require("node-fetch")
 
const response = await fetch("https://api.keygen.sh/v1/accounts/{ACCOUNT}/products/31339351-f7f5-4bdd-8346-5d8399a1ac07", {
method: "PATCH",
headers: {
"Content-Type": "application/vnd.api+json",
"Accept": "application/vnd.api+json",
"Authorization": "Bearer {TOKEN}"
},
body: JSON.stringify({
"data": {
"type": "products",
"attributes": {
"platforms": [
"iOS",
"Android",
"Windows"
]
}
}
})
})
 
const { data, errors } = await response.json()
import requests
import json
 
res = requests.patch(
"https://api.keygen.sh/v1/accounts/{ACCOUNT}/products/31339351-f7f5-4bdd-8346-5d8399a1ac07",
headers={
"Content-Type": "application/vnd.api+json",
"Accept": "application/vnd.api+json",
"Authorization": "Bearer {TOKEN}"
},
data=json.dumps({
"data": {
"type": "products",
"attributes": {
"platforms": [
"iOS",
"Android",
"Windows"
]
}
}
})
).json()
import SwiftyJSON
import Alamofire
 
Alamofire.request("https://api.keygen.sh/v1/accounts/{ACCOUNT}/products/31339351-f7f5-4bdd-8346-5d8399a1ac07",
method: .patch,
headers: [
"Content-Type": "application/vnd.api+json",
"Accept": "application/vnd.api+json",
"Authorization": "Bearer {TOKEN}"
],
parameters: [
"data": [
"type": "products",
"attributes": [
"platforms": [
"iOS",
"Android",
"Windows"
]
]
]
],
encoding: JSONEncoding.default
).responseJSON { response in
let json = JSON(data: response.data!)
}
using RestSharp;
 
var client = new RestClient("https://api.keygen.sh/v1/accounts/{ACCOUNT}");
var request = new RestRequest(
"products/31339351-f7f5-4bdd-8346-5d8399a1ac07",
Method.PATCH
);
 
request.AddHeader("Accept", "application/vnd.api+json");
request.AddHeader("Authorization", "Bearer {TOKEN}");
 
request.AddJsonBody(new {
data = new {
type = "products",
attributes = new {
platforms = new[] { "iOS", "Android", "Windows" }
}
}
});
 
var response = client.Execute(request);
import com.mashape.unirest.http.exceptions.*
import com.mashape.unirest.http.*
import org.json.*
 
val body = JSONObject(mapOf(
"data" to mapOf(
"type" to "products",
"attributes" to mapOf(
"platforms" to listOf("iOS", "Android", "Windows")
)
)
))
 
val res = Unirest.patch("https://api.keygen.sh/v1/accounts/{ACCOUNT}/products/31339351-f7f5-4bdd-8346-5d8399a1ac07")
.header("Authorization", "Bearer {TOKEN}")
.header("Content-Type", "application/vnd.api+json")
.header("Accept", "application/vnd.api+json")
.body(body)
.asJson()
import com.mashape.unirest.http.exceptions.*;
import com.mashape.unirest.http.*;
import org.json.*;
 
import static java.util.Map.ofEntries;
import static java.util.Map.entry;
import static java.util.List.of;
 
JSONObject body = new JSONObject(ofEntries(
entry("data", ofEntries(
entry("type", "products"),
entry("attributes", ofEntries(
entry("platforms", of("iOS", "Android", "Windows"))
))
))
));
 
HttpResponse<JsonNode> res = Unirest.patch("https://api.keygen.sh/v1/accounts/{ACCOUNT}/products/31339351-f7f5-4bdd-8346-5d8399a1ac07")
.header("Authorization", "Bearer {TOKEN}")
.header("Content-Type", "application/vnd.api+json")
.header("Accept", "application/vnd.api+json")
.body(body)
.asJson();
#include <iostream>
#include <string>
#include <cpprest/http_client.h>
#include <cpprest/filestream.h>
 
using namespace std;
using namespace web;
using namespace web::http;
using namespace web::http::client;
using namespace web::json;
using namespace utility;
 
http_client client("https://api.keygen.sh/v1/accounts/{ACCOUNT}");
http_request req;
 
value platforms;
platforms[0] = value::string("iOS");
platforms[1] = value::string("Android");
platforms[2] = value::string("Windows");
 
value attrs;
attrs["platforms"] = platforms;
 
value data;
data["type"] = value::string("products");
data["attributes"] = attrs;
 
value body;
body["data"] = data;
 
req.headers().add("Authorization", "Bearer {TOKEN}");
req.headers().add("Content-Type", "application/vnd.api+json");
req.headers().add("Accept", "application/json");
 
req.set_request_uri("/products/31339351-f7f5-4bdd-8346-5d8399a1ac07");
req.set_method(methods::PATCH);
req.set_body(body.serialize());
 
client.request(req)
.then([](http_response res)
{
auto data = res.extract_json().get();
})
.wait();
curl -X PATCH https://api.keygen.sh/v1/accounts/{ACCOUNT}/products/31339351-f7f5-4bdd-8346-5d8399a1ac07 \
-H 'Content-Type: application/vnd.api+json' \
-H 'Accept: application/vnd.api+json' \
-H 'Authorization: Bearer {TOKEN}' \
-d '{
"data": {
"type": "products",
"attributes": {
"platforms": [
"iOS",
"Android",
"Windows"
]
}
}
}'

Example response / 200 OK

{
"data": {
"id": "31339351-f7f5-4bdd-8346-5d8399a1ac07",
"type": "products",
"links": {
"self": "/v1/accounts/{ACCOUNT}/products/31339351-f7f5-4bdd-8346-5d8399a1ac07"
},
"attributes": {
"name": "Product Hunt",
"url": "https://producthunt.com",
"platforms": ["iOS", "Android", "Windows"],
"metadata": {},
"created": "2017-01-02T20:26:53.464Z",
"updated": "2017-01-02T20:26:53.464Z"
},
"relationships": {
"account": {
"links": {
"related": "/v1/accounts/{ACCOUNT}"
},
"data": {
"type": "accounts",
"id": "{ACCOUNT}"
}
},
"policies": {
"links": {
"related": "/v1/accounts/{ACCOUNT}/products/31339351-f7f5-4bdd-8346-5d8399a1ac07/policies"
}
},
"licenses": {
"links": {
"related": "/v1/accounts/{ACCOUNT}/products/31339351-f7f5-4bdd-8346-5d8399a1ac07/licenses"
}
},
"machines": {
"links": {
"related": "/v1/accounts/{ACCOUNT}/products/31339351-f7f5-4bdd-8346-5d8399a1ac07/machines"
}
},
"users": {
"links": {
"related": "/v1/accounts/{ACCOUNT}/products/31339351-f7f5-4bdd-8346-5d8399a1ac07/users"
}
},
"tokens": {
"links": {
"related": "/v1/accounts/{ACCOUNT}/products/31339351-f7f5-4bdd-8346-5d8399a1ac07/tokens"
}
}
}
}
}

linkDelete a product

Permanently deletes a product. It cannot be undone. This action also immediately deletes any policies, licenses and machines that the product is associated with.

linkAuthentication

  • linkBearer

    required

    An authentication token with privileges to manage the resource: either an admin or the product.

linkURL Parameters

  • link:account

    string, required

    The identifier (UUID) or slug of your Keygen account.

  • link:id

    string, required

    The identifier (UUID) of the product to be deleted.

linkReturns

A 204 No Content response will be returned.

Upon error, an errors object will be returned along with an HTTP status code indicating the type of error. When an error occurs, the data property will not be included.

Definition

https://api.keygen.sh/v1/accounts/{ACCOUNT}/products/{ID}

Example request

const fetch = require("node-fetch")
 
const response = await fetch("https://api.keygen.sh/v1/accounts/{ACCOUNT}/products/31339351-f7f5-4bdd-8346-5d8399a1ac07", {
method: "DELETE",
headers: {
"Accept": "application/vnd.api+json",
"Authorization": "Bearer {TOKEN}"
}
})
import requests
 
res = requests.delete(
"https://api.keygen.sh/v1/accounts/{ACCOUNT}/products/31339351-f7f5-4bdd-8346-5d8399a1ac07",
headers={
"Accept": "application/vnd.api+json",
"Authorization": "Bearer {TOKEN}"
}
)
import SwiftyJSON
import Alamofire
 
Alamofire.request("https://api.keygen.sh/v1/accounts/{ACCOUNT}/products/31339351-f7f5-4bdd-8346-5d8399a1ac07",
method: .delete,
headers: [
"Accept": "application/vnd.api+json",
"Authorization": "Bearer {TOKEN}"
]
).responseJSON { response in
let status = response.response?.statusCode
}
using RestSharp;
 
var client = new RestClient("https://api.keygen.sh/v1/accounts/{ACCOUNT}");
var request = new RestRequest(
"products/31339351-f7f5-4bdd-8346-5d8399a1ac07",
Method.DELETE
);
 
request.AddHeader("Accept", "application/vnd.api+json");
request.AddHeader("Authorization", "Bearer {TOKEN}");
 
var response = client.Execute(request);
import com.mashape.unirest.http.exceptions.*
import com.mashape.unirest.http.*
 
val res = Unirest.delete("https://api.keygen.sh/v1/accounts/{ACCOUNT}/products/31339351-f7f5-4bdd-8346-5d8399a1ac07")
.header("Authorization", "Bearer {TOKEN}")
.header("Accept", "application/vnd.api+json")
.asJson()
import com.mashape.unirest.http.exceptions.*;
import com.mashape.unirest.http.*;
 
HttpResponse<JsonNode> res = Unirest.delete("https://api.keygen.sh/v1/accounts/{ACCOUNT}/products/31339351-f7f5-4bdd-8346-5d8399a1ac07")
.header("Authorization", "Bearer {TOKEN}")
.header("Accept", "application/vnd.api+json")
.asJson();
#include <iostream>
#include <string>
#include <cpprest/http_client.h>
#include <cpprest/filestream.h>
 
using namespace std;
using namespace web;
using namespace web::http;
using namespace web::http::client;
using namespace utility;
 
http_client client("https://api.keygen.sh/v1/accounts/{ACCOUNT}");
http_request req;
 
req.headers().add("Authorization", "Bearer {TOKEN}");
req.headers().add("Accept", "application/json");
 
req.set_request_uri("/products/31339351-f7f5-4bdd-8346-5d8399a1ac07");
req.set_method(methods::DELETE);
 
client.request(req)
.then([](http_response res) {
auto status = res.status_code();
})
.wait();
curl -X DELETE https://api.keygen.sh/v1/accounts/{ACCOUNT}/products/31339351-f7f5-4bdd-8346-5d8399a1ac07 \
-H 'Accept: application/vnd.api+json' \
-H 'Authorization: Bearer {TOKEN}'

Example response / 204 No Content

No content

linkList all products

Returns a list of products. The products are returned sorted by creation date, with the most recent products appearing first.

linkAuthentication

  • linkBearer

    required

    An authentication token with admin privileges.

linkURL Parameters

  • link:account

    string, required

    The identifier (UUID) or slug of your Keygen account.

linkFilters

  • linklimit

    integer, default is10

    A limit on the number of products to be returned. Limit must be a number between 1 and 100.

    https://api.keygen.sh/v1/accounts/{ACCOUNT}/products?limit=25
  • linkpage

    object<string, integer>

    Object containing page size and page number. Page size must be a number between 1 and 100

    https://api.keygen.sh/v1/accounts/{ACCOUNT}/products?page[size]=15&page[number]=2

linkReturns

A 200 OK response will be returned along with a list of product objects.

Upon error, an errors object will be returned along with an HTTP status code indicating the type of error. When an error occurs, the data property will not be included.

Definition

https://api.keygen.sh/v1/accounts/{ACCOUNT}/products{FILTERS}

Example request

const fetch = require("node-fetch")
 
const response = await fetch("https://api.keygen.sh/v1/accounts/{ACCOUNT}/products?limit=15", {
method: "GET",
headers: {
"Accept": "application/vnd.api+json",
"Authorization": "Bearer {TOKEN}"
}
})
 
const { data, errors } = await response.json()
import requests
import json
 
res = requests.get(
"https://api.keygen.sh/v1/accounts/{ACCOUNT}/products?limit=15",
headers={
"Accept": "application/vnd.api+json",
"Authorization": "Bearer {TOKEN}"
}
).json()
import SwiftyJSON
import Alamofire
 
Alamofire.request("https://api.keygen.sh/v1/accounts/{ACCOUNT}/products?limit=15",
headers: [
"Accept": "application/vnd.api+json",
"Authorization": "Bearer {TOKEN}"
]
).responseJSON { response in
let json = JSON(data: response.data!)
}
using RestSharp;
 
var client = new RestClient("https://api.keygen.sh/v1/accounts/{ACCOUNT}");
var request = new RestRequest("products", Method.GET);
 
request.AddHeader("Accept", "application/vnd.api+json");
request.AddHeader("Authorization", "Bearer {TOKEN}");
 
request.AddParameter("limit", 15);
 
var response = client.Execute(request);
import com.mashape.unirest.http.exceptions.*
import com.mashape.unirest.http.*
 
val res = Unirest.get("https://api.keygen.sh/v1/accounts/{ACCOUNT}/products")
.header("Authorization", "Bearer {TOKEN}")
.header("Accept", "application/vnd.api+json")
.queryString("limit", 15)
.asJson()
import com.mashape.unirest.http.exceptions.*;
import com.mashape.unirest.http.*;
 
HttpResponse<JsonNode> res = Unirest.get("https://api.keygen.sh/v1/accounts/{ACCOUNT}/products")
.header("Authorization", "Bearer {TOKEN}")
.header("Accept", "application/vnd.api+json")
.queryString("limit", 15)
.asJson();
#include <iostream>
#include <string>
#include <cpprest/http_client.h>
#include <cpprest/filestream.h>
 
using namespace std;
using namespace web;
using namespace web::http;
using namespace web::http::client;
using namespace utility;
 
http_client client("https://api.keygen.sh/v1/accounts/{ACCOUNT}");
http_request req;
 
req.headers().add("Authorization", "Bearer {TOKEN}");
req.headers().add("Accept", "application/json");
 
uri_builder uri("/products");
uri.append_query("limit", 15);
 
req.set_request_uri(uri.to_uri());
req.set_method(methods::GET);
 
client.request(req)
.then([](http_response res) {
auto data = res.extract_json().get();
})
.wait();
curl https://api.keygen.sh/v1/accounts/{ACCOUNT}/products?limit=15 -g \
-H 'Accept: application/vnd.api+json' \
-H 'Authorization: Bearer {TOKEN}'

Example response / 200 OK

{
"data": [
{
"id": "31339351-f7f5-4bdd-8346-5d8399a1ac07",
"type": "products",
"links": {
"self": "/v1/accounts/{ACCOUNT}/products/31339351-f7f5-4bdd-8346-5d8399a1ac07"
},
"attributes": {
"name": "Product Hunt",
"url": "https://producthunt.com",
"platforms": ["iOS", "Android"],
"metadata": {},
"created": "2017-01-02T20:26:53.464Z",
"updated": "2017-01-02T20:26:53.464Z"
},
"relationships": {
"account": {
"links": {
"related": "/v1/accounts/{ACCOUNT}"
},
"data": {
"type": "accounts",
"id": "{ACCOUNT}"
}
},
"policies": {
"links": {
"related": "/v1/accounts/{ACCOUNT}/products/31339351-f7f5-4bdd-8346-5d8399a1ac07/policies"
}
},
"licenses": {
"links": {
"related": "/v1/accounts/{ACCOUNT}/products/31339351-f7f5-4bdd-8346-5d8399a1ac07/licenses"
}
},
"machines": {
"links": {
"related": "/v1/accounts/{ACCOUNT}/products/31339351-f7f5-4bdd-8346-5d8399a1ac07/machines"
}
},
"users": {
"links": {
"related": "/v1/accounts/{ACCOUNT}/products/31339351-f7f5-4bdd-8346-5d8399a1ac07/users"
}
},
"tokens": {
"links": {
"related": "/v1/accounts/{ACCOUNT}/products/31339351-f7f5-4bdd-8346-5d8399a1ac07/tokens"
}
}
}
},
]
}

linkGenerate a product token

Generates a new product token resource. Product tokens do not expire.

Product tokens should not be included in any client-facing code, as they offer full access to all of the product's resources. Only use these tokens server-side e.g. to consume webhooks or to create new resources in response to events from your payment provider.

linkAuthentication

  • linkBearer

    required

    An authentication token with admin privileges.

linkURL Parameters

  • link:account

    string, required

    The identifier (UUID) or slug of your Keygen account.

  • link:id

    string, required

    The identifier (UUID) of the product to generate a token for.

linkReturns

A 200 OK response will be returned along with the new token object.

Upon error, an errors object will be returned along with an HTTP status code indicating the type of error. When an error occurs, the data property will not be included.

Definition

https://api.keygen.sh/v1/accounts/{ACCOUNT}/products/{ID}/tokens

Example request

const fetch = require("node-fetch")
 
const response = await fetch("https://api.keygen.sh/v1/accounts/{ACCOUNT}/products/31339351-f7f5-4bdd-8346-5d8399a1ac07/tokens", {
method: "POST",
headers: {
"Accept": "application/vnd.api+json",
"Authorization": "Bearer {TOKEN}"
}
})
 
const { data, errors } = await response.json()
import requests
import json
 
res = requests.post(
"https://api.keygen.sh/v1/accounts/{ACCOUNT}/products/31339351-f7f5-4bdd-8346-5d8399a1ac07/tokens",
headers={
"Accept": "application/vnd.api+json",
"Authorization": "Bearer {TOKEN}"
}
).json()
import SwiftyJSON
import Alamofire
 
Alamofire.request("https://api.keygen.sh/v1/accounts/{ACCOUNT}/products/31339351-f7f5-4bdd-8346-5d8399a1ac07/tokens",
method: .post,
headers: [
"Accept": "application/vnd.api+json",
"Authorization": "Bearer {TOKEN}"
]
).responseJSON { response in
let json = JSON(data: response.data!)
}
using RestSharp;
 
var client = new RestClient("https://api.keygen.sh/v1/accounts/{ACCOUNT}");
var request = new RestRequest(
"products/31339351-f7f5-4bdd-8346-5d8399a1ac07/tokens",
Method.POST
);
 
request.AddHeader("Accept", "application/vnd.api+json");
request.AddHeader("Authorization", "Bearer {TOKEN}");
 
var response = client.Execute(request);
import com.mashape.unirest.http.exceptions.*
import com.mashape.unirest.http.*
 
val res = Unirest.post("https://api.keygen.sh/v1/accounts/{ACCOUNT}/products/31339351-f7f5-4bdd-8346-5d8399a1ac07/tokens")
.header("Authorization", "Bearer {TOKEN}")
.header("Accept", "application/vnd.api+json")
.asJson()
import com.mashape.unirest.http.exceptions.*;
import com.mashape.unirest.http.*;
 
HttpResponse<JsonNode> res = Unirest.post("https://api.keygen.sh/v1/accounts/{ACCOUNT}/products/31339351-f7f5-4bdd-8346-5d8399a1ac07/tokens")
.header("Authorization", "Bearer {TOKEN}")
.header("Accept", "application/vnd.api+json")
.asJson();
#include <iostream>
#include <string>
#include <cpprest/http_client.h>
#include <cpprest/filestream.h>
 
using namespace std;
using namespace web;
using namespace web::http;
using namespace web::http::client;
using namespace utility;
 
http_client client("https://api.keygen.sh/v1/accounts/{ACCOUNT}");
http_request req;
 
req.headers().add("Authorization", "Bearer {TOKEN}");
req.headers().add("Accept", "application/json");
 
req.set_request_uri("/products/31339351-f7f5-4bdd-8346-5d8399a1ac07/tokens");
req.set_method(methods::POST);
 
client.request(req)
.then([](http_response res) {
auto data = res.extract_json().get();
})
.wait();
curl -X POST https://api.keygen.sh/v1/accounts/{ACCOUNT}/products/31339351-f7f5-4bdd-8346-5d8399a1ac07/tokens \
-H 'Accept: application/vnd.api+json' \
-H 'Authorization: Bearer {TOKEN}'

Example response / 200 OK

{
"data": {
"id": "07d52aa8-b96c-4b55-b05d-f5f570e1775a",
"type": "tokens",
"attributes": {
"kind": "product-token",
"token": "prod-2ddd064509b6bcaa356958dcce6da3a538919e13ddbc26b359fb374ff89dfacav3",
"expiry": "2022-03-15T19:27:50.440Z",
"created": "2017-01-02T20:26:53.464Z",
"updated": "2017-01-02T20:26:53.464Z"
},
"relationships": {
"account": {
"links": {
"related": "/v1/accounts/{ACCOUNT}"
},
"data": {
"type": "accounts",
"id": "{ACCOUNT}"
}
},
"bearer": {
"links": {
"related": "/v1/accounts/{ACCOUNT}/products/31339351-f7f5-4bdd-8346-5d8399a1ac07"
},
"data": {
"type": "products",
"id": "31339351-f7f5-4bdd-8346-5d8399a1ac07"
}
}
}
}
}

linkEntitlements

linkThe entitlement object

Below you will find the various attributes for the entitlement resource. Entitlements can be attached to policies and to licenses to grant named "permissions" for things such as application features.

Entitlements can be attached to the following resources:

  • Policies: Any entitlement attached to a policy will automatically be attached to all licenses which implement the given policy.
  • Licenses: Entitlements attached to a license are one-off and only apply to that specific license resource.
  • Releases: Entitlements can be attached to releases through constraints, in order to assert that a given licensee possesses the neccessary entitlements before being allowed to access a release's artifacts.

linkAttributes

  • linkdata.attributes.name

    string

    The name of the entitlement.

  • linkdata.attributes.code

    string

    The unique code for the entitlement. This can be used within license validation requests to assert a license possesses a given entitlement.

  • linkdata.attributes.created

    timestamp (ISO8601 format)read only

    When the entitlement was created.

  • linkdata.attributes.updated

    timestamp (ISO8601 format)read only

    When the entitlement was last updated.

  • linkdata.attributes.metadata

    object<string, scalar>

    Object containing entitlement metadata.

linkRelationships

  • linkdata.relationships.account

    individual

    The account that the entitlement belongs to.

Example object

{
"data": {
"id": "db1ff21b-f42f-4623-952b-ca7f2600bded",
"type": "entitlements",
"attributes": {
"name": "Example Feature",
"code": "EXAMPLE_FEATURE",
"metadata": {},
"created": "2017-01-02T20:26:53.464Z",
"updated": "2017-01-02T20:26:53.464Z"
},
"relationships": {
"account": {
"links": {
"related": "/v1/accounts/{ACCOUNT}"
},
"data": {
"type": "accounts",
"id": "{ACCOUNT}"
}
}
},
"links": {
"self": "/v1/accounts/{ACCOUNT}/entitlements/db1ff21b-f42f-4623-952b-ca7f2600bded"
}
}
}

linkCreate an entitlement

Creates a new entitlement resource.

linkAuthentication

  • linkBearer

    required

    An authentication token with privileges to create the resource: an admin.

linkURL Parameters

  • link:account

    string, required

    The identifier (UUID) or slug of your Keygen account.

linkAttributes

  • linkdata.attributes.name

    string, required

    The name of the entitlement.

  • linkdata.attributes.code

    string, required

    The unique code for the entitlement. The code cannot collide with any entitlements that already exist.

  • linkdata.attributes.metadata

    object<string, scalar>, optional

    Object containing entitlement metadata.

linkReturns

A 201 Created response will be returned along with the new entitlement object.

Upon error, an errors object will be returned along with an HTTP status code indicating the type of error. When an error occurs, the data property will not be included.

Definition

https://api.keygen.sh/v1/accounts/{ACCOUNT}/entitlements

Example request

const fetch = require("node-fetch")
 
const response = await fetch("https://api.keygen.sh/v1/accounts/{ACCOUNT}/entitlements", {
method: "POST",
headers: {
"Content-Type": "application/vnd.api+json",
"Accept": "application/vnd.api+json",
"Authorization": "Bearer {TOKEN}"
},
body: JSON.stringify({
"data": {
"type": "entitlements",
"attributes": {
"name": "Example Entitlement",
"code": "EXAMPLE_ENTITLEMENT"
}
}
})
})
 
const { data, errors } = await response.json()
import requests
import json
 
res = requests.post(
"https://api.keygen.sh/v1/accounts/{ACCOUNT}/entitlements",
headers={
"Content-Type": "application/vnd.api+json",
"Accept": "application/vnd.api+json",
"Authorization": "Bearer {TOKEN}"
},
data=json.dumps({
"data": {
"type": "entitlements",
"attributes": {
"name": "Example Entitlement",
"code": "EXAMPLE_ENTITLEMENT"
}
}
})
).json()
import SwiftyJSON
import Alamofire
 
Alamofire.request("https://api.keygen.sh/v1/accounts/{ACCOUNT}/entitlements",
method: .post,
headers: [
"Content-Type": "application/vnd.api+json",
"Accept": "application/vnd.api+json",
"Authorization": "Bearer {TOKEN}"
],
parameters: [
"data": [
"type": "entitlements",
"attributes": [
"name": "Example Entitlement",
"code": "EXAMPLE_ENTITLEMENT"
]
]
],
encoding: JSONEncoding.default
).responseJSON { response in
let json = JSON(data: response.data!)
}
using RestSharp;
 
var client = new RestClient("https://api.keygen.sh/v1/accounts/{ACCOUNT}");
var request = new RestRequest("entitlements", Method.POST);
 
request.AddHeader("Content-Type", "application/vnd.api+json");
request.AddHeader("Accept", "application/vnd.api+json");
request.AddHeader("Authorization", "Bearer {TOKEN}");
 
request.AddJsonBody(new {
data = new {
type = "entitlements",
attributes = new {
name = "Example Entitlement",
code = "EXAMPLE_ENTITLEMENT"
}
}
});
 
var response = client.Execute(request);
import com.mashape.unirest.http.exceptions.*
import com.mashape.unirest.http.*
import org.json.*
 
val body = JSONObject(mapOf(
"data" to mapOf(
"type" to "entitlements",
"attributes" to mapOf(
"name" to "Example Entitlement",
"code" to "EXAMPLE_ENTITLEMENT"
)
)
))
 
val res = Unirest.post("https://api.keygen.sh/v1/accounts/{ACCOUNT}/entitlements")
.header("Authorization", "Bearer {TOKEN}")
.header("Content-Type", "application/vnd.api+json")
.header("Accept", "application/vnd.api+json")
.body(body)
.asJson()
import com.mashape.unirest.http.exceptions.*;
import com.mashape.unirest.http.*;
import org.json.*;
 
import static java.util.Map.ofEntries;
import static java.util.Map.entry;
 
JSONObject body = new JSONObject(ofEntries(
entry("data", ofEntries(
entry("type", "entitlements"),
entry("attributes", ofEntries(
entry("name", "Example Entitlement"),
entry("code", "EXAMPLE_ENTITLEMENT")
))
))
));
 
HttpResponse<JsonNode> res = Unirest.post("https://api.keygen.sh/v1/accounts/{ACCOUNT}/entitlements")
.header("Authorization", "Bearer {TOKEN}")
.header("Content-Type", "application/vnd.api+json")
.header("Accept", "application/vnd.api+json")
.body(body)
.asJson();
#include <iostream>
#include <string>
#include <cpprest/http_client.h>
#include <cpprest/filestream.h>
 
using namespace std;
using namespace web;
using namespace web::http;
using namespace web::http::client;
using namespace web::json;
using namespace utility;
 
http_client client("https://api.keygen.sh/v1/accounts/{ACCOUNT}");
http_request req;
 
value attrs;
attrs["name"] = value::string("Example Entitlement");
attrs["code"] = value::string("EXAMPLE_ENTITLEMENT");
 
value data;
data["type"] = value::string("entitlements");
data["attributes"] = attrs;
 
value body;
body["data"] = data;
 
req.headers().add("Authorization", "Bearer {TOKEN}");
req.headers().add("Content-Type", "application/vnd.api+json");
req.headers().add("Accept", "application/json");
 
req.set_request_uri("/entitlements");
req.set_method(methods::POST);
req.set_body(body.serialize());
 
client.request(req)
.then([](http_response res)
{
auto data = res.extract_json().get();
})
.wait();
curl -X POST https://api.keygen.sh/v1/accounts/{ACCOUNT}/entitlements \
-H 'Content-Type: application/vnd.api+json' \
-H 'Accept: application/vnd.api+json' \
-H 'Authorization: Bearer {TOKEN}' \
-d '{
"data": {
"type": "entitlements",
"attributes": {
"name": "Example Feature",
"code": "EXAMPLE_FEATURE",
}
}
}'

Example response / 201 Created

{
"data": {
"id": "db1ff21b-f42f-4623-952b-ca7f2600bded",
"type": "entitlements",
"attributes": {
"name": "Example Feature",
"code": "EXAMPLE_FEATURE",
"metadata": {},
"created": "2017-01-02T20:26:53.464Z",
"updated": "2017-01-02T20:26:53.464Z"
},
"relationships": {
"account": {
"links": {
"related": "/v1/accounts/{ACCOUNT}"
},
"data": {
"type": "accounts",
"id": "{ACCOUNT}"
}
}
},
"links": {
"self": "/v1/accounts/{ACCOUNT}/entitlements/db1ff21b-f42f-4623-952b-ca7f2600bded"
}
}
}

linkRetrieve an entitlement

Retrieves the details of an existing entitlement.

linkAuthentication

  • linkBearer

    required

    An authentication token with privileges to view the resource: either an admin or a product.

linkURL Parameters

  • link:account

    string, required

    The identifier (UUID) or slug of your Keygen account.

  • link:id

    string, required

    The identifier (UUID) of the entitlement to be retrieved.

linkReturns

A 200 OK response will be returned along with an entitlement object.

Upon error, an errors object will be returned along with an HTTP status code indicating the type of error. When an error occurs, the data property will not be included.

Definition

https://api.keygen.sh/v1/accounts/{ACCOUNT}/entitlements/{ID}

Example request

const fetch = require("node-fetch")
 
const response = await fetch("https://api.keygen.sh/v1/accounts/{ACCOUNT}/entitlements/db1ff21b-f42f-4623-952b-ca7f2600bded", {
method: "GET",
headers: {
"Accept": "application/vnd.api+json",
"Authorization": "Bearer {TOKEN}"
}
})
 
const { data, errors } = await response.json()
import requests
import json
 
res = requests.get(
"https://api.keygen.sh/v1/accounts/{ACCOUNT}/entitlements/db1ff21b-f42f-4623-952b-ca7f2600bded",
headers={
"Accept": "application/vnd.api+json",
"Authorization": "Bearer {TOKEN}"
}
).json()
import SwiftyJSON
import Alamofire
 
Alamofire.request("https://api.keygen.sh/v1/accounts/{ACCOUNT}/entitlements/db1ff21b-f42f-4623-952b-ca7f2600bded",
headers: [
"Accept": "application/vnd.api+json",
"Authorization": "Bearer {TOKEN}"
]
).responseJSON { response in
let json = JSON(data: response.data!)
}
using RestSharp;
 
var client = new RestClient("https://api.keygen.sh/v1/accounts/{ACCOUNT}");
var request = new RestRequest(
"entitlements/db1ff21b-f42f-4623-952b-ca7f2600bded",
Method.GET
);
 
request.AddHeader("Accept", "application/vnd.api+json");
request.AddHeader("Authorization", "Bearer {TOKEN}");
 
var response = client.Execute(request);
import com.mashape.unirest.http.exceptions.*
import com.mashape.unirest.http.*
 
val res = Unirest.get("https://api.keygen.sh/v1/accounts/{ACCOUNT}/entitlements/db1ff21b-f42f-4623-952b-ca7f2600bded")
.header("Authorization", "Bearer {TOKEN}")
.header("Accept", "application/vnd.api+json")
.asJson()
import com.mashape.unirest.http.exceptions.*;
import com.mashape.unirest.http.*;
 
HttpResponse<JsonNode> res = Unirest.get("https://api.keygen.sh/v1/accounts/{ACCOUNT}/entitlements/db1ff21b-f42f-4623-952b-ca7f2600bded")
.header("Authorization", "Bearer {TOKEN}")
.header("Accept", "application/vnd.api+json")
.asJson();
#include <iostream>
#include <string>
#include <cpprest/http_client.h>
#include <cpprest/filestream.h>
 
using namespace std;
using namespace web;
using namespace web::http;
using namespace web::http::client;
using namespace utility;
 
http_client client("https://api.keygen.sh/v1/accounts/{ACCOUNT}");
http_request req;
 
req.headers().add("Authorization", "Bearer {TOKEN}");
req.headers().add("Accept", "application/json");
 
req.set_request_uri("/entitlements/db1ff21b-f42f-4623-952b-ca7f2600bded");
req.set_method(methods::GET);
 
client.request(req)
.then([](http_response res) {
auto data = res.extract_json().get();
})
.wait();
curl https://api.keygen.sh/v1/accounts/{ACCOUNT}/entitlements/db1ff21b-f42f-4623-952b-ca7f2600bded \
-H 'Accept: application/vnd.api+json' \
-H 'Authorization: Bearer {TOKEN}'

Example response / 200 OK

{
"data": {
"id": "db1ff21b-f42f-4623-952b-ca7f2600bded",
"type": "entitlements",
"attributes": {
"name": "Example Feature",
"code": "EXAMPLE_FEATURE",
"metadata": {},
"created": "2017-01-02T20:26:53.464Z",
"updated": "2017-01-02T20:26:53.464Z"
},
"relationships": {
"account": {
"links": {
"related": "/v1/accounts/{ACCOUNT}"
},
"data": {
"type": "accounts",
"id": "{ACCOUNT}"
}
}
},
"links": {
"self": "/v1/accounts/{ACCOUNT}/entitlements/db1ff21b-f42f-4623-952b-ca7f2600bded"
}
}
}

linkUpdate an entitlement

Updates the specified entitlement resource by setting the values of the parameters passed. Any parameters not provided will be left unchanged.

Renaming an entitlement code that is already in use may cause license validations using the old entitlement code to fail. We suggest making sure the existing code is no longer in use before changing it, to prevent unintented license validation failures.

linkAuthentication

  • linkBearer

    required

    An authentication token with privileges to manage the resource: an admin.

linkURL Parameters

  • link:account

    string, required

    The identifier (UUID) or slug of your Keygen account.

  • link:id

    string, required

    The identifier (UUID) of the entitlement to be updated.

linkAttributes

  • linkdata.attributes.name

    string, optional

    The name of the entitlement.

  • linkdata.attributes.code

    string, optional

    The unique code for the entitlement.

linkReturns

A 200 OK response will be returned along with the updated entitlement object.

Upon error, an errors object will be returned along with an HTTP status code indicating the type of error. When an error occurs, the data property will not be included.

Definition

https://api.keygen.sh/v1/accounts/{ACCOUNT}/entitlements/{ID}

Example request

const fetch = require("node-fetch")
 
const response = await fetch("https://api.keygen.sh/v1/accounts/{ACCOUNT}/entitlements/db1ff21b-f42f-4623-952b-ca7f2600bded", {
method: "PATCH",
headers: {
"Content-Type": "application/vnd.api+json",
"Accept": "application/vnd.api+json",
"Authorization": "Bearer {TOKEN}"
},
body: JSON.stringify({
"data": {
"type": "entitlements",
"attributes": {
"code": "EXAMPLE_ENTITLEMENT_CODE"
}
}
})
})
 
const { data, errors } = await response.json()
import requests
import json
 
res = requests.patch(
"https://api.keygen.sh/v1/accounts/{ACCOUNT}/entitlements/db1ff21b-f42f-4623-952b-ca7f2600bded",
headers={
"Content-Type": "application/vnd.api+json",
"Accept": "application/vnd.api+json",
"Authorization": "Bearer {TOKEN}"
},
data=json.dumps({
"data": {
"type": "entitlements",
"attributes": {
"code": "EXAMPLE_ENTITLEMENT_CODE"
}
}
})
).json()
import SwiftyJSON
import Alamofire
 
Alamofire.request("https://api.keygen.sh/v1/accounts/{ACCOUNT}/entitlements/db1ff21b-f42f-4623-952b-ca7f2600bded",
method: .patch,
headers: [
"Content-Type": "application/vnd.api+json",
"Accept": "application/vnd.api+json",
"Authorization": "Bearer {TOKEN}"
],
parameters: [
"data": [
"type": "entitlements",
"attributes": [
"code": "EXAMPLE_ENTITLEMENT_CODE"
]
]
],
encoding: JSONEncoding.default
).responseJSON { response in
let json = JSON(data: response.data!)
}
using RestSharp;
 
var client = new RestClient("https://api.keygen.sh/v1/accounts/{ACCOUNT}");
var request = new RestRequest(
"entitlements/db1ff21b-f42f-4623-952b-ca7f2600bded",
Method.PATCH
);
 
request.AddHeader("Content-Type", "application/vnd.api+json");
request.AddHeader("Accept", "application/vnd.api+json");
request.AddHeader("Authorization", "Bearer {TOKEN}");
 
request.AddJsonBody(new {
data = new {
type = "entitlements",
attributes = new {
code = "EXAMPLE_ENTITLEMENT_CODE"
}
}
});
 
var response = client.Execute(request);
import com.mashape.unirest.http.exceptions.*
import com.mashape.unirest.http.*
import org.json.*
 
val body = JSONObject(mapOf(
"data" to mapOf(
"type" to "entitlements",
"attributes" to mapOf(
"code" to "EXAMPLE_ENTITLEMENT_CODE"
)
)
))
 
val res = Unirest.patch("https://api.keygen.sh/v1/accounts/{ACCOUNT}/entitlements/db1ff21b-f42f-4623-952b-ca7f2600bded")
.header("Authorization", "Bearer {TOKEN}")
.header("Content-Type", "application/vnd.api+json")
.header("Accept", "application/vnd.api+json")
.body(body)
.asJson()
import com.mashape.unirest.http.exceptions.*;
import com.mashape.unirest.http.*;
import org.json.*;
 
import static java.util.Map.ofEntries;
import static java.util.Map.entry;
 
JSONObject body = new JSONObject(ofEntries(
entry("data", ofEntries(
entry("type", "entitlements"),
entry("attributes", ofEntries(
entry("code", "EXAMPLE_ENTITLEMENT_CODE")
))
))
));
 
HttpResponse<JsonNode> res = Unirest.patch("https://api.keygen.sh/v1/accounts/{ACCOUNT}/entitlements/db1ff21b-f42f-4623-952b-ca7f2600bded")
.header("Authorization", "Bearer {TOKEN}")
.header("Content-Type", "application/vnd.api+json")
.header("Accept", "application/vnd.api+json")
.body(body)
.asJson();
#include <iostream>
#include <string>
#include <cpprest/http_client.h>
#include <cpprest/filestream.h>
 
using namespace std;
using namespace web;
using namespace web::http;
using namespace web::http::client;
using namespace web::json;
using namespace utility;
 
http_client client("https://api.keygen.sh/v1/accounts/{ACCOUNT}");
http_request req;
 
value attrs;
attrs["code"] = value::string("EXAMPLE_ENTITLEMENT_CODE");
 
value data;
data["type"] = value::string("entitlements");
data["attributes"] = attrs;
 
value body;
body["data"] = data;
 
req.headers().add("Authorization", "Bearer {TOKEN}");
req.headers().add("Content-Type", "application/vnd.api+json");
req.headers().add("Accept", "application/json");
 
req.set_request_uri("/entitlements/db1ff21b-f42f-4623-952b-ca7f2600bded");
req.set_method(methods::PATCH);
req.set_body(body.serialize());
 
client.request(req)
.then([](http_response res)
{
auto data = res.extract_json().get();
})
.wait();
curl -X PATCH https://api.keygen.sh/v1/accounts/{ACCOUNT}/entitlements/db1ff21b-f42f-4623-952b-ca7f2600bded \
-H 'Content-Type: application/vnd.api+json' \
-H 'Accept: application/vnd.api+json' \
-H 'Authorization: Bearer {TOKEN}' \
-d '{
"data": {
"type": "entitlements",
"attributes": {
"code": "EXAMPLE_ENTITLEMENT_CODE"
}
}
}'

Example response / 200 OK

{
"data": {
"id": "db1ff21b-f42f-4623-952b-ca7f2600bded",
"type": "entitlements",
"attributes": {
"name": "Example Feature",
"code": "EXAMPLE_ENTITLEMENT_CODE",
"metadata": {},
"created": "2017-01-02T20:26:53.464Z",
"updated": "2017-01-02T20:26:53.464Z"
},
"relationships": {
"account": {
"links": {
"related": "/v1/accounts/{ACCOUNT}"
},
"data": {
"type": "accounts",
"id": "{ACCOUNT}"</