Manually grant an add-on to a system. Only the owner organization or a
Super Admin can manage grants directly (403 otherwise); the shop goes
through POST /entitlements/activate instead.
The entitlement must exist in the catalog; scope is only accepted
for scoped catalog items, and a catalog item restricted to a
system_type cannot be granted to a system of a different known type.
Body
Required
-
Catalog id of the add-on to grant
-
Application instance to narrow the grant to (only for scoped catalog items)
-
Override the start date. Omitted = now. Used by the legacy import to preserve the original order date.
-
Expiry of the grant. Null/omitted = perpetual.
-
How the grant was created
Values are
manual,shop, orlegacy-import. Default value ismanual. -
Free-form reference to the originating record
-
Customer email of the originating shop order (legacy-import backfill): resolved to a my user for the
purchased_bysnapshot, kept raw when no user matches. -
Shop variation (tier) of the purchased product line. Display metadata only: the add-on↔product mapping stays on the parent product and /auth enforcement ignores it. Refreshed by activate, so tier upgrades/downgrades follow the renewals. Omitted for manual grants and simple (non-variable) products.
Responses
-
Entitlement created successfully
-
Unknown entitlement, scope not supported by the catalog item, invalid source, or system-type mismatch ("this entitlement applies to nsec systems only")
-
Unauthorized - invalid or missing token
-
Forbidden - insufficient permissions
-
Resource not found
-
Grant already exists for this (system, entitlement, scope)
-
Internal server error
curl \
--request POST 'https://api.your-domain.com/api/systems/sys_123456789/entitlements' \
--header "Authorization: Bearer $ACCESS_TOKEN" \
--header "Content-Type: application/json" \
--data '{"entitlement":"nsec-blacklist","scope":"nethvoice1","valid_from":"2026-02-01T00:00:00Z","valid_until":"2027-07-01T10:00:00Z","source":"manual","source_ref":"sub_12345","buyer_email":"mario.rossi@example.com","variant":{"id":245,"sku":"ATS-15","label":"1-15 device"}}'
{
"entitlement": "nsec-blacklist",
"scope": "nethvoice1",
"valid_from": "2026-02-01T00:00:00Z",
"valid_until": "2027-07-01T10:00:00Z",
"source": "manual",
"source_ref": "sub_12345",
"buyer_email": "mario.rossi@example.com",
"variant": {
"id": 245,
"sku": "ATS-15",
"label": "1-15 device"
}
}
{
"code": 201,
"message": "entitlement created successfully",
"data": {
"id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"system_id": "550e8400-e29b-41d4-a716-446655440000",
"entitlement": "nsec-blacklist",
"scope": "nethvoice1",
"source": "manual",
"source_ref": "sub_12345",
"valid_from": "2026-07-01T10:00:00Z",
"valid_until": "2027-07-01T10:00:00Z",
"revoked_at": "2026-05-04T09:42:00Z",
"revoked_source": "shop",
"active": true,
"pending_ref": "wc-order-83164",
"pending_since": "2026-07-15T18:00:00Z",
"status": "active",
"created_by": {
"user_id": "53h5zxpwu4vc",
"user_name": "Edoardo Super",
"organization_id": "lbswt1rxdhbz",
"organization_name": "Nethesis Italia",
"channel": "shop"
},
"purchased_by": {
"logto_id": "53h5zxpwu4vc",
"name": "Mario Rossi",
"email": "mario.rossi@example.com",
"organization_id": "akkbs6x2wo82",
"organization_name": "ACME S.r.l.",
"org_role": "reseller",
"user_roles": [
"Admin"
],
"out_of_scope": true
},
"variant": {
"id": 245,
"sku": "ATS-15",
"label": "1-15 device"
},
"renewal_count": 3,
"created_at": "2026-07-01T10:00:00Z",
"updated_at": "2026-07-01T10:00:00Z"
}
}
{
"code": 400,
"message": "validation failed",
"data": {
"type": "validation_error",
"errors": [
{
"key": "username",
"message": "required",
"value": "string"
}
]
}
}
{
"code": 401,
"message": "invalid token",
"data": {}
}
{
"code": 403,
"message": "insufficient permissions",
"data": {}
}
{
"code": 400,
"message": "validation failed",
"data": {
"type": "validation_error",
"errors": [
{
"key": "username",
"message": "required",
"value": "string"
}
]
}
}
{
"code": 409,
"message": "entitlement already exists for this system",
"data": {}
}
{
"code": 500,
"message": "internal server error",
"data": {}
}