Skip to main content

Keybox licensing SDK for Python

Project description

KeyBox SDK — Python

Official Python SDK for KeyBox. Add license activation, periodic online validation, and FastAPI request gating to any Python application in a single function call.


Requirements

  • Python ≥ 3.10
  • FastAPI (peer dependency, only required when using protect_fastapi_app / license_guard)
  • requests library

Installation

pip install keybox-sdk

Quick Start

from fastapi import FastAPI
from keybox_sdk import protect_fastapi_app
import os

app = FastAPI()

@app.get("/")
def root():
    return {"message": "Hello from a licensed app"}

protect_fastapi_app(
    app=app,
    product_name="MyApp",
    key=os.environ["KEYBOX_LICENSE_KEY"],
)

That's it. The SDK will:

  1. Activate the license on first run and persist the instance ID to .instance-id
  2. Register a FastAPI middleware that returns HTTP 402 on every request while the license is inactive
  3. Start a background daemon thread that revalidates every 15 minutes and automatically picks up renewals

How It Works

App starts
    │
    ├─ Is .instance-id on disk?
    │       Yes → POST /validate          → already active? skip activation
    │       No  → POST /validate/activate → write UUID to .instance-id
    │
    ├─ App starts (uvicorn / gunicorn)
    │
    └─ Daemon thread starts (every 15 min)
            │
            ├─ POST /validate
            ├─ Response ACTIVE   → allow all requests
            ├─ Response REVOKED / EXPIRED / PENDING → HTTP 402 on all requests
            ├─ Response 429 / 5xx → keep current state (transient)
            └─ Network failure
                    ├─ Within offline grace period → keep ACTIVE
                    └─ Grace exceeded             → HTTP 402

License States

State Meaning Requests
pending_validation Cold-start sentinel — server not yet contacted Blocked (402)
ACTIVE License confirmed valid Allowed
PENDING Key exists but was never activated Blocked (402)
EXPIRED License period ended Blocked (402)
REVOKED Explicitly disabled by the developer Blocked (402)

API Reference

protect_fastapi_app(options) (recommended entry point)

Registers the license guard, activates on first run, starts the validation daemon, and hooks into FastAPI's startup/shutdown lifecycle.

protect_fastapi_app(
    app=app,                            # FastAPI app instance (required)
    product_name="MyApp",               # Product identifier shown in KeyBox (required)
    key=os.environ["KEYBOX_LICENSE_KEY"],  # License key (required)
    bypass_paths=["/webhook"],          # list[str] — paths that skip the 402 guard
    on_revoke=lambda data: ...,         # called when license becomes inactive
    on_recover=lambda data: ...,        # called when license becomes active again
    interval_seconds=900,               # Daemon interval, default 900 (15 min)
    request_timeout=10,                 # Per-request network timeout in seconds, default 10
    offline_grace_seconds=None,         # How long to stay ACTIVE offline, default max(2×interval, 1800)
)

license_guard(bypass_paths=None) — FastAPI middleware

Returns a FastAPI middleware function that blocks requests when the license is not ACTIVE. Use this when you need the guard on specific routers rather than the whole app.

from keybox_sdk import license_guard

app.middleware("http")(license_guard(bypass_paths=["/api/health"]))

Paths in bypass_paths use prefix matching: "/admin" also bypasses "/admin/users".

The following paths are always bypassed regardless of state:

  • /health
  • /license/status

start_license_daemon(options) / stop_license_daemon()

Start and stop the background validation daemon manually (if you are not using protect_fastapi_app).

from keybox_sdk import start_license_daemon, stop_license_daemon, license_guard
import os

app.middleware("http")(license_guard())

start_license_daemon(
    product_name="MyApp",
    key=os.environ["KEYBOX_LICENSE_KEY"],
    on_revoke=lambda data: print("License revoked!"),
    on_recover=lambda data: print("License recovered!"),
)

# Later:
stop_license_daemon()

activate_license(options)

Activate a license key and persist the instance ID to .instance-id. Called automatically by protect_fastapi_app.

from keybox_sdk import activate_license
import os

activate_license(
    product_name="MyApp",
    key=os.environ["KEYBOX_LICENSE_KEY"],
)

Raises LicenseAlreadyActivatedError if the key is already bound to a different machine or instance.


check_license_status(options)

Read-only check — does not activate. Returns { "status", "active", "data" }.

from keybox_sdk import check_license_status
import os

result = check_license_status(
    product_name="MyApp",
    key=os.environ["KEYBOX_LICENSE_KEY"],
)

if not result["active"]:
    print("License is", result["status"])

get_license_state()

Returns the current in-memory license state string. Useful for rendering status in a dashboard route.

from keybox_sdk import get_license_state, LicenseState

@app.get("/license/status")
def license_status():
    return {"state": get_license_state()}

LicenseState

Class with string constants for all valid states:

from keybox_sdk import LicenseState

LicenseState.ACTIVE              # "ACTIVE"
LicenseState.PENDING             # "PENDING"
LicenseState.EXPIRED             # "EXPIRED"
LicenseState.REVOKED             # "REVOKED"
LicenseState.PENDING_VALIDATION  # "pending_validation"

LicenseAlreadyActivatedError

Raised by activate_license when the server responds with HTTP 403 indicating the key is already bound to a different machine or instance.

from keybox_sdk import activate_license, LicenseAlreadyActivatedError
import os

try:
    activate_license(product_name="MyApp", key=os.environ["KEYBOX_LICENSE_KEY"])
except LicenseAlreadyActivatedError:
    # Key is already in use elsewhere
    pass

Bypass Paths

Pass bypass_paths to skip license enforcement on specific routes.

protect_fastapi_app(
    app=app,
    product_name="MyApp",
    key=os.environ["KEYBOX_LICENSE_KEY"],
    bypass_paths=["/webhook", "/public"],
)

Prefix matching: "/public" also bypasses "/public/assets/logo.png".


Callbacks: on_revoke & on_recover

protect_fastapi_app(
    app=app,
    product_name="MyApp",
    key=os.environ["KEYBOX_LICENSE_KEY"],
    on_revoke=lambda data: print("License revoked — all requests now return 402"),
    on_recover=lambda data: print("License recovered — requests are flowing again"),
)

on_revoke fires when transitioning into any inactive state (REVOKED, EXPIRED, PENDING). on_recover fires when transitioning back to ACTIVE.

Both receive the raw server response dict (or None on network errors). Exceptions in callbacks are caught and logged without crashing the daemon.


Offline Grace Period

When the KeyBox server is unreachable after the license was previously confirmed ACTIVE, the SDK stays unlocked for a configurable grace period before switching to REVOKED.

Default: max(2 × interval_seconds, 1800) — at least 30 minutes.

protect_fastapi_app(
    app=app,
    product_name="MyApp",
    key=os.environ["KEYBOX_LICENSE_KEY"],
    offline_grace_seconds=3600,  # 1 hour
)

402 Response Shape

When a request is blocked the guard returns:

{
    "error": "LICENSE_INACTIVE",
    "state": "REVOKED",
    "message": "Go pay your developer"
}

state will be one of PENDING, EXPIRED, REVOKED, or pending_validation.


.instance-id File

On first successful activation the SDK writes a UUID to .instance-id in the current working directory. This file binds the installation to its license slot on the KeyBox server.

  • Do not commit it to version control — add .instance-id to .gitignore
  • Do not delete it between restarts — deleting it forces a re-activation attempt
  • Each deployment/machine should have its own file

What the SDK Does NOT Do

  • Generate or issue license keys (done in the KeyBox dashboard)
  • Renew or extend licenses
  • Work with non-FastAPI Python servers (Django, Flask, etc.) out of the box
  • Provide tamper-proof offline enforcement — Python bytecode can always be patched

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

keybox_sdk-0.1.2.tar.gz (12.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

keybox_sdk-0.1.2-py3-none-any.whl (10.0 kB view details)

Uploaded Python 3

File details

Details for the file keybox_sdk-0.1.2.tar.gz.

File metadata

  • Download URL: keybox_sdk-0.1.2.tar.gz
  • Upload date:
  • Size: 12.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.3

File hashes

Hashes for keybox_sdk-0.1.2.tar.gz
Algorithm Hash digest
SHA256 2f8b763ade79435fdeee7372c7cc773501f368ed8386e9444f7be3cd46bcab8d
MD5 f65190adb586104d8a1ce77121e6936b
BLAKE2b-256 f09313890cf9a204594210c1936115f3b81e83b9896571f39f787db92f1351c7

See more details on using hashes here.

File details

Details for the file keybox_sdk-0.1.2-py3-none-any.whl.

File metadata

  • Download URL: keybox_sdk-0.1.2-py3-none-any.whl
  • Upload date:
  • Size: 10.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.3

File hashes

Hashes for keybox_sdk-0.1.2-py3-none-any.whl
Algorithm Hash digest
SHA256 13ded8766838dbb092a160f9591860677a1d80b57f3b1dc36f73d1239214a9ba
MD5 2e09aaa30c3ce31d4ef1143eb2de6af1
BLAKE2b-256 335c86d5d49b8001eb46b9fceeadddf4c0207272777e5a776f02af1be21b0eb5

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page