Entitlement Operations walkthrough
This walkthrough sells a support-hours package: customers buy a plan that covers a support service and includes ten consumable hours. It goes from an empty workspace to a granted, partially consumed entitlement with a readable ledger — configuration through the Configuration API, grant and consumption through the public runtime API.
All examples were executed against a real Moltaro installation; response bodies are real, trimmed for length. Concepts are in the Entitlement Operations user documentation; the complete contract is in the Configuration API reference and the API reference.
The workspace owner supplies the guide, URL, key, module setup, and required permissions through the Portal or administrative handoff. An agent does not provision or elevate itself. Apply the durable idempotency, polling, and ledger-verification rules in Reliable API automation.
Calling Entitlement Operations from workspace C# automation
Section titled “Calling Entitlement Operations from workspace C# automation”For an Action, TriggerHandler, Command, Job, or HttpEndpoint in the Net
Operation Project, prefer IEntitlementAutomationCommandService and
IEntitlementAutomationQueryService from
Moltaro.Package.NET.EntitlementOperations.Automation. They wrap the package
domain services with the complete application flow for grant, lifecycle,
quantity ledger, access/history, and renewal operations. Request DTOs contain
a stable IdempotencyKey but no caller-controlled ActorUserId or
correlation field.
Writes persist as moltaro-system-automation; the original user, function
run, function/version, and correlation remain in origin, audit, resource
event, and ledger metadata. Each facade call owns a separate application
scope/transaction. A successful return means its main transaction committed;
reuse the same idempotency key on retry, use RowVersion for concurrency, and
poll any returned function job id at
GET /api/workspace/functions/jobs/{jobId}. Validation and
BeforeSaveMutation phases return
moltaro.automation.executionPhase.unsupported.
The older IEntitlementRuntime* and IEntitlementRenewal* services remain
advanced TrustedDirectDb APIs and do not promise the application side
effects above. See the exact
C# Entitlement recipes
and the installation’s developer-surface response.
How the pieces fit
Section titled “How the pieces fit”- An entitlement model declares who owns entitlements (the party, a record type) and what they get access to (the resource, another record type). Models are typed — software license, membership, information service access, warranty — and the type decides which terms a plan may carry. Consumable quantities are available on information service access and warranty models.
- A plan is a sellable package inside a model: covered resources, duration, and quantity terms.
- An entitlement is one granted plan for one owner. Quantity operations (consume, reverse, adjust) are ledgered.
Prerequisites
Section titled “Prerequisites”Two record types with one record each — the party and the resource. Create them exactly like in the Configuration quickstart; this walkthrough uses:
Customer(definitionvGMraALVTZk5) with the record Acme GmbH (0f46096527cd4a24ae26087c4040919c);SupportService(definitionSR6dzMPbIx0h) with the record Priority support (3188ece4de5d428db3666adc57ef6990).
The admin endpoints below need the Admin or Configurator role; plan and entitlement runtime operations are also open to subjects with the model-scoped Manage plans / Manage entitlements permissions.
Step 1 — Enable the module
Section titled “Step 1 — Enable the module”curl -s -X POST https://ops.example.com/api/workspace/admin/entitlement-operations/enable \ -H "Authorization: Bearer <token>" -H "Content-Type: application/json" -d '{}'(operation;
GET /api/workspace/admin/entitlement-operations/setup reports module state
and existing models at any time.)
Step 2 — Create the entitlement model
Section titled “Step 2 — Create the entitlement model”POST /api/workspace/admin/entitlement-operations/models/information-service-access
(operation)
binds the party and resource roles to the two record types:
curl -s -X POST https://ops.example.com/api/workspace/admin/entitlement-operations/models/information-service-access \ -H "Authorization: Bearer <token>" -H "Content-Type: application/json" \ -d '{ "DisplayName": "Support access", "PartyType": 0, "AccessMode": 0, "ResourceScopeMode": 0, "OwnerParty": { "DisplayName": "Customer", "EntityDefinitionId": "vGMraALVTZk5" }, "Resource": { "DisplayName": "Support service", "EntityDefinitionId": "SR6dzMPbIx0h" } }'{ "Data": { "Id": "8Ip2QXlDaNLk", "Key": "supportAccess", "DisplayName": "Support access", "PartyType": 0, "AccessMode": 0, "ResourceScopeMode": 0, "OwnerParty": { "Id": "tocEHOfa9qw8", "DisplayName": "Customer", "EntityDefinitionId": "vGMraALVTZk5", "EntityDisplayName": "Customers" }, "Resource": { "Id": "CNC7hR6QHXkB", "DisplayName": "Support service", "EntityDefinitionId": "SR6dzMPbIx0h", "EntityDisplayName": "Support services" }, "Plans": [], "IsActive": true, "RowVersion": "7a9cf98a-5962-4a23-9e80-281b5e7cc94f" }, "Success": true}PartyType: 0 is an individual owner party, AccessMode: 0 is direct
(non-delegated) access, and ResourceScopeMode: 0 covers exactly the
resources a plan lists. Before writing, POST .../setup-plan validates a
model shape without saving, and
GET .../setup/relationship-paths discovers delegation and hierarchy paths
for the delegated shapes — both useful for agents assembling a model from
workspace facts.
Step 3 — Create a plan with a consumable quantity
Section titled “Step 3 — Create a plan with a consumable quantity”POST /api/workspace/entitlement-operations/models/information-service-access/{modelId}/plans
(operation)
creates the sellable package. Resources lists the concrete covered records;
ConsumableQuantity adds a one-time quantity pool (use TimeBoundQuantity
plus window terms for a per-period quota instead):
curl -s -X POST https://ops.example.com/api/workspace/entitlement-operations/models/information-service-access/8Ip2QXlDaNLk/plans \ -H "Authorization: Bearer <token>" -H "Content-Type: application/json" \ -d '{ "DisplayName": "Support 10-hour pack", "IsActive": true, "DurationValue": 12, "DurationUnit": 1, "ConsumableQuantity": 10, "Resources": [ { "EntityDefinitionId": "SR6dzMPbIx0h", "ResourceEntityInstanceId": "3188ece4de5d428db3666adc57ef6990", "SortOrder": 1 } ] }'{ "Data": { "Id": "5M05saIptqZI", "Key": "support-10-hour-pack", "DisplayName": "Support 10-hour pack", "EntitlementModelId": "8Ip2QXlDaNLk", "IsActive": true, "DurationValue": 12, "DurationUnit": 1, "ConsumableQuantity": 10.0000, "Resources": [ { "Id": "f7sHOYnuJjsN", "EntityDefinitionId": "SR6dzMPbIx0h", "ResourceEntityInstanceId": "3188ece4de5d428db3666adc57ef6990", "SortOrder": 1 } ], "RowVersion": "966cb62b-6294-42d6-b3f1-ac701e10ecac" }, "Success": true}DurationUnit is 0 days, 1 months, 2 years — this plan grants twelve
months of access.
Step 4 — Grant an entitlement
Section titled “Step 4 — Grant an entitlement”Granting switches to the public runtime API.
POST /api/workspace/entitlement-operations/entitlements
(operation)
needs only the plan and the owner record; the plan resolves the model, terms,
and covered resources. grant-preview runs the same resolution as a dry run:
curl -s -X POST https://ops.example.com/api/workspace/entitlement-operations/entitlements \ -H "Authorization: Bearer <token>" -H "Content-Type: application/json" \ -d '{ "PlanId": "5M05saIptqZI", "OwnerEntityInstanceId": "0f46096527cd4a24ae26087c4040919c" }'{ "Data": { "Id": "SxNWSs7BjQK8", "EntitlementNumber": 1, "EntitlementModelId": "8Ip2QXlDaNLk", "EntitlementPlanId": "5M05saIptqZI", "Owner": { "EntityDefinitionId": "vGMraALVTZk5", "EntityDisplayName": "Customer", "EntityInstanceId": "0f46096527cd4a24ae26087c4040919c" }, "Status": 2, "EffectiveState": 1, "StartsAtUtc": "2026-07-22T15:51:31.09229+00:00", "EndsAtUtc": "2027-07-22T15:51:31.09229+00:00", "Limits": [ { "Id": "HTCJy9FeWGK9", "LimitFamily": 0, "StartsAtUtc": "2026-07-22T15:51:31.09229+00:00", "EndsAtUtc": "2027-07-22T15:51:31.09229+00:00", "IsActive": true }, { "Id": "0q94w96LMhOD", "LimitFamily": 1, "GrantedAmount": 10.0000, "AvailableAmount": 10.0000, "ConsumedAmountSnapshot": 0.0000, "IsActive": true } ], "QuantityLedger": [], "AllowedActions": { "CanRenew": true, "CanSuspend": true, "CanRevoke": true, "CanConsume": true, "CanReverse": false, "CanAdjust": true } }, "Success": true}The plan’s terms became two limits: LimitFamily: 0 is the duration
term (twelve months from grant), LimitFamily: 1 is the consumable
quantity pool. Keep the quantity limit id — quantity operations target it
explicitly when an entitlement carries more than one quantity limit.
Step 5 — Consume quantity
Section titled “Step 5 — Consume quantity”POST /api/workspace/entitlement-operations/entitlements/{entitlementId}/quantity/consume
(operation):
curl -s -X POST https://ops.example.com/api/workspace/entitlement-operations/entitlements/SxNWSs7BjQK8/quantity/consume \ -H "Authorization: Bearer <token>" -H "Content-Type: application/json" \ -d '{ "Quantity": 1.5, "ReasonText": "Incident #4812 troubleshooting" }'{ "Data": { "Id": "SxNWSs7BjQK8", "Limits": [ { "Id": "HTCJy9FeWGK9", "LimitFamily": 0, "IsActive": true }, { "Id": "0q94w96LMhOD", "LimitFamily": 1, "GrantedAmount": 10.0000, "AvailableAmount": 8.5000, "ConsumedAmountSnapshot": 1.5000, "IsActive": true } ], "QuantityLedger": [ { "Id": "A4BO8YgXl6fu", "EntitlementLimitId": "0q94w96LMhOD", "Operation": 1, "Quantity": 1.5000, "BalanceAfter": 1.5000, "ReversibleQuantity": 1.5000, "ReasonText": "Incident #4812 troubleshooting", "OccurredAtUtc": "2026-07-22T15:51:32.648059+00:00", "CorrelationId": "ad8d16de0793473da712f35ef0e94c31" } ] }, "Success": true}Mistakes are corrected with .../quantity/reverse (undo a consumption) and
.../quantity/adjust (set a corrected level); every operation supports an
IdempotencyKey so an agent can retry safely.
Step 6 — Read the state
Section titled “Step 6 — Read the state”GET /api/workspace/entitlement-operations/entitlements/{entitlementId}
(operation)
returns the full detail — lifecycle Status and computed EffectiveState,
periods, resources, limits with remaining quantities, and the entitlement’s
own quantity ledger.
GET .../entitlements/summary
aggregates operational indicators (active entitlements, expiring soon,
failed renewals, usage pressure) for dashboards.
There is no HTTP “may this party use this resource right now” endpoint:
integrations read entitlement state through detail and summary, while
workspace C# logic can run the full access evaluation in-process through
IEntitlementRuntimeAccessService — see
C# business logic.
Step 7 — The ledger
Section titled “Step 7 — The ledger”Every entitlement operation lands in the append-only workspace ledger — the
grant itself included.
GET /api/workspace/entitlement-operations/ledger
(operation):
{ "Data": { "Items": [ { "Id": "A4BO8YgXl6fu", "EntitlementId": "SxNWSs7BjQK8", "EntitlementPlanDisplayName": "Support 10-hour pack", "Operation": 1, "Quantity": 1.5000, "BalanceAfter": 1.5000, "EntitlementLimitId": "0q94w96LMhOD", "ReasonText": "Incident #4812 troubleshooting", "Actor": { "DisplayName": "owner" }, "OccurredAtUtc": "2026-07-22T15:51:32.648059+00:00" }, { "Id": "HSLKRscSPMjW", "EntitlementId": "SxNWSs7BjQK8", "EntitlementPlanDisplayName": "Support 10-hour pack", "Operation": 0, "Quantity": 0.0000, "BalanceAfter": null, "Actor": { "DisplayName": "owner" }, "OccurredAtUtc": "2026-07-22T15:51:31.139382+00:00" } ] }, "Success": true}The ledger is the audit surface for quantity truth — reversals and
adjustments appear as their own entries instead of rewriting history, and
each entry carries the actor, the acted-on limit, and a CorrelationId for
tracing.
Configure a renewal policy
Section titled “Configure a renewal policy”Renewal is configured per model, then attached to a plan. Create a policy on the model through the Configuration API:
POST /api/workspace/entitlement-operations/models/{modelType}/{modelId}/renewal-policiesContent-Type: application/json
{ "DisplayName": "Annual manual review", "Mode": 1, "LeadDays": 30}Mode selects the workflow: 0 Disabled (no renewal workflow, no lead time or
key is stored), 1 ManualReview (a background worker opens a manual-review
renewal operation when the active period enters the LeadDays lead window), and
2 External (a trusted external system decides, using the stored
ExternalPolicyKey).
Assign the policy to a plan through the plan’s optional RenewalPolicyId, so new
grants from that plan inherit it. ManualReview renewal operations are then
observed, retried, and cancelled through the public renewal-operations endpoints
(ListRenewalOperationsAsync from C#) — never reconstruct renewal state
yourself.
Where to go next
Section titled “Where to go next”- Lifecycle —
suspend,resume,revoke,expire, andrenewon the same entitlement resource. - Boards for entitlement-related work — put the related Entity record on the Board for the normal Runtime UI workflow. A direct Entitlement Board target is an advanced API/C# scenario without full Runtime Board UI support.