Skip to content

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.

Send a token as a bearer credential:

Terminal window
curl -H "Authorization: Bearer dyc_<id>_<secret>" \
https://app.dycrypt.com/o/acme/api/licenses

Mint 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.

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.

Whatever it was granted:

  • manage tokens — /api/tokens requires 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.
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
Terminal window
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.

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.

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.

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.

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.

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.

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:

Terminal window
npx @openapitools/openapi-generator-cli generate \
-i https://docs.dycrypt.com/openapi/dycrypt.yaml \
-g typescript-fetch -o ./dycrypt-client

The 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.

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.