Skip to content

Issue a licence

POST
/api/licenses
curl --request POST \
--url https://app.dycrypt.com/o/acme/api/licenses \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '{ "productId": 1, "customer": { "name": "Globex Ltd", "email": "ops@globex.example" }, "validityMonths": 12, "graceDays": 14, "licenceType": "HYBRID", "params": { "maxUsers": 250, "edition": "enterprise", "features": [ "sso", "audit" ] } }'

Signs a new licence with the product’s key and returns it in full, including licenseString — the artifact to deliver.

Refused with 403 once the organisation is at its plan’s active-licence limit. Treat that as “upgrade needed”, not as a broken credential.

Media typeapplication/json
object
productId
required

The product whose key signs this licence and whose parameters it must satisfy.

integer format: int64
customerId

An existing customer…

integer | null format: int64
customer

…or one created inline. Resolved find-or-create by email.

object
name
string
email
string format: email
licensee

Legacy free-text pair with licenseeEmail. Resolves through the same find-or-create path.

string
licenseeEmail
string
issueDate

The date the licence is dated. Absent = today.

string format: date
startsOn

The older name for issueDate. Still accepted.

string format: date
validityMonths

0 = perpetual. Absent = the product’s default.

integer
graceDays

Absent = the product’s default.

integer
licenceType

Absent = the product’s default. Must be one the product allows.

string
Allowed values: OFFLINE ONLINE HYBRID
activation

Only meaningful for HYBRID.

string
Allowed values: ONLINE OFFLINE
fingerprint

Present when the licence binds at issue rather than on first check.

string
fingerprints

Several machines, for a licence bound to more than one at issue. The fp claim becomes an array and the schema version rises to 4, so a verifier that predates multi-machine binding refuses it rather than reading an array it does not understand as “binds nothing”.

fingerprint is the one-machine spelling and still works; both may be sent and are combined.

Array<string>
maxMachines

How many machines this licence binds at once. Absent = the product’s figure. Never fewer than the fingerprints signed in.

integer
>= 1
params

Values for the product’s declared parameters, keyed by parameter key. Each becomes a claim.

object
notes
string
Examples

A twelve-month Hybrid licence for a new customer

{
"productId": 1,
"customer": {
"name": "Globex Ltd",
"email": "ops@globex.example"
},
"validityMonths": 12,
"graceDays": 14,
"licenceType": "HYBRID",
"params": {
"maxUsers": 250,
"edition": "enterprise",
"features": [
"sso",
"audit"
]
}
}

The signed licence.

Media typeapplication/json
object
lid
required
string format: uuid
customerId
integer | null format: int64
licensee
required

The customer’s name

string
licenseeEmail
string | null
productId
required
integer format: int64
productName
string | null
productKey

Signed into the licence as prd.

string | null
issuedAt
required
string format: date-time
startsAt

The nbf claim. Absent means it is valid immediately.

string | null format: date-time
expiresAt

Null for a perpetual licence — the exp claim is omitted entirely.

string | null format: date-time
graceDays
required
integer
licenceType
required

See the licence-mode guides — Offline verifies alone, Online must check in, Hybrid does both.

string
Allowed values: OFFLINE ONLINE HYBRID
activation

How a Hybrid licence first binds to a machine. Only meaningful for HYBRID.

string
Allowed values: ONLINE OFFLINE
activated
boolean
boundAt

When it bound to a machine. Null means nobody has installed it yet.

string | null format: date-time
rebindCount

How many times an operator released the binding.

integer
maxMachines

How many machines this licence binds at once. Copied from the product at issue.

integer
seatsUsed

How many of them are bound right now.

integer
status
required

Where the licence is against the calendar and the revocation flag. PENDING, EXPIRED and REVOKED do not count against plan capacity.

string
Allowed values: PENDING ACTIVE GRACE EXPIRED REVOKED
checkHealth

What the field says, beside what the calendar says. NOT_APPLICABLE for every Offline licence, which is most of them in a typical organisation.

string
Allowed values: NOT_APPLICABLE NEVER OK DUE OVERDUE
checkMaxSeconds

The signed offline window.

integer | null
checkEverySeconds

How often the application should check in.

integer | null
revoked
required
boolean
revokedReason
string | null
archived

Filed away — off the default list and out of renewal. Still verifies in the field.

boolean
archivedAt
string | null format: date-time
issuedBy

The person or token that issued it.

string
lastCheckedAt
string | null format: date-time
renewedAt
string | null format: date-time
claims

The signed payload, decoded. Issuer claims first, then the product’s own parameters.

object
licenseString

The artifact to deliver: payloadB64url.signatureB64url. This is what a customer’s application verifies, and its own signature is its proof.

string
notes

Free text. Never signed into the licence.

string | null
Example
{
"licenceType": "OFFLINE",
"activation": "ONLINE",
"status": "PENDING",
"checkHealth": "NOT_APPLICABLE"
}

The request is wrong — a bad date, a missing field, a value the product does not declare.

Media typeapplication/json

Every failure in this API has this shape and no other.

object
error
required
string
Example
{
"error": "this token does not have licenses:write"
}

No credential, or one that no longer resolves — revoked, expired, deleted. Replace it.

Media typeapplication/json

Every failure in this API has this shape and no other.

object
error
required
string
Example
{
"error": "this token does not have licenses:write"
}

Missing licenses:write, a read-only role, or the plan’s active-licence limit reached.

Media typeapplication/json

Every failure in this API has this shape and no other.

object
error
required
string
Example
{
"error": "this token does not have licenses:write"
}