Skip to main content
POST
/
v1
/
loans
/
external
/
{loan_external_id}
/
rollback
Roll back a loan
curl --request POST \
  --url https://api.dev.bsa.ai/v1/loans/external/{loan_external_id}/rollback \
  --header 'Authorization: Bearer <token>' \
  --header 'Content-Type: application/json' \
  --data '
{
  "note": "<string>"
}
'
Removes a loan that should never have existed — the exception flow for a failed wallet disbursement. One call performs the full teardown internally (undo-disbursalundo-approvaldelete) and returns a single response, so your exception handler doesn’t need to chain three synchronous requests. The three individual endpoints remain available; rollback is the consolidated form built on top of them.

Resumable by design

Each teardown step is durable in the LMS the moment it succeeds. If a step fails (or your call times out mid-sequence), the loan is left at a well-defined interim state — and calling rollback again resumes from that state instead of failing on steps that already ran:
Loan status when calledSteps performed
Activeundo-disbursal → undo-approval → delete
Approvedundo-approval → delete
Submitted and pending approvaldelete
Closed (obligations met), Closed (written off), Overpaidrejected with 409 aborted — settled loans can’t be rolled back
So the partner-side recovery logic collapses to: call rollback until you get 200. There is no partial-failure state you need to disambiguate yourself — but if you want to inspect anyway, GET /v1/loans/external/{loan_external_id} reports the interim status dynamically after every step (ActiveApprovedSubmitted and pending approval404 once deleted).
A loan with recorded repayments is rejected with 409 aborted. Rollback assumes the loan should never have existed; once a customer has paid against it, that premise no longer holds and erasing the payment must be an explicit decision. Reverse the repayment(s) first, then roll back.

Path parameters

loan_external_id
string
required
The loan’s externalId. On the /v1/loans/{loan_id}/rollback form, this is the numeric LMS id instead.

Request body

note
string
Optional. Reason for the rollback — recorded against both undo steps in the LMS audit log.
The body is optional; POST with no body is valid.

Examples

# By loan externalId (recommended)
curl -sf -X POST "$BASE/v1/loans/external/loan-ext-12345/rollback" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"note": "Wallet credit failed"}'

# Same effect, by LMS id
curl -sf -X POST "$BASE/v1/loans/501/rollback" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"note": "Wallet credit failed"}'

Response

200 OK with the steps that ran on this call. A rollback resumed from an interim state reports only the remaining steps it executed.
{
  "rolledBack": true,
  "stepsPerformed": ["undo-disbursal", "undo-approval", "delete"]
}
After a successful rollback the loan no longer appears in queries — GET /v1/loans/external/{loan_external_id} returns 404. The LMS retains the full activity history (creation, approval, disbursal, every reversal, deletion) in its audit log.

Errors

CodeWhen
not_foundNo loan with that id or externalId (including a loan already fully rolled back)
abortedLoan is settled (Closed / Overpaid), or has recorded repayments — rollback is not applicable in either case
otherA teardown step failed mid-sequence. The message names the step that halted and the steps already completed; completed steps are durable — call rollback again to resume