ข้ามไปยังเนื้อหาหลัก

LOS Flow

This guide describes the Loan Origination System (LOS) flow end-to-end. It gives you everything you need to build your own origination front end against our public API — the same journey a borrower or sales agent follows to take an application from creation to a decision.

Origination is workflow-driven: the server owns the process and tells your application what to do next. Your job is to start a workflow, render the form it asks for, submit the answers, and advance — repeating until the workflow reaches a final state. You never hard-code the sequence of screens; you follow the workflow.

All REST paths below are relative to your gateway base URL:

https://openapi-dev.alpha.looms.cloud

Key concepts

  • Workflow — an origination journey is a server-side workflow you start and then drive step by step. Each step tells you what to render and which transitions are allowed.
  • JSON form structures — most steps present a form. The form is described by a JSON structure that you fetch and render dynamically; you collect the user's answers and submit them back. Forms are not fixed in your code, so the platform can change a journey without you shipping a new build.
  • Application identity — starting a workflow creates an instance; its id also serves as the application's request id, which threads through every later call.
  • Status — an application's status is a two-level value (a main status and a sub-status) resolved against status dictionaries.

Sequence

Step-by-step

1. Authenticate

Obtain an OAuth2 access token and send it as a bearer token on every request:

Authorization: Bearer <access_token>

See the Quick Start for obtaining credentials and a token. Tokens expire — when a request returns 401 Unauthorized, refresh the token and retry the request once. All data is automatically scoped to the organization your credentials belong to.

2. Start an application

List the available onboarding workflows, then start an instance of the one you want.

GET /state-machine/api/state-machines?purpose=ONBOARD
POST /state-machine/api/state-machine-instances/start
Content-Type: application/json

{ "stateMachineId": "<id from the list above>" }

The start response contains an id — this is your instance id. Keep it: it identifies this application for the rest of the flow and is also used as the application's request id.

3. Drive the workflow (the main loop)

Repeat until the workflow reaches a final state.

a. Read the current state for your instance id:

GET /state-machine/api/state-machine-instances/current-state/{instanceId}

The response tells you the current step and whether it is a start/final step, the action to take (render a form, wait for server-side processing, or show a final result), the form id(s) to render, and the request id used to correlate form submissions to this application.

b. If the step renders a form, work with the Forms API. The current state gives you the form id(s); fetch each form's JSON structure and render it dynamically:

GET /form-middleware/v1/form/{formId}

Collect the user's answers and submit them. Carry the application's request id in the x-request-id header so the submission is tied to this application; the submit returns a submission id:

POST /form-middleware/v1/form/{formId}/submission
x-request-id: <requestId>
Content-Type: application/json

{ "data": { /* the user's answers, keyed by the form's field names */ } }

To pre-fill a step the user has already answered, batch-read existing submissions by their form ids:

GET /form-middleware/v1/submission/bulk?_id__in={formId1},{formId2}
x-request-id: <requestId>

c. Advance the workflow to the next step:

POST /state-machine/api/state-machine-instances/{instanceId}/continue
Content-Type: application/json

{ "condition": "SUCCESS" }

d. If the step is processing on the server (no form), wait for it to finish — either via the real-time channel or by re-reading the current state on a short interval — then continue.

Re-entering and correcting steps

Let the user go back one step, or return to a specific earlier form to amend an answer:

POST /state-machine/api/state-machine-instances/step-back/{instanceId}
POST /state-machine/api/state-machine-instances/return-to-prev-state
Content-Type: application/json

{
"requestId": "<instanceId>",
"formId": "<formId>",
"returnType": "USER_DATA",
"context": { "reason": "Need additional data" }
}

4. Detect completion

The current state's action tells you when the journey is over. Final actions are SUCCESS (approved/completed), REJECTED, and ERROR. When you receive one, stop the loop and show the corresponding result screen; the state also indicates whether the user can return to a dashboard.

5. Track applications

GET /relation-store/loan-applications?page={n}&limit={n}&sort=updated_at&sortDir=desc

List an organization's applications (paged and filterable). Filter to top-level applications with application_type=eq:MAIN. Fetch a single application's full detail by its request id:

GET /relation-store/loan-applications/{requestId}

Status model

An application carries numeric status ids rather than status text. Fetch the two reference dictionaries once and cache them, then map an application's sub-status id to its label:

GET /relation-store/application-statuses/main
GET /relation-store/application-statuses/main-sub

Each returns entries of { id, code, label }. The status you display is the application's sub-status id resolved against the sub-status dictionary.

6. Documents and contracts

Documents generated for an application:

POST /dms-v2/nodes/search
Content-Type: application/json

{ "level": 3, "page": 1, "limit": 20, "metadata": { "requestId": "<requestId>" } }
GET /dms-v2/nodes/{nodeId}/download      # returns the file (application/octet-stream)

Contracts generated for an application:

GET /contract-manager/contract/bundle/list?applicationId={requestId}&page=1&size=20
GET /contract-manager/file/{fileUrl} # returns the contract file

Each contract carries a status and signingMethod, so you can present only the ones relevant to the user.

Real-time updates with STOMP

The platform pushes workflow state changes over STOMP 1.2 on a WebSocket, so your UI can react the moment the server advances a step (instead of polling). The reference client is @stomp/stompjs.

npm install @stomp/stompjs
import { Client, type IMessage } from "@stomp/stompjs";

// Provided per environment: the notifications WebSocket URL and the environment
// segment used in topic names. `userUuid` is your authenticated user id
// (the `sub` claim of your access token).
const client = new Client({
brokerURL: `${WS_URL}?userUuid=${userUuid}&userId=${userUuid}&transport=websocket`,
connectHeaders: { Authorization: `Bearer ${accessToken}` },
reconnectDelay: 5000,
heartbeatIncoming: 5000,
heartbeatOutgoing: 5000,
onConnect: () => {
// All state changes for the current user:
client.subscribe(`/topic/state_change.${env}.${userUuid}.#`, (message: IMessage) => {
const event = JSON.parse(message.body);
// event.stateMachineInstanceId changed → re-fetch its current state.
// Treat the message as a signal to refresh, not as the source of truth.
});
},
});

client.activate();
// On teardown: client.deactivate();

Topics

Subscribe to the scope you need. {env} is your environment segment, {userUuid} is your user id, and {instanceId} is a workflow instance id:

TopicScope
/topic/state_change.{env}.{userUuid}.#Every state change for the user
/topic/state_change.{env}.{userUuid}.instance.{instanceId}A single application/instance
/topic/state_change.{env}.{userUuid}.dashboardDashboard-level changes

The server only delivers events whose {userUuid} segment matches the authenticated user.

Message payloads

A state-change message:

{
"userUuid": "...",
"stateMachineInstanceId": "...",
"stateMachineId": "...",
"currentStateId": "...",
"currentStateName": "...",
"updated": 1700000000000
}

A deletion message:

{ "type": "DELETE", "userUuid": "...", "stateMachineInstanceId": "..." }

When you receive a message, re-fetch the current state for that stateMachineInstanceId (step 3a) and react to the new action.

Polling fallback

If you don't want a WebSocket, you can poll GET .../current-state/{instanceId} on a short interval while a step is processing. Polling alone is enough to build a fully working client; STOMP simply makes it real-time.

Request context

Beyond the bearer token:

  • Requests are scoped to your organization, derived from your credentials.
  • Form submissions must include the request id of the application they belong to, so the platform can correlate the answers to the right application.

Endpoint reference

StepMethod & path
List onboarding workflowsGET /state-machine/api/state-machines?purpose=ONBOARD
Start a workflow instancePOST /state-machine/api/state-machine-instances/start
Read current stateGET /state-machine/api/state-machine-instances/current-state/{instanceId}
Advance the workflowPOST /state-machine/api/state-machine-instances/{instanceId}/continue
Step backPOST /state-machine/api/state-machine-instances/step-back/{instanceId}
Return to a previous formPOST /state-machine/api/state-machine-instances/return-to-prev-state
Fetch a form's JSON structureGET /form-middleware/v1/form/{formId}
Submit form answersPOST /form-middleware/v1/form/{formId}/submission
Bulk-read submissions (prefill)GET /form-middleware/v1/submission/bulk?_id__in=...
List applicationsGET /relation-store/loan-applications
Application detailGET /relation-store/loan-applications/{requestId}
Status dictionariesGET /relation-store/application-statuses/main, GET /relation-store/application-statuses/main-sub
List / download documentsPOST /dms-v2/nodes/search, GET /dms-v2/nodes/{nodeId}/download
List / download contractsGET /contract-manager/contract/bundle/list, GET /contract-manager/file/{fileUrl}
Real-time updatesSTOMP over WebSocket — topic /topic/state_change.{env}.{userUuid}.#

Scope

This page covers the borrower-facing origination journey only — starting a workflow, completing its forms, tracking status, and retrieving documents and contracts. That is everything required to reproduce the origination experience. Administrative and operational concerns (user provisioning, audit logging, back-office automation) are outside this flow and are not part of the public integration surface.