Governed operations & action requests
Execute typed operations at their owning boundary, materialize approval work only when required, and retain the generalized gateway contract as product direction.
The current servicing runtime does not create an action request for every change. The owning operation resolves current facts and policy first. An ALLOWED operation executes immediately; an action request is materialized only for APPROVAL_REQUIRED, deferred, manual, external, or unavailable work.
Current implemented boundary
| Work | Current path | Authority |
|---|---|---|
| Servicing-owned change | The typed resource endpoint, such as POST /v1/servicing/cases/{caseId}/promises or /restriction-changes. |
The endpoint’s named permission plus its execution-time policy result. |
| Direct servicing outcome | /responses, /follow-up-tasks, and /payment-support-reviews. |
Executes directly after current-fact checks; it does not fabricate an action request. |
| Lending Core command | The canonical customer or loan route under /v1, with LendEasy-Case on a human servicing call. |
The command’s exact LMS permission. The case supplies causation and scope, never authority. |
| Workflow automation | A registered typed case effect. | The workflow and effect type are closed contracts; callers cannot submit an arbitrary command, URL, or payload. |
The governed operations
Eight case-scoped operations cover the borrower-facing and account-changing work the servicing plane owns. Each is a typed resource under its case, each requires its own permission, and each decides for itself whether to execute or materialize a request:
| Operation | Route on /v1/servicing/cases/{caseId} |
Permission |
|---|---|---|
| Prepare a customer-authorized payment | POST /payment-requests |
CREATE_SERVICING_PAYMENT_REQUEST |
| Record an external payment | POST /external-payments |
RECORD_SERVICING_EXTERNAL_PAYMENT |
| Update contact or consent | POST /contact-consent-updates |
UPDATE_SERVICING_CONTACT_CONSENT |
| Apply or clear a restriction | POST /restriction-changes |
CHANGE_SERVICING_RESTRICTION |
| Update a bankruptcy record | POST /bankruptcy-updates |
UPDATE_SERVICING_BANKRUPTCY |
| Send or log a case response | POST /responses |
SEND_SERVICING_RESPONSE |
| Create internal follow-up work | POST /follow-up-tasks |
CREATE_SERVICING_FOLLOW_UP_TASK |
| Create payment-support review work | POST /payment-support-reviews |
REQUEST_SERVICING_PAYMENT_SUPPORT_REVIEW |
Every one of them requires a reason. The three that change authoritative state on the strength of evidence — external payments, restriction changes, and bankruptcy updates — also require evidenceIds, so a protective control can never rest on an assertion alone.
curl -X POST "$BASE/v1/servicing/cases/1001/restriction-changes" \
-H "Authorization: Bearer $TOKEN" \
-H "LendEasy-Tenant: demo-lender" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: cease-1001-2027-05-05" \
-d '{
"customerRef": "123",
"loanRef": "456",
"operation": "APPLY",
"restrictionType": "CEASE_COMMUNICATIONS",
"effectiveAt": "2027-05-05T18:30:00Z",
"reason": "Verified written cease request.",
"evidenceIds": [8801]
}'
The response is an outcome envelope, not a request receipt:
{
"outcome": "COMPLETED",
"restrictionRef": "restriction-41",
"resourceId": 41
}
Branch on outcome. COMPLETED means the typed operation already executed against the authoritative boundary and the returned reference is real. Where policy instead requires approval, or execution is deferred, manual, external, or unavailable, the same call reports that and returns the materialized action request — which is the only way an action request comes into existence.
Two of these are worth calling out because they look like approvals and are not. /responses, /follow-up-tasks, and /payment-support-reviews create the owning interaction or task directly after current-fact checks. A payment-support review is specialist work, not a request for someone to authorize something.
Applying a protective restriction and relaxing one follow separate action-specific policy. operation: APPLY on a cease request is not the mirror image of clearing it, and the two do not share an approval threshold.
Action-request lifecycle
When an owning operation does materialize a request, the implemented API exposes the case-scoped lifecycle:
| Route | Purpose | Permission |
|---|---|---|
GET /v1/servicing/cases/{caseId}/action-requests |
List only the requests actually materialized on this case. | READ_SERVICING_CASE |
GET /v1/servicing/action-requests/{actionRequestId} |
Read the typed request and its safe lifecycle state. | READ_SERVICING_ACTION_REQUEST |
POST .../{actionRequestId}/approve |
Approve with a required decisionNote. |
APPROVE_SERVICING_ACTION_REQUEST |
POST .../{actionRequestId}/reject |
Reject with a required reason. |
REJECT_SERVICING_ACTION_REQUEST |
POST .../{actionRequestId}/submit |
Execute the approved inputs. Accepts no request body. | SUBMIT_SERVICING_ACTION_REQUEST |
POST .../{actionRequestId}/manual-complete |
Record a manual result, only from MANUAL_EXECUTION_REQUIRED. |
MANUAL_COMPLETE_SERVICING_ACTION_REQUEST |
Both reads return allowedActions computed for the calling actor:
{
"actionRequestId": 6001,
"externalActionRef": "LE-ACTION-6001",
"caseId": 1001,
"actionType": "AR_002",
"riskClass": "R2",
"status": "APPROVAL_REQUIRED",
"preparedByActorType": "HUMAN",
"preparedByWorkerId": 4101,
"approvalRequired": true,
"approvalPermissionCode": "APPROVE_SERVICING_PROMISE",
"allowedActions": ["APPROVE", "REJECT"],
"requestPayload": { "loanRef": "456", "planType": "SINGLE", "promiseDate": "2026-08-01" }
}
A workspace should render buttons from allowedActions rather than inferring them from status. The list already accounts for the caller’s permissions and for maker-not-approver, so the maker who prepared request 6001 sees an empty approval set on their own request.
Approving checks two authorities, not one: the route permission above and the server-derived action-specific permission named in approvalPermissionCode — APPROVE_SERVICING_<ACTION>. The caller cannot choose riskClass, approvalPermissionCode, or a generic approval role; all three are derived from the action type. Submit takes no body precisely so that approved inputs stay immutable between approval and execution.
Manual completion is evidence-backed and reachable only from MANUAL_EXECUTION_REQUIRED. outcomeCode, reason, and evidenceIds are all required, with an optional externalResultRef naming the authoritative record outside the platform:
{
"outcomeCode": "COMPLETED_EXTERNALLY",
"reason": "The external system completed the change.",
"evidenceIds": [8801],
"externalResultRef": "external-result-71"
}
The generalized lifecycle (roadmap)
Typed actions, not raw commands
Every request has an actionType from the published catalog and a schema specific to that type. Callers cannot submit arbitrary command names, URLs, or unvalidated payloads.
The catalog includes payments, autopay changes, external-payment records, contact-consent updates, restrictions, re-aging, re-amortization, rescheduling, interest treatment, charges, refunds, recovery payments, write-off, charge-off, fraud flags, SCRA adjustments, contract termination, and closure. See Servicing actions.
Decide, then prepare
There is no preview endpoint on the built-in Lending Core and no preview token. For a schedule-changing or balance-changing action, decide from authoritative reads—GET /v1/loans/{loanId}?associations=repaymentSchedule,transactions and GET /v1/loans/{loanId}/summary—and attach the before/after comparison the decision rests on to the case. Preparation freezes the typed payload and context:
{
"actionType": "re_amortize",
"loanRef": "7204",
"parameters": {
"interestHandling": "default"
},
"context": {
"caseId": "case_CEDAR_HARDSHIP_4",
"reasonCode": "temporary_income_reduction"
}
}
Approval binds to the hash of that frozen request. If material facts change before submission, the execution-time gate fails the request closed and the decision must be remade from fresh reads.
Operational context is mandatory
Every state-changing action links to one of:
- a case it serves;
- a controlled administrative exception with approver and expiry; or
- a named system trigger with policy version.
The context is not a free-form note. It carries structured reason, origin, source interaction or task, evidence references, and actor. This makes the chain traversable from ledger transaction back to decision.
Execution-time gate
Immediately before submission, the gateway re-reads critical facts and reruns policy. Approval from yesterday does not override a bankruptcy restriction received today. Possible outcomes are:
| Decision | Meaning |
|---|---|
ALLOWED |
Execute under the resolved policy. |
ALLOWED_WITH_WARNING |
Execute and preserve named warnings. |
APPROVAL_REQUIRED |
Stop until the required role approves the frozen action. |
BLOCKED |
A known rule prohibits the action. |
UNAVAILABLE_DUE_TO_MISSING_FACTS |
Required facts are absent or stale; fail closed. |
The result stores rule IDs, jurisdictions, effective versions, facts and freshness, and any override—not merely a boolean.
Approval and separation of duties
Approval policy can consider action type, amount, balance effect, customer vulnerability, source worker, and tenant thresholds. Maker and checker must be distinct where dual control applies. A checker sees the same before/after comparison, evidence, warnings, and action hash that execution will use.
AI can prepare and recommend an action within its work packet. It cannot supply human approval, change its own autonomy level, or alter the action after approval.
Idempotent submission and acknowledgement
The gateway derives a stable execution key from tenant, action request, and attempt. The system of record must return the same acknowledgement for a retry.
For the built-in Lending Core, execution is in process rather than a call to a generic actions API: a typed executor resolves the loan’s facts and capabilities, applies the governed native command—case context attached, maker-checker parking respected—and records the case effects and governance record. A bring-your-own-core binding submits the same typed action over its documented HTTP contract.
{
"actionRequestId": "act_CEDAR_RA_81",
"status": "completed",
"result": {
"coreCommand": "reAmortize",
"resourceId": 7204,
"governanceId": 903,
"effectiveDate": "2027-08-20"
},
"requestedHash": "sha256:2a7f…12ce",
"appliedHash": "sha256:2a7f…12ce"
}
Normalized execution results are COMPLETED, PENDING_EXTERNAL, REJECTED, MANUAL_REQUIRED, FAILED, and CAPABILITY_UNAVAILABLE. Provider-native detail remains available in restricted evidence.
Divergence and manual execution
If the applied amount, date, treatment, or resulting state differs from the approved request, the gateway records both, marks FAILED_ACTION_REVIEW, and opens reconciliation work. It never labels the requested value as applied.
For a MANUAL_REQUIRED binding, an approved specialist records the authoritative reference and applied facts. Manual completion passes the same reconciliation checks; it is not a free-form “done” button.
Undo is a compensating action
Supported undo operations—dedicated commands such as undowriteoff, undo-charge-off, undoReAge, undoReAmortize, and undoContractTermination on the built-in core—create a new action linked to the original. They verify that the target is still reversible and that no later dependent transaction makes undo unsafe. The original action, approval, and evidence remain immutable.