1. Signing in
Admins sign in with their email, by a six-digit code mailed to them, as your users do. There are no passwords.
- On the web: licensebox.dev/admin. Everything below can be done there.
- From the command line, with the
licenseboxtool, below.
The command line
Download it for your system; it is a single file with nothing to install:
The files are built from github.com/licensebox/go for each release, with their SHA-256 sums in SHA256SUMS. On Linux and macOS, make it runnable and put it on your path; macOS asks once before running a downloaded program:
chmod +x licensebox-* && sudo mv licensebox-* /usr/local/bin/licensebox
licensebox login you@example.org # asks for the code mailed to you
licensebox help
With Go installed, you can build it instead: go install licensebox.dev/cmd/licensebox@latest.
A sign-in lasts 30 days. The tool keeps it in licensebox/admin-token in your configuration folder (~/.config on Linux); licensebox logout ends it. Every command takes -json to print the server's answer, for scripts.
2. Create a product
On the web, fill in New product on your products page. From the command line:
licensebox create myapp -name "My App" \
-modules core,export,batch -free core \
-trial export -trial-days 14 \
-terms-url https://example.org/terms -contact sales@example.org
licensebox makes the product's signing key, and you get what to build into your app:
client.Config{
Product: "myapp",
Keys: lease.Keys{
"myapp-k1": lease.MustKey("K3T9LqUUCrye7JPtsqVhn5sdT8HixGQxYjOnfVLl_m8"), // current
},
}
id | Lower-case letters, digits and -. It can't be changed later. |
-modules | Every module of the product: the names your app passes to Require. |
-free | Modules everyone who signs in may run. |
-trial, -trial-days | Modules each user gets once, for this many days from their first sign-in. |
-lease-hours | How long the app runs without reaching licensebox: 24 by default. Online, it checks at every run. |
-terms-url, -terms | Your terms, linked on the sign-in page, and their version, recorded with each user when they accept them. |
-contact | Where users ask for modules they don't have. |
Then follow the developer guide to check the licence in your app.
3. Change a product
On the web, under Settings on the product's page. From the command line, give only what changes; the rest is kept:
licensebox product myapp -modules core,export,batch,cloud # add a module
licensebox product myapp -trial-days 30 # a longer trial for new users
licensebox product myapp # settings, admins and keys
licensebox products # all your products
Changes reach each user's app at its next run. A longer trial applies to users who sign in from now on; one already running is not extended.
4. More admins
Whoever creates a product is its admin. Add colleagues, who then sign in with their own email:
licensebox admin add myapp colleague@example.org
licensebox admin remove myapp colleague@example.org
Every admin can do everything on the product, including adding and removing admins; a product always keeps at least one. Each admin sees only the products they manage.
5. See your users
licensebox users myapp # email, since when, what they use it for, modules
licensebox users myapp -q research # matching an email or their stated purpose
licensebox user myapp ada@example.org # one user: their grants and every machine they use
A user's page lists each machine with its key, system, app version and last check. The key is what you pass to revoke one machine. Users are kept per product: what someone does in another product on licensebox is not shown to you, and does not affect yours.
6. Grant modules
Grant a module to give someone access. Admins decide who gets it and for how long; a grant can be permanent, end on a date, or last for a number of days. licensebox does not process payments or manage billing; any such arrangements are handled separately by your team:
licensebox grant myapp ada@example.org export batch # for good
licensebox grant myapp ada@example.org export -until 2027-01-31 -note "approved request"
licensebox grant myapp ada@example.org batch -days 30 # a month
licensebox ungrant myapp ada@example.org batch # take it back
- You can grant to someone who hasn't signed in yet: they have the modules at their first sign-in.
- The app picks up a new grant at its next run, so a user whose access was just approved only needs to run the app again.
- A grant that ends, or is taken back, drops out at the next run. A lease never outlasts the earliest timed grant in it, so a module also goes away offline when its grant ends.
Answer requests
The product page lists waiting requests with the user's note. Tick modules to grant and choose an optional end date, or decline with a note. Both outcomes send the user an email. Modules already granted arrive at their next run.
licensebox product myapp -describe "export: STEP and IGES export" -hidden internal -usage on
licensebox requests myapp
licensebox approve myapp REQUEST export -days 30 -note "Welcome"
licensebox decline myapp REQUEST -note "Please tell us more"
licensebox requests myapp -all
licensebox usage myapp -days 30
Hidden modules are not offered for requests to users who do not hold them. Usage is off by default: enable it only with appropriate disclosure in your app. The Usage card shows command counts, failures, mean times, users and versions; command names, outcomes, times, version and operating system are reported, without file names, arguments or file contents.
Machine seats and permitted locations
Set these in product settings. Products with no explicit machine policy migrate to one seat. Set zero for unrestricted installations. New products created in the web console or CLI start with one seat per user. A new sign-in at capacity transfers the oldest allocation; revocation remains a separate administrator action.
licensebox product myapp -machine-limit 1
licensebox product myapp -allow-areas 'AT;DE | Berlin' -block-areas 'AT | Vienna'
licensebox product myapp -allow-areas '48.2082,16.3738 | 80' -block-areas ''
licensebox product myapp -machine-limit 0 -allow-areas '' -block-areas ''
Areas are country codes, a city within its country, or coordinates with a radius in kilometres. Allow lists are combined; blocked areas always win. Blank allow lists allow everywhere except blocked areas. Location is approximate, based on the app's IP connection. Missing information needed to decide a rule refuses access. No client-reported location is trusted.
The old machine stops at its next server check; while offline, it may keep using its existing lease until expiry. Moving to a different location while offline has the same limit. Shorten offline hours when faster enforcement matters. Applications must use an SDK that handles policy refusals.
Optional heartbeat timing
For applications that explicitly check during long-running work, enable the heartbeat policy in product settings. A 300-second interval and 900-second grace period asks opted-in monitors to check every five minutes and caps each new signed lease at fifteen minutes without contact. Existing offline and grant limits can shorten it further.
licensebox product myapp -heartbeat-interval 300 -heartbeat-grace 900
licensebox product myapp -heartbeat-interval 0
The policy is off by default. Intervals must be 30–86,400 seconds; grace must be at least the interval and no more than 365 days. An older client still enforces signed expiry. Your application must explicitly start a monitor or call heartbeat from its own event loop; the server setting cannot start one. Applications own safe cancellation and checkpointing.
7. Revoke a user or a machine
licensebox revoke myapp ada@example.org -note "access ended" # the user, on every machine
licensebox revoke myapp -machine 8vx3P--sPvGa9zrR… # one machine, as for a lost laptop
licensebox restore myapp ada@example.org # undo it
A revoked user can't sign in to the product again, and their apps' checks are refused: the client deletes the lease and returns ErrRevoked. Revoking one machine does the same for that installation only.
A revocation takes effect at the app's next run. An app that is offline keeps running until its lease ends: at most the product's offline hours, 24 by default.
8. Activity and numbers
licensebox events myapp -n 100 # sign-ins, trials, grants, revocations, refused checks, admins' changes
licensebox stats myapp # users, revoked users, machines, those active in 30 days, by app version
Every sign-in, trial, grant, revocation, refused check and change to the product is recorded, with which admin made it. Ordinary checks are not, since there is one at every run; each machine's last check shows on the user's page.
9. Signing keys
Each product signs its leases with its own Ed25519 key. licensebox makes the keys and keeps their private halves encrypted; no one, admins included, ever sees them. Your app carries the public halves.
To change keys, as on a schedule or if you think a build of your app was tampered with:
licensebox key add myappmakes a new key, the current one from now: new leases are signed with it.- Ship app versions that carry the new public key, with the old.
- Once users have updated,
licensebox key retire myapp myapp-k1. Apps still holding a lease signed with a retired key sign in again.
An app version that lacks the current key can't accept new leases: it keeps working on its old lease until that runs out, then asks the user to connect. So ship the new key to users soon after adding it.
10. Automating it
Everything above is available through an HTTP API, so your own tools can grant or revoke access after an admin approves it. Any payment or billing workflow stays with your team. In Go, use the same package the tool uses:
import "licensebox.dev/admin"
a := admin.New(token) // from licensebox login
_, err := a.Grant(ctx, "myapp", admin.GrantRequest{Email: "ada@example.org", Modules: []string{"export"}, Note: "approved request"})
Otherwise, send the token as Authorization: Bearer <token> to https://licensebox.dev/v1/admin/…:
POST login, POST login/finish | {"email"} mails a code; {"email", "code"} returns {"token", "expires"}. |
GET products, POST products | Your products; create one: {"id", "name", "modules", "free", "trial_modules", "trial_days", "lease_hours", "heartbeat", "terms", "terms_url", "contact"}. |
GET, PUT products/{id} | A product; replace its settings (the same fields, without id). |
GET, POST products/{id}/keys, DELETE …/keys/{kid} | Its keys; add one; retire one. |
POST products/{id}/admins, DELETE …/admins/{email} | Add or remove an admin. |
GET products/{id}/users?q=, GET …/users/{email} | Its users; one user with machines and grants. |
POST products/{id}/grants, …/grants/remove | {"email", "modules": [], "until": "2027-02-01T00:00:00Z", "note"}; leave out until for good. |
POST products/{id}/revoke, …/restore | {"email"} or {"machine"}, with an optional "note". |
GET products/{id}/requests?status=pending | Requests, newest first. Omit status for all. |
POST products/{id}/requests/{request}/approve, …/decline | {"modules":[],"until":"…","note":"…"}; approving without modules grants all requested modules. |
GET products/{id}/usage?days=30 | Usage by day, command, user, version and operating system. |
GET products/{id}/events?limit=, …/stats | Activity, newest first; the numbers. |
Errors come back as {"error": "…", "error_description": "…"} with a 4xx status. A product you don't manage answers 404, as one that doesn't exist.