Use Keygen to license and distribute commercial Python packages

Securely license and distribute private Python packages with a single API.


Free during development, no upfront commitment

5 stars
Demo of the Keygen admin dashboard
export KEYGEN_LICENSE_KEY='C1B6DE-39A6E3-DE1529-8559A0-4AF593-V3'
 
cat ./requirements.txt
 
--extra-index-url https://license:${KEYGEN_LICENSE_KEY}@get.keygen.sh/demo/pip
private-package>=1.0.0
 
pip install -r requirements.txt
# => Collecting private-package==1.0.0
# => Downloading private-package-1.0.0.tar.gz (335 kB)
# => |████████████████████████████████| 335 kB 9.6 MB/s
# => Building wheels for collected packages: private-package
# => Building wheel for private-package (setup.py) ... done
# => Created wheel for private-package: filename=private-package-1.0.0-py3-none-any.whl size=360812
# => Stored in directory: /pip/wheels/fa/12/21/ffb9a123ac350ce41c003077e595b1ab2eeafebc1b2b88a564
# => Successfully built private-package
# => Successfully installed private-package-1.0.0

From license keys to package distribution, we can help.

  • checkUse our software distribution API as a private package store. Securely distribute your private Python repository to licensed users, installable with pip install.
  • checkProtect your code with our flagship software licensing API. Add fine-grained license entitlements, restrict upgrades to specific version ranges, enforce license activation limits, and more.
Get Started with Keygen

Bring your own programming language

Quickly implement Keygen's API in any programming language.

package main
 
import (
"github.com/denisbrodbeck/machineid"
"github.com/keygen-sh/keygen-go/v2"
)
 
func main() {
keygen.Account = "YOUR_KEYGEN_ACCOUNT_ID"
keygen.Product = "YOUR_KEYGEN_PRODUCT_ID"
keygen.LicenseKey = "key/..."
 
fingerprint, err := machineid.ProtectedID(keygen.Product)
if err != nil {
panic(err)
}
 
// Validate the license for the current fingerprint
license, err := keygen.Validate(fingerprint)
switch {
case err == keygen.ErrLicenseNotActivated:
// Activate the current fingerprint
machine, err := license.Activate(fingerprint)
switch {
case err == keygen.ErrMachineLimitExceeded:
panic("Machine limit has been exceeded!")
case err != nil:
panic("Machine activation failed!")
}
case err == keygen.ErrLicenseExpired:
panic("License is expired!")
case err != nil:
panic("License is invalid!")
}
 
fmt.Println("License is activated!")
}
const fetch = require('node-fetch')
 
const res = await fetch('https://api.keygen.sh/v1/accounts/demo/licenses/actions/validate-key', {
method: 'POST',
headers: {
'Content-Type': 'application/vnd.api+json',
'Accept': 'application/vnd.api+json'
},
body: JSON.stringify({
meta: {
key: 'C1B6DE-39A6E3-DE1529-8559A0-4AF593-V3'
}
})
})
 
const { meta } = await res.json()
 
if (meta.valid) {
// Do something
} else {
// Do something else
}
import SwiftyJSON
import Alamofire
 
Alamofire.request("https://api.keygen.sh/v1/accounts/demo/licenses/actions/validate-key",
method: .post,
headers: [
"Content-Type": "application/vnd.api+json",
"Accept": "application/vnd.api+json"
],
parameters: [
"meta": [
"key": "C1B6DE-39A6E3-DE1529-8559A0-4AF593-V3"
]
],
encoding: JSONEncoding.default
).responseJSON { response in
let json = JSON(data: response.data!)
let valid = json["meta"]["valid"].bool
 
if valid {
// Do something
} else {
// Do something else
}
}
using RestSharp;
using System;
using System.Collections.Generic;
 
var client = new RestClient("https://api.keygen.sh/v1/accounts/demo");
var request = new RestRequest(
"licenses/actions/validate-key",
Method.POST
);
 
request.AddHeader("Content-Type", "application/vnd.api+json");
request.AddHeader("Accept", "application/vnd.api+json");
request.AddJsonBody(new {
meta = new {
key = "C1B6DE-39A6E3-DE1529-8559A0-4AF593-V3"
}
});
 
var response = client.Execute<Dictionary<string, object>>(request);
var meta = (Dictionary<string, object>) response.Data["meta"];
 
if ((bool) meta["valid"])
{
// Do something
}
else
{
// Do something else
}
import com.mashape.unirest.http.exceptions.*
import com.mashape.unirest.http.*
import org.json.*
 
val body = JSONObject(mapOf(
"meta" to mapOf(
"key" to "C1B6DE-39A6E3-DE1529-8559A0-4AF593-V3"
)
))
 
val res = Unirest.post("https://api.keygen.sh/v1/accounts/demo/licenses/actions/validate-key")
.header("Content-Type", "application/vnd.api+json")
.header("Accept", "application/vnd.api+json")
.body(body)
.asJson()
 
val data = res.getBody().getObject()
val meta = data.getJSONObject("meta")
 
if (meta.getBoolean("valid")) {
// Do something
} else {
// Do something else
}
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("meta", ofEntries(
entry("key", "C1B6DE-39A6E3-DE1529-8559A0-4AF593-V3")
))
));
 
HttpResponse<JsonNode> res = Unirest.post("https://api.keygen.sh/v1/accounts/demo/licenses/actions/validate-key")
.header("Content-Type", "application/vnd.api+json")
.header("Accept", "application/vnd.api+json")
.body(body)
.asJson();
 
JSONObject data = res.getBody().getObject();
JSONObject meta = data.getJSONObject("meta");
 
if (meta.getBoolean("valid"))
{
// Do something
}
else
{
// Do something else
}
#[tokio::main]
async fn main() -> Result<(), reqwest::Error> {
let validation: serde_json::Value = reqwest::Client::new()
.post("https://api.keygen.sh/v1/accounts/demo/licenses/actions/validate-key")
.header("Content-Type", "application/vnd.api+json")
.header("Accept", "application/vnd.api+json")
.json(&serde_json::json!({
"meta": {
"key": "C1B6DE-39A6E3-DE1529-8559A0-4AF593-V3"
}
}))
.send()
.await?
.json()
.await?;
 
if validation["meta"]["valid"].as_bool().unwrap() {
// Do something
} else {
// Do something else
}
 
Ok(())
}
#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/demo");
http_request req;
 
value meta;
meta["key"] = value::string("C1B6DE-39A6E3-DE1529-8559A0-4AF593-V3");
 
value body;
body["meta"] = meta;
 
req.headers().add("Content-Type", "application/vnd.api+json");
req.headers().add("Accept", "application/json");
 
req.set_request_uri("/licenses/actions/validate-key");
req.set_method(methods::POST);
req.set_body(body.serialize());
 
client.request(req)
.then([](http_response res)
{
auto data = res.extract_json().get();
auto meta = data.at("meta");
 
if (meta.at("valid").as_bool())
{
// Do something
}
else
{
// Do something else
}
})
.wait();
import requests
import json
 
data = requests.post(
"https://api.keygen.sh/v1/accounts/demo/licenses/actions/validate-key",
headers={
"Content-Type": "application/vnd.api+json",
"Accept": "application/vnd.api+json"
},
data=json.dumps({
"meta": {
"key": "C1B6DE-39A6E3-DE1529-8559A0-4AF593-V3"
}
})
).json()
 
if data["meta"]["valid"]:
# Do something
else:
# Do something else
require 'httparty'
 
data = HTTParty.post(
'https://api.keygen.sh/v1/accounts/demo/licenses/actions/validate-key',
headers: {
'Content-Type' => 'application/vnd.api+json',
'Accept' => 'application/vnd.api+json'
},
body: {
meta: {
key: 'C1B6DE-39A6E3-DE1529-8559A0-4AF593-V3'
}
}
)
 
if data['meta']['valid']
# Do something
else
# Do something else
end
<?php
 
$url = 'https://api.keygen.sh/v1/accounts/demo/licenses/actions/validate-key';
$ch = curl_init($url);
 
$body = json_encode([
'meta' => [
'key' => 'C1B6DE-39A6E3-DE1529-8559A0-4AF593-V3'
]
]);
 
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: application/json',
'Accept: application/json'
]);
 
$json = curl_exec($ch);
$data = json_decode($json);
 
if ($data->meta->valid) {
// Do something
} else {
// Do something else
}

Resources for Licensing and Distribution


  • library_booksKeygen Quickstarts

    Guides and API references for developers of all skill levels.

    View Docs

  • insert_chartKeygen Dashboard

    Manage your products from an intuitive dashboard.

    View Dashboard

  • businessKeygen Pricing

    From indie to enterprise, we have plans for all business sizes.

    View Pricing