Governed operations & action requests
Every action runs in the system that owns it, an approval is opened only when one is genuinely needed,
Every account change a case needs — take a payment, apply a hold, change a schedule, send a letter — runs on the system that owns it and comes back either done, or as an approval request naming who has to decide. An action request exists only where the change could not simply proceed, so its presence is a signal that something is genuinely blocked. A gateway that manufactured one for every change would make that signal worthless, which is why this one does not. The owning operation resolves current facts and policy first: an ALLOWED operation executes immediately, and a request is opened only for APPROVAL_REQUIRED, deferred, manual, external, or unavailable work.
Where each change runs
| Work | Current path | Authority |
|---|---|---|
| Servicing-owned change | The resource’s own 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 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 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 workspace owns. Each is its own resource under its case, each requires its own permission, and each decides for itself whether to execute or open 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": "412",
"loanRef": "7204",
"operation": "APPLY",
"restrictionType": "CEASE_COMMUNICATIONS",
"effectiveAt": "2027-05-05T18:30:00Z",
"reason": "Verified written cease request.",
"evidenceIds": [8801]
}'
The response is an outcome, not a request receipt:
{
"outcome": "COMPLETED",
"restrictionRef": "restriction-41",
"resourceId": 41
}
Branch on outcome. COMPLETED means the operation already executed against the system that owns it 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 action request it opened — 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 open 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 opened on this case. | READ_SERVICING_CASE |
GET /v1/servicing/action-requests/{actionRequestId} |
Read the 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": "7204", "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"
}
Named 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 request and its 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.
Every change names the matter it serves
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.
Approval from yesterday is not permission today
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.
An AI agent executes the actions its registration grants and its autonomy level allows, and prepares the rest for the person policy reserves them to. It cannot supply that approval, raise its own autonomy level, or alter an action after it is approved.
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: an 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 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.
It never labels the requested value as applied
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.