Management API
Everything the console does inside your organisation, a script can do. The Management API is the same surface the console itself calls: issue a licence when an order is paid, renew from a subscription webhook, revoke on a chargeback, export the licence list into a CRM.
Every path is scoped to one organisation and prefixed with its slug:
https://app.dycrypt.com/o/{slug}/api/…A request that reaches no organisation reaches no organisation data. The slug in the URL is what selects the tenant, and a token issued by one organisation is refused under another’s slug.
Authentication
Section titled “Authentication”Send a token as a bearer credential:
curl -H "Authorization: Bearer dyc_<id>_<secret>" \ https://app.dycrypt.com/o/acme/api/licensesMint one at Settings → API tokens. Creating, revoking, and deleting tokens requires a signed-in organisation administrator and is closed to tokens themselves — a token that could mint tokens could grant itself every scope, which would make the whole model advisory.
The secret is shown once, at creation, and nothing stores it. A lost token is replaced, not recovered. Only its SHA-256 is kept: the secret is 256 bits of CSPRNG output, so there is no guessable structure a slow hash would defend against.
Revoking takes effect on the next request. Expiry is optional. lastUsedAt is stamped at most once a minute, which is enough to spot an integration that has stopped calling without turning every read into a write.
Scopes
Section titled “Scopes”A token carries capabilities, not a role. A scope is <resource>:<read|write>, and write implies read — a token that may issue a licence may also read it back.
| Capability | :read |
:write |
|---|---|---|
licenses |
list, read, download .lic, verify |
issue, renew, revoke, reinstate, release binding, delete |
products |
list, read, public key, certificate, form options | create, edit, rotate keys, rotate check token, delete |
customers |
list, read | create, edit, delete |
users |
list | create, edit, delete — can create an organisation administrator |
analytics |
the overview: counts, statuses, expiries | — derived data, no write half |
activity |
the audit trail | — written by the platform, never by a caller |
Grant only what the integration needs. A billing webhook that issues and renews wants licenses:write and customers:write, and nothing else.
Scopes are validated when the token is created. A scope this deployment does not define is refused outright rather than stored and quietly ignored, because a token claiming a permission nobody enforces is a lie waiting to be believed.
What a token cannot do
Section titled “What a token cannot do”Whatever it was granted:
- manage tokens —
/api/tokensrequires a human organisation administrator; - cross an organisation — it is a row in one tenant and carries that organisation with it;
- reach the control plane;
- change a password or register a passkey — it has neither.
Licences
Section titled “Licences”GET /licenses?productId=&customerId= |
the whole list, or one product’s or customer’s |
GET /licenses/page?q=&status=&window=&sort=&asc=&limit=&offset= |
one page, filtered and sorted in the database, with the matching total |
GET /licenses/windows |
how many licences fall in each expiry window |
GET /licenses/export.csv?… |
the current filter as a spreadsheet; refuses above 25,000 rows rather than truncating |
POST /licenses |
issue |
GET /licenses/{lid} |
detail, including claims and the signed licenseString |
GET /licenses/{lid}/download |
the .lic file — the wire-format string, nothing else |
POST /licenses/{lid}/renew |
a new signed term under the same lid |
POST /licenses/{lid}/revoke |
optional { "reason": "…" } |
POST /licenses/{lid}/reinstate |
undo a revocation |
POST /licenses/{lid}/release-binding |
let a customer move to replacement hardware |
GET /licenses/{lid}/versions · /{versionNo} · /{versionNo}/download |
superseded terms, newest first |
GET /licenses/{lid}/activity |
this licence’s own audit trail |
POST /licenses/verify |
{ "license": "…" } → what the customer’s application would see |
DELETE /licenses/{lid} |
remove the record |
curl -X POST https://app.dycrypt.com/o/acme/api/licenses \ -H "Authorization: Bearer dyc_<id>_<secret>" \ -H 'Content-Type: application/json' \ -d '{ "productId": 1, "customer": { "name": "Globex Ltd", "email": "ops@globex.example" }, "validityMonths": 12, "graceDays": 14, "licenceType": "HYBRID", "params": { "maxUsers": 250, "edition": "enterprise", "features": ["sso", "audit"] } }'| Field | |
|---|---|
productId |
required — the product whose key signs this licence and whose parameters it must satisfy |
customerId |
an existing customer… |
customer |
…or { name, email } to find or create one inline |
issueDate |
ISO date the licence is dated. Absent = today |
validityMonths |
0 = perpetual; absent = the product’s default |
graceDays |
absent = the product’s default |
licenceType |
OFFLINE, ONLINE, or HYBRID. Absent = the product’s default, and it must be one the product allows |
activation |
ONLINE or OFFLINE — only meaningful for HYBRID |
fingerprint |
the customer’s DYC1… blob, when the licence binds at issue rather than on first check |
params |
values for the product’s declared parameters, keyed by parameter key |
notes |
free text, never signed into the licence |
The response is the full licence detail, including licenseString — the signed artifact to deliver. Deliver that string; it is what your application verifies.
POST /licenses/{lid}/renew accepts issueDate, validityMonths, graceDays, and params. Omit the body entirely to renew on the product’s defaults.
Renewal keeps the lid and signs a new string. The old one does not keep working — an installation still holding it needs the new file, either through your own delivery path or through self-service fetch if the product has that turned on.
Products
Section titled “Products”GET /products · /{id} |
list · detail |
GET /products/options |
the algorithms and certificate types this deployment offers, each marked available or not |
POST /products · PUT /products/{id} · DELETE /products/{id} |
manage products |
GET /products/{id}/public-key · /certificate |
PEM downloads for your application |
POST /products/{id}/check-token |
rotate the product’s check token |
A product carries the signing key and the licence shape: its declared parameters, its default validity and grace, which licence types it allows, the check interval and offline window for Online and Hybrid, and how many fingerprint components must match.
Private key material is write-only. privateKeyPem, keystore passwords, and a PKCS#11 PIN are accepted on the way in, sealed, and never appear in any response.
Send "regenerateKey": true on a PUT to rotate a managed product’s key pair.
Customers
Section titled “Customers”GET /customers · /{id} |
list · detail |
POST /customers · PUT /customers/{id} |
{ name, email, notes } |
DELETE /customers/{id} |
refuses while the customer still holds licences |
Customers are who a licence is issued to. They are not users of your DYCRYPT organisation, and they have no sign-in.
People
Section titled “People”GET /users |
members of the organisation and their roles |
POST /users · PUT /users/{id} · DELETE /users/{id} |
manage them |
Requires users:write. Treat it as the most sensitive scope you can grant: it can create an organisation administrator, and an administrator can do everything else.
Overview and activity
Section titled “Overview and activity”GET /dashboard |
counts, status breakdown, and what is expiring (analytics:read) |
GET /dashboard/metrics |
the same figures without the surrounding page data (analytics:read) |
GET /dashboard/activity |
the organisation’s audit trail (activity:read) |
The trail is append-only and records operator actions — issuance, renewal, revocation, machine-binding changes, key rotation — with the actor that performed each one. A token appears there under its own name, so an entry written by automation never reads as though a person made it.
Errors
Section titled “Errors”Every failure has one shape:
{ "error": "this token does not have licenses:write" }| Status | Means |
|---|---|
400 |
the request is wrong — a bad date, a missing parameter, a value the product does not declare |
401 |
no credential, or one that no longer resolves: revoked, expired, deleted |
403 |
authenticated, but not allowed — a missing scope, an endpoint closed to tokens, a plan limit reached, or a token used under another organisation’s slug |
404 |
no such licence, product, customer, or endpoint |
401 and 403 are worth telling apart in an integration: 401 means replace the credential, 403 means it is the right credential doing something it may not do.
The machine-readable specification
Section titled “The machine-readable specification”Everything on this page and the licence check API is also published as an OpenAPI 3.1 document, rendered endpoint by endpoint under Endpoint reference with a request sample per operation.
Point a generator at it to get a typed client:
npx @openapitools/openapi-generator-cli generate \ -i https://docs.dycrypt.com/openapi/dycrypt.yaml \ -g typescript-fetch -o ./dycrypt-clientThe organisation is a server variable, not part of the path, so a generated client takes your slug once at construction and every operation reads the way you call it.
The document is written by hand and kept honest by two tests that fail DYCRYPT’s build if an endpoint exists and the specification does not describe it. What those tests cannot see is a changed field, so treat schema properties as accurate-at-the-release rather than mechanically guaranteed.
Sessions instead of tokens
Section titled “Sessions instead of tokens”Every path here also accepts the console’s session cookie, which is how the console itself calls them. A person is measured against their role — viewer reads, issuer writes, organisation administrator also manages people — and a token against its scopes. Neither model borrows permission from the other, so a token cannot inherit the role of whoever created it.
For automation, use a token. A session belongs to a person, ends when they sign out, and cannot be scoped down.