Finance
Automating mortgage pre-approval with Dynamiq and Amazon Nova for banks and lenders
Build a multi-agent mortgage pre-approval workflow on Amazon Nova: four specialist agents, a senior risk analyst, your lending policy and a human sign-off.

In short
Mortgage pre-approval can run as five cooperating agents: financial, credit, income and property specialists working in parallel, and a senior risk analyst who applies your lending policy. This tutorial builds that workflow with the Dynamiq SDK on Amazon Nova models through Amazon Bedrock. The agents recommend; an underwriter decides, and every run is traced for audit.
You can automate most of the mortgage pre-approval review with a team of AI agents that mirrors an underwriting team. Specialists analyze the applicant's finances, credit, income and property in parallel, and a senior risk analyst combines their reports under your lending policy. This tutorial builds that workflow with the Dynamiq SDK and Amazon Nova models on Amazon Bedrock; the result is a recommendation with its reasons, and an underwriter still makes the decision.
Why automate mortgage pre-approval?
Pre-approval is one of the slowest steps in a home purchase. An underwriter pulls bank data, a credit report and income documents, runs the same calculations for every file and writes up a recommendation, often while the buyer waits on it to make an offer.
Agents help in three ways:
- Speed. The analysis starts as soon as the file is complete, and the four specialist reviews run at the same time instead of one after another.
- Consistency. Every file gets the same checks, calculations and report structure.
- Explainability. Each agent shows its inputs and formulas, and every run is traced, so the reasons behind a recommendation can be checked.
That last point is a legal requirement, not a nice-to-have. In the US, the Equal Credit Opportunity Act and Regulation B require lenders to give applicants specific reasons when they deny credit. A workflow that cannot explain itself cannot be used for credit decisions.
What does the workflow look like?
The workflow mirrors a pre-approval team:
- Applicant file. The loan request, bank account and liability data (for example from Plaid) and a credit report (for example from Experian) are combined into one packet. This tutorial mocks both data sources.
- Four specialists, in parallel:
- A financial analyst calculates the debt-to-income ratio (DTI).
- A credit analyst reviews the score, payment history, inquiries and utilization.
- An income verifier assesses employment stability and verified income.
- A property analyst checks the value and calculates the loan-to-value ratio (LTV).
- A senior risk analyst reads the four reports, applies your lending policy and writes the recommendation.
- An underwriter reviews the recommendation and decides.
Step 1: Connect to Amazon Nova on Bedrock
Install the SDK with pip install dynamiq, and make sure your AWS credentials can call Amazon Bedrock in a region where your chosen Nova model is enabled. You also need an E2B_API_KEY, because the specialists run their arithmetic in a code sandbox instead of doing it in their heads.
from dynamiq.connections import AWS as AWSConnection
from dynamiq.nodes.llms import Bedrock
llm = Bedrock(
connection=AWSConnection(), # reads AWS credentials and region from the environment
model="us.amazon.nova-premier-v1:0", # any Amazon Nova model enabled in your account
temperature=0,
max_tokens=4000,
)
The same workflow runs on any of the 29 model providers the SDK supports; change the LLM node and nothing else.
Step 2: Assemble the applicant file
In production, this data comes from your loan origination system and data providers. Here it is mocked:
import json
loan_request = {
"loan_amount": 240000,
"property_value": 300000,
"down_payment_percentage": 20,
"interest_rate": 6.5,
"term_years": 30,
"property_type": "single-family residence",
"occupancy": "primary residence",
"annual_income": 96000,
"employment": {"employer": "Example Manufacturing Co.", "years_in_role": 4},
}
bank_data = { # shaped like a bank data aggregator's response
"accounts": {"checking": 9200.0, "savings": 62500.0},
"liabilities": {
"credit_cards": {"total_balance": 1800.0, "minimum_payment": 60.0},
"student_loans": {"total_balance": 12800.0, "monthly_payment": 275.0},
},
}
credit_report = { # shaped like a credit bureau's response
"credit_score": 688,
"credit_history_length_years": 5,
"payment_history": {"on_time": "94%", "late_30_days": 2, "late_60_days": 1},
"revolving_utilization": "36%",
"hard_inquiries_last_6_months": 3,
"verification": {"identity": True, "employment": False, "income": True},
}
application_packet = (
f"Loan request:\n{json.dumps(loan_request, indent=2)}\n\n"
f"Bank accounts and liabilities:\n{json.dumps(bank_data, indent=2)}\n\n"
f"Credit profile:\n{json.dumps(credit_report, indent=2)}"
)
Step 3: Create the four specialist agents
Each specialist gets a focused role, a code interpreter for calculations and the same reporting rules, so the senior analyst receives reports in a consistent shape.
from dynamiq.connections import E2B as E2BConnection
from dynamiq.nodes.agents import Agent
from dynamiq.nodes.tools.e2b_sandbox import E2BInterpreterTool
from dynamiq.nodes.types import Behavior, InferenceMode
REPORTING_RULES = (
" Show each input and formula before the result, and run calculations in the code interpreter."
" If data is missing, write 'Data not provided' and explain the effect on confidence."
" End with a risk level (Low, Moderate or High) and a recommendation"
" (Approve, Approve with conditions, or Decline) with at most two sentences of reasons."
)
ROLES = {
"Financial analyst": (
"You are a mortgage underwriting financial analyst. List every monthly debt obligation, "
"include the proposed mortgage payment, confirm gross monthly income, and calculate the "
"debt-to-income ratio (DTI = total monthly debt / gross monthly income x 100)."
),
"Credit analyst": (
"You are a credit risk underwriter. Assess the credit score on the 300 to 850 scale, "
"payment history, recent hard inquiries and revolving utilization."
),
"Income verifier": (
"You are an income verification specialist. Assess employment stability, which income "
"sources are verified or unverified, and any inconsistencies an underwriter should resolve."
),
"Property analyst": (
"You are a property valuation analyst. Check the property value, calculate the loan-to-value "
"ratio (LTV = loan amount / property value x 100), state whether mortgage insurance applies, "
"and assess collateral risk."
),
}
specialists = [
Agent(
name=name,
role=role + REPORTING_RULES,
llm=llm,
tools=[E2BInterpreterTool(connection=E2BConnection())], # reads E2B_API_KEY
inference_mode=InferenceMode.XML,
max_loops=8,
behaviour_on_max_loops=Behavior.RETURN,
)
for name, role in ROLES.items()
]
Step 4: Add your lending policy and the senior risk analyst
The senior analyst runs after all four specialists and receives their reports together with your institution's policy. The policy below is illustrative; replace it with your own credit policy.
LENDING_POLICY = """Illustrative pre-approval policy:
- Maximum DTI 43%; 36% or lower preferred.
- Minimum credit score 640.
- Maximum LTV 95%; mortgage insurance required above 80%.
- If any major factor is weak, at least two strong compensating factors must exist:
a credit score above 760, a down payment above 20%, reserves above 6 months,
or an LTV below 75%."""
SENIOR_ANALYST_ROLE = (
"You are the senior risk analyst and write the recommendation an underwriter will review. "
"Rely on the specialist reports and the lending policy; do not re-analyze raw data. "
"Write a report with: an overall risk score out of 100 with a label; a recommendation "
"(Recommend pre-approval, Recommend with conditions, or Recommend decline); the specific "
"reasons, citing DTI, credit, income and LTV; the maximum loan amount you recommend; "
"and the conditions an underwriter must verify."
)
def compile_reports(inputs: dict, outputs: dict) -> str:
reports = "\n\n".join(f"## {agent.name}\n{outputs[agent.id]['content']}" for agent in specialists)
return f"{reports}\n\n## Lending policy\n{LENDING_POLICY}"
risk_analyst = (
Agent(
name="Senior risk analyst",
role=SENIOR_ANALYST_ROLE,
llm=llm,
max_loops=4,
behaviour_on_max_loops=Behavior.RETURN,
)
.inputs(input=compile_reports)
.depends_on(specialists)
)
Step 5: Run the workflow
The four specialists share no dependencies, so the flow runs them in parallel; the senior analyst starts when all four have finished.
from dynamiq import Workflow
from dynamiq.flows import Flow
workflow = Workflow(flow=Flow(nodes=[*specialists, risk_analyst]))
result = workflow.run(input_data={"input": application_packet})
print(result.output[risk_analyst.id]["output"]["content"])
This applicant is a borderline case by design. The DTI is comfortable, but the LTV sits at the 80% threshold, the credit score clears the policy minimum without being strong, and employment is not yet verified. A good report reflects that with a moderate risk score and conditions for the underwriter to clear, such as employment verification and recent pay stubs.
What does it take to run this at a lender?
The workflow above is the analytical core. A production deployment adds controls that let risk, compliance and model governance teams sign off:
- A human decision. On the Dynamiq platform, an approval pauses the workflow before a recommendation leaves the system, and the underwriter can edit its fields before it proceeds.
- A complete record. Every run is traced, with each agent's inputs, outputs, tool calls, cost and latency, which gives you the reasons behind every recommendation; see observability.
- Measured consistency. Build a dataset from past applications with known outcomes, score the workflow against it with evaluations, and attach real-time evals to the live deployment.
- Fair lending checks. Keep protected characteristics out of the agents' inputs, test outcomes across applicant groups, and review the prompts and policy with your compliance team.
- Your data, your perimeter. Detect PII before it reaches a model, and run Dynamiq in your own AWS account; it is available on AWS Marketplace and can be self-hosted, as described on how we deploy.
Plaid is among the platform's app integrations, and credit bureau or loan origination APIs connect through an HTTP tool, so the mocked data sources become live ones without changing the agents.
For the broader pattern of reading loan files, claims and contracts against your rules, see document review and underwriting.
FAQ
Can AI approve a mortgage on its own?
It shouldn't. AI agents can analyze an application and recommend a decision with reasons, but a qualified underwriter should make the credit decision. Regulations such as the Equal Credit Opportunity Act also require lenders to explain denials with specific reasons.
Why use several agents instead of one?
Each specialist has a narrow role and a short prompt, which makes its analysis easier to test and audit. Running them in parallel is also faster, and a failure in one review is visible in its own report instead of buried in a single long answer.
Which models can I use for mortgage analysis?
This tutorial uses Amazon Nova through Amazon Bedrock, which keeps inference inside AWS. The Dynamiq SDK supports 29 model providers, so you can use the model your institution has approved and compare candidates with evaluations before you switch.
How do you explain an AI-assisted credit recommendation?
Require every agent to show its inputs, formulas and reasons, keep the lending policy explicit in the workflow, and keep the trace of each run. Together they show which data and which rule led to the recommendation.
Can this run inside our own AWS account?
Yes. Dynamiq can be self-hosted in your AWS account on Kubernetes and is available on AWS Marketplace, and Amazon Bedrock keeps model calls within AWS.


