Loan Inquiry Agent
Build a banking virtual assistant that answers loan questions, grounds replies in rate and eligibility FAQs, looks up application status via a custom API, and supports webchat plus outbound voice.
What you'll build
An Apex Loan Agent that handles customer inquiries across webchat and voice: it explains mortgage, personal, and auto loan options, quotes eligibility rules and rates from a knowledge base, looks up application status through your core banking API, and can place outbound follow-up calls. This recipe is the most complete SDK walkthrough for workflow → agent → knowledge base → custom API → go live → chat → telephony.
Customer journey
A prospective borrower visits your bank website and opens the Web Chat widget to ask about personal loan rates and eligibility. The agent answers from the FAQ knowledge base and, if the customer provides an application reference, calls your core banking API to look up status. For high-intent leads, your backend triggers an outbound call so the agent can walk through next steps by phone.
No human is in the loop for standard rate and eligibility questions. Escalate to a loan officer when the customer requests an exception or disputes a decision — set Escalation Email under Settings → Agent.
Prerequisites
- BimpeAI account with an API key (
sk_…) - Read Anatomy of a workflow agent first — this recipe skips steps covered there
- A reachable core banking HTTP API (for the custom integration in Step 4)
- Outbound telephony: either an approved phone number on your team account, or willingness to run test calls with
is_test_call: truewhile provisioning completes
Steps
1. Create the workflow
Build a workflow from scratch with workflows.create. Only name and system_prompt are required; the returned id is what you bind the agent to in the next step.
import { BimpeAI } from "@bimpeai/sdk";
const bimpe = new BimpeAI({ apiKey: process.env.BIMPEAI_API_KEY! });
const loanWorkflow = await bimpe.workflows.create({
name: "Loan Inquiry Rules",
system_prompt:
"You are a professional banking virtual assistant specialized in loans. " +
"Your job is to gather initial information from customers regarding loan types " +
"(e.g., mortgage, personal, auto), explain basic interest rates, check " +
"eligibility rules, and assist them with their application status.",
});
console.log(loanWorkflow.id);import os
from bimpeai import BimpeAI
client = BimpeAI(api_key=os.environ["BIMPEAI_API_KEY"])
loan_workflow = client.workflows.create(
name="Loan Inquiry Rules",
system_prompt=(
"You are a professional banking virtual assistant specialized in loans. "
"Your job is to gather initial information from customers regarding loan types "
"(e.g., mortgage, personal, auto), explain basic interest rates, check "
"eligibility rules, and assist them with their application status."
),
)
print(loan_workflow.id)2. Create the agent
const loanAgent = await bimpe.agents.create(
{
name: "Apex Loan Agent",
description: "Handles client inquiries regarding rates, eligibility, and loan status.",
workflow_id: loanWorkflow.id,
persona: "professional",
},
{ idempotencyKey: "create-apex-loan-agent-v1" },
);
console.log(loanAgent.id);loan_agent = client.agents.create(
workflow_id=loan_workflow.id,
name="Apex Loan Agent",
description="Handles client inquiries regarding rates, eligibility, and loan status.",
persona="professional",
idempotency_key="create-apex-loan-agent-v1",
)
print(loan_agent.id)3. Add a knowledge base (optional)
Knowledge bases are optional; this recipe uses one so the agent quotes rates and eligibility thresholds consistently.
await bimpe.agents.knowledgeBases.create(loanAgent.id, {
type: "text",
name: "2026 Loan Terms FAQ",
content:
"Personal Loan rates start at 5.5% APR. Minimum credit score required is 640. " +
"Mortgage down payments must be at least 3.5%. Maximum auto loan duration is 72 months.",
});client.agents.knowledge_bases.create(
loan_agent.id,
{
"type": "text",
"name": "2026 Loan Terms FAQ",
"content": (
"Personal Loan rates start at 5.5% APR. Minimum credit score required is 640. "
"Mortgage down payments must be at least 3.5%. Maximum auto loan duration is 72 months."
),
},
)4. Configure a custom API integration
Register your core banking backend and add a tool the agent can call to look up application status. See Configuring integrations for the full custom API surface.
const bankingApi = await bimpe.agents.integrations.customApi.configure(loanAgent.id, {
name: "Core Banking Dashboard",
base_url: process.env.BANKING_API_BASE_URL ?? "https://api.example.com/v1",
});
await bimpe.agents.integrations.customApi.tools.add(loanAgent.id, bankingApi.id, {
name: "Lookup Application Status",
http_method: "POST",
url_template: "/loans/status-lookup",
});banking_api = client.agents.integrations.custom_api.configure(
agent_id=loan_agent.id,
name="Core Banking Dashboard",
base_url=os.environ.get("BANKING_API_BASE_URL", "https://api.example.com/v1"),
)
client.agents.integrations.custom_api.tools.add(
loan_agent.id,
banking_api.id,
{
"name": "Lookup Application Status",
"http_method": "POST",
"url_template": "/loans/status-lookup",
},
)5. Connect channels (dashboard)
Channels are connected in the dashboard
The API cannot create channel connections. Connect Web Chat and Telephony on the Deploy screen of the Console dashboard. See Deploying and testing channels for the full reference.
Connect Web Chat
Embed the widget on your loan product pages so visitors can ask about rates and eligibility without leaving the site.
- Open Deploy and select your agent.
- Under Messaging & Chat, click Connect on the Web Chat Widget card.
- Copy the embed snippet and paste it into your site's HTML before the closing body tag.
- Publish your site — the chat bubble appears in the corner and routes messages to this agent.
Full reference: Deploying and testing channels.
Connect Telephony (outbound)
Link an outbound-capable number so your backend can call applicants for follow-ups and status updates.
- Open Deploy and click Set up on the Telephony card.
- Under Team settings → Phone numbers, request or link an outbound-capable number and assign it to this agent.
- Under Settings → Voice, set the outbound voice and greeting.
- Place calls with
calls.makeandis_test_call: false, or trigger calls from your backend when events fire.
Full reference: Deploying and testing channels.
Alternatively, provision and link numbers programmatically in Step 8.
6. Go live
When configuration is complete, switch the agent from development to live.
await bimpe.agents.updateLiveStatus(loanAgent.id, {
status: "live",
status_reason: "Deploying finalized loan desks helper agent to consumers.",
});client.agents.update_live_status(
agent_id=loan_agent.id,
status="live",
status_reason="Deploying finalized loan desks helper agent to consumers.",
)7. Test on webchat
Seed or find a conversation by channel identity with conversations.send. Use a stable channel_user_id per end-user session. For sandbox testing without a live widget, pass is_test_channel: true — see Deploying and testing channels.
Test Web Chat
Playground: open Playground → Chat in the dashboard for a quick sanity check.
SDK injection: call conversations.send with channel_type: "webchat" and a stable channel_user_id, or set is_test_channel: true before go-live.
Full reference: Deploying and testing channels.
import { randomUUID } from "node:crypto";
const channelUserId = randomUUID();
const reply = await bimpe.conversations.send(loanAgent.id, {
message: "tell me about you?",
channel_type: "webchat",
channel_user_id: channelUserId,
});
console.log(reply.message);import uuid
channel_user_id = str(uuid.uuid4())
reply = client.conversations.send(
agent_id=loan_agent.id,
message="tell me about you?",
channel_type="webchat",
channel_user_id=channel_user_id,
)
print(reply.message)8. Provision a phone number and place an outbound call
Test Telephony (outbound)
Place a test call before linking a live number. Use is_test_call: true until provisioning completes.
Test call: use calls.make(agentId, { destination, is_test_call: true }) — test telephony consumes no live minutes.
Confirm channels.telephony.is_enabled from getTestCode before placing live calls.
Full reference: Deploying and testing channels.
Request a team phone number if none is pending_review, link an approved number to the agent, then call out. When no approved number exists yet, set is_test_call: true so the call runs on BimpeAI test telephony.
If phoneNumbers.requests.list() or phoneNumbers.list() raises ValidationError because pending requests lack an e164 field, parse the raw error body and continue — the snippets below show the fallback.
import { ValidationError } from "@bimpeai/sdk";
const destination = process.env.BIMPE_DESTINATION_NUMBER ?? "+15551234567";
let hasPendingRequest = false;
let requestsList: Array<Record<string, unknown>> = [];
try {
const requestsPage = await bimpe.phoneNumbers.requests.list();
for (const req of requestsPage.data) {
if (req.status === "pending_review") hasPendingRequest = true;
}
} catch (error) {
if (error instanceof ValidationError && error.body) {
const parsed =
typeof error.body === "string" ? JSON.parse(error.body) : (error.body as { data?: unknown[] });
requestsList = (parsed.data ?? []) as Array<Record<string, unknown>>;
hasPendingRequest = requestsList.some((r) => r.status === "pending_review");
} else {
throw error;
}
}
if (!hasPendingRequest) {
await bimpe.phoneNumbers.requests.create({
business_name: "Apex Bank Loan Desk",
intended_use: "Handling customer loan inquiries via inbound and outbound lines",
region: "uk",
agent_count: 1,
outbound_minutes: 1000,
});
}
let phoneNumberId: string | undefined;
try {
const numbers = await bimpe.phoneNumbers.list();
phoneNumberId = numbers.data[0]?.id;
} catch (error) {
if (error instanceof ValidationError) {
const hit = requestsList.find((r) => r.e164);
phoneNumberId = hit?.id as string | undefined;
} else {
throw error;
}
}
const isTest = !phoneNumberId;
if (phoneNumberId) {
await bimpe.phoneNumbers.update(phoneNumberId, {
agent_id: loanAgent.id,
label: "Loan Telephony Desk Line",
});
}
const call = await bimpe.calls.make(loanAgent.id, {
destination,
is_test_call: isTest,
});
console.log(call.status, call.call_id);import json
from bimpeai import ValidationError
destination = os.environ.get("BIMPE_DESTINATION_NUMBER", "+15551234567")
has_pending_request = False
requests_list = []
try:
requests_page = client.phone_numbers.requests.list()
requests_list = requests_page.data
for req in requests_list:
if getattr(req, "status", None) == "pending_review":
has_pending_request = True
break
except ValidationError as err:
if err.raw_body:
raw = err.raw_body.decode("utf-8") if isinstance(err.raw_body, bytes) else err.raw_body
requests_list = json.loads(raw).get("data", [])
has_pending_request = any(
isinstance(r, dict) and r.get("status") == "pending_review" for r in requests_list
)
if not has_pending_request:
client.phone_numbers.requests.create(
business_name="Apex Bank Loan Desk",
intended_use="Handling customer loan inquiries via inbound and outbound lines",
region="uk",
agent_count=1,
outbound_minutes=1000,
)
phone_number_id = None
try:
numbers_page = client.phone_numbers.list()
if numbers_page.data:
phone_number_id = numbers_page.data[0].id
except ValidationError:
for req in requests_list:
if isinstance(req, dict) and req.get("e164"):
phone_number_id = req.get("id")
break
is_test = phone_number_id is None
if phone_number_id:
client.phone_numbers.update(
phone_number_id,
agent_id=loan_agent.id,
label="Loan Telephony Desk Line",
)
call = client.calls.make(
agent_id=loan_agent.id,
body={"destination": destination, "is_test_call": is_test},
)
print(call.status, getattr(call, "call_id", None))Full example
The complete end-to-end scripts are in Runnable scripts below. This condensed version covers the core lifecycle without telephony error handling.
import { BimpeAI } from "@bimpeai/sdk";
const bimpe = new BimpeAI({ apiKey: process.env.BIMPEAI_API_KEY! });
const workflow = await bimpe.workflows.create({
name: "Loan Inquiry Rules",
system_prompt:
"You are a professional banking virtual assistant specialized in loans. " +
"Your job is to gather initial information from customers regarding loan types, " +
"explain basic interest rates, check eligibility rules, and assist with application status.",
});
const agent = await bimpe.agents.create(
{
name: "Apex Loan Agent",
description: "Handles client inquiries regarding rates, eligibility, and loan status.",
workflow_id: workflow.id,
persona: "professional",
},
{ idempotencyKey: "create-apex-loan-agent-v1" },
);
await bimpe.agents.knowledgeBases.create(agent.id, {
type: "text",
name: "2026 Loan Terms FAQ",
content:
"Personal Loan rates start at 5.5% APR. Minimum credit score required is 640. " +
"Mortgage down payments must be at least 3.5%. Maximum auto loan duration is 72 months.",
});
const api = await bimpe.agents.integrations.customApi.configure(agent.id, {
name: "Core Banking Dashboard",
base_url: "https://api.example.com/v1",
});
await bimpe.agents.integrations.customApi.tools.add(agent.id, api.id, {
name: "Lookup Application Status",
http_method: "POST",
url_template: "/loans/status-lookup",
});
await bimpe.agents.updateLiveStatus(agent.id, { status: "live" });
const reply = await bimpe.conversations.send(agent.id, {
message: "What are your personal loan rates?",
channel_type: "webchat",
channel_user_id: "demo-user-001",
});
console.log("Agent ID:", agent.id, "Reply:", reply.message);import os
from bimpeai import BimpeAI
client = BimpeAI(api_key=os.environ["BIMPEAI_API_KEY"])
workflow = client.workflows.create(
name="Loan Inquiry Rules",
system_prompt=(
"You are a professional banking virtual assistant specialized in loans. "
"Your job is to gather initial information from customers regarding loan types, "
"explain basic interest rates, check eligibility rules, and assist with application status."
),
)
agent = client.agents.create(
workflow_id=workflow.id,
name="Apex Loan Agent",
description="Handles client inquiries regarding rates, eligibility, and loan status.",
persona="professional",
idempotency_key="create-apex-loan-agent-v1",
)
client.agents.knowledge_bases.create(agent.id, {
"type": "text",
"name": "2026 Loan Terms FAQ",
"content": (
"Personal Loan rates start at 5.5% APR. Minimum credit score required is 640. "
"Mortgage down payments must be at least 3.5%. Maximum auto loan duration is 72 months."
),
})
api = client.agents.integrations.custom_api.configure(
agent_id=agent.id,
name="Core Banking Dashboard",
base_url="https://api.example.com/v1",
)
client.agents.integrations.custom_api.tools.add(
agent.id,
api.id,
{"name": "Lookup Application Status", "http_method": "POST", "url_template": "/loans/status-lookup"},
)
client.agents.update_live_status(agent_id=agent.id, status="live")
reply = client.conversations.send(
agent_id=agent.id,
message="What are your personal loan rates?",
channel_type="webchat",
channel_user_id="demo-user-001",
)
print("Agent ID:", agent.id, "Reply:", reply.message)Runnable scripts
Full commented scripts with telephony provisioning and ValidationError fallbacks:
#!/usr/bin/env python3
"""
BimpeAI SDK — Loan Inquiry Agent (end-to-end)
Install:
pip install bimpeai
Run:
BIMPEAI_API_KEY=sk_... python notebooks/examples/bimpeai_sdk_usecase.py
Environment variables:
BIMPEAI_API_KEY (required) Your team API key (sk_...)
BIMPE_DESTINATION_NUMBER (optional) E.164 number for outbound call demo
BANKING_API_BASE_URL (optional) Base URL for the custom API integration
"""
from __future__ import annotations
import json
import os
import uuid
from bimpeai import BimpeAI, ValidationError
# ---------------------------------------------------------------------------
# Step 1: Installation & Setup
# Initialize the synchronous client. The SDK does not read the API key from
# the environment automatically — pass it explicitly when constructing BimpeAI.
# ---------------------------------------------------------------------------
client = BimpeAI(api_key=os.environ["BIMPEAI_API_KEY"])
BANKING_API_BASE_URL = os.environ.get("BANKING_API_BASE_URL", "https://api.example.com/v1")
DESTINATION_NUMBER = os.environ.get("BIMPE_DESTINATION_NUMBER", "+15551234567")
def main() -> None:
# -----------------------------------------------------------------------
# Step 2: Create the Loan Agent Workflow
# A workflow encodes conversation logic and the system prompt. The agent
# binds to this workflow in the next step.
# -----------------------------------------------------------------------
loan_workflow = client.workflows.create(
name="Loan Inquiry Rules",
system_prompt=(
"You are a professional banking virtual assistant specialized in loans. "
"Your job is to gather initial information from customers regarding loan types "
"(e.g., mortgage, personal, auto), explain basic interest rates, check "
"eligibility rules, and assist them with their application status."
),
)
print(f"Workflow created with ID: {loan_workflow.id}")
# -----------------------------------------------------------------------
# Step 3: Provision and Configure the Agent
# workflow_id, name, and description are required on create.
# -----------------------------------------------------------------------
loan_agent = client.agents.create(
workflow_id=loan_workflow.id,
name="Apex Loan Agent",
description="Handles client inquiries regarding rates, eligibility, and loan status.",
persona="professional",
idempotency_key="create-apex-loan-agent-v1",
)
print(f"Agent provisioned with ID: {loan_agent.id}")
# -----------------------------------------------------------------------
# Step 4: Add a Knowledge Base (optional but recommended)
# Grounds the agent in static loan terms it must quote accurately.
# -----------------------------------------------------------------------
client.agents.knowledge_bases.create(
loan_agent.id,
{
"type": "text",
"name": "2026 Loan Terms FAQ",
"content": (
"Personal Loan rates start at 5.5% APR. Minimum credit score required is 640. "
"Mortgage down payments must be at least 3.5%. Maximum auto loan duration is 72 months."
),
},
)
print("Knowledge base attached.")
# -----------------------------------------------------------------------
# Step 5: Connect a Custom API Tool & Integration (optional)
# Register your core banking backend so the agent can look up application status.
# See: /docs/use-cases/configuring-integrations#custom-http-apis-customapi
# -----------------------------------------------------------------------
banking_api = client.agents.integrations.custom_api.configure(
agent_id=loan_agent.id,
name="Core Banking Dashboard",
base_url=BANKING_API_BASE_URL,
)
client.agents.integrations.custom_api.tools.add(
loan_agent.id,
banking_api.id,
{
"name": "Lookup Application Status",
"http_method": "POST",
"url_template": "/loans/status-lookup",
},
)
print("Custom API integration configured.")
# -----------------------------------------------------------------------
# Step 6: Transition to production — go live
# Move the agent from development to live once configuration is complete.
# -----------------------------------------------------------------------
client.agents.update_live_status(
agent_id=loan_agent.id,
status="live",
status_reason="Deploying finalized loan desks helper agent to consumers.",
)
print("Agent is officially live!")
# -----------------------------------------------------------------------
# Step 7: Start testing / interaction (webchat)
# conversations.send seeds or finds a conversation by channel identity.
# channel_user_id should be stable per end-user session.
# -----------------------------------------------------------------------
channel_user_id = str(uuid.uuid4())
print("\nStarting webchat conversation with BimpeAI Agent...")
print("Type your message and hit Enter. Type 'exit' or 'quit' to end the chat.\n")
initial_greet = client.conversations.send(
agent_id=loan_agent.id,
message="tell me about you?",
channel_type="webchat",
channel_user_id=channel_user_id,
)
print(f"Agent response: {initial_greet.message}")
# Optional: extend to a full interactive REPL in the terminal.
# while True:
# user_input = input("You: ").strip()
# if user_input.lower() in ("exit", "quit"):
# break
# reply = client.conversations.send(
# agent_id=loan_agent.id,
# message=user_input,
# channel_type="webchat",
# channel_user_id=channel_user_id,
# )
# print(f"Agent: {reply.message}")
# -----------------------------------------------------------------------
# Step 8: Link a phone number line and trigger a call
# Request a number if none is pending review, link it to the agent, then
# place an outbound call. Falls back to is_test_call=True when no approved
# number is available yet.
# -----------------------------------------------------------------------
agent_id = loan_agent.id
print("\nChecking for pending phone number requests...")
has_pending_request = False
requests_list: list = []
try:
requests_page = client.phone_numbers.requests.list()
requests_list = requests_page.data
for req in requests_list:
status = getattr(req, "status", None)
if status == "pending_review":
has_pending_request = True
break
except ValidationError as err:
# Fallback when Pydantic cannot parse pending requests missing e164.
if err.raw_body:
raw_json = err.raw_body.decode("utf-8") if isinstance(err.raw_body, bytes) else err.raw_body
data = json.loads(raw_json)
requests_list = data.get("data", [])
for req in requests_list:
if isinstance(req, dict) and req.get("status") == "pending_review":
has_pending_request = True
break
if not has_pending_request:
print("No pending review requests found. Requesting a new phone number...")
client.phone_numbers.requests.create(
business_name="Apex Bank Loan Desk",
intended_use="Handling customer loan inquiries via inbound and outbound lines",
region="uk", # "us" | "uk" | "eu" | "ng"
agent_count=1,
outbound_minutes=1000,
)
print("Provisioning request submitted successfully.")
else:
print("An active request is currently 'pending_review'. Skipping new submission.")
print("\nChecking if there is an approved phone number ready...")
phone_number_id = None
approved_number_e164 = None
try:
numbers_page = client.phone_numbers.list()
if numbers_page.data:
phone_number_id = numbers_page.data[0].id
approved_number_e164 = numbers_page.data[0].e164
except ValidationError:
for req in requests_list:
if isinstance(req, dict) and req.get("e164"):
phone_number_id = req.get("id")
approved_number_e164 = req.get("e164")
break
is_test = False
if phone_number_id:
print(f"Approved number found: {approved_number_e164}")
print(f"Linking phone line (ID: {phone_number_id}) to Agent (ID: {agent_id})...")
client.phone_numbers.update(
phone_number_id,
agent_id=agent_id,
label="Loan Telephony Desk Line",
)
print("Line successfully connected!")
else:
print("No fully approved phone numbers are active yet.")
print("Switching to test telephony mode.")
is_test = True
print(f"\nInitiating outbound call to {DESTINATION_NUMBER} (is_test_call={is_test})...")
call_result = client.calls.make(
agent_id=agent_id,
body={
"destination": DESTINATION_NUMBER,
"is_test_call": is_test,
},
)
print(f"Call dispatch status: {call_result.status}")
if getattr(call_result, "call_id", None):
print(f"Call tracking ID: {call_result.call_id}")
if __name__ == "__main__":
main()
/**
* BimpeAI SDK — Loan Inquiry Agent (end-to-end)
*
* Install:
* npm install @bimpeai/sdk
* npm install -D tsx # to run this file directly
*
* Run:
* BIMPEAI_API_KEY=sk_... npx tsx notebooks/examples/bimpeai_sdk_usecase.ts
*
* Environment variables:
* BIMPEAI_API_KEY (required) Your team API key (sk_...)
* BIMPE_DESTINATION_NUMBER (optional) E.164 number for outbound call demo
* BANKING_API_BASE_URL (optional) Base URL for the custom API integration
*/
import { randomUUID } from "node:crypto";
import { BimpeAI, ValidationError } from "@bimpeai/sdk";
const bimpe = new BimpeAI({ apiKey: process.env.BIMPEAI_API_KEY! });
const BANKING_API_BASE_URL = process.env.BANKING_API_BASE_URL ?? "https://api.example.com/v1";
const DESTINATION_NUMBER = process.env.BIMPE_DESTINATION_NUMBER ?? "+15551234567";
function parseValidationBody(body: unknown): { data?: Array<Record<string, unknown>> } | null {
if (body == null) return null;
if (typeof body === "string") {
try {
return JSON.parse(body) as { data?: Array<Record<string, unknown>> };
} catch {
return null;
}
}
if (typeof body === "object") {
return body as { data?: Array<Record<string, unknown>> };
}
return null;
}
async function main(): Promise<void> {
// -------------------------------------------------------------------------
// Step 2: Create the Loan Agent Workflow
// A workflow encodes conversation logic and the system prompt.
// -------------------------------------------------------------------------
const loanWorkflow = await bimpe.workflows.create({
name: "Loan Inquiry Rules",
system_prompt:
"You are a professional banking virtual assistant specialized in loans. " +
"Your job is to gather initial information from customers regarding loan types " +
"(e.g., mortgage, personal, auto), explain basic interest rates, check " +
"eligibility rules, and assist them with their application status.",
});
console.log(`Workflow created with ID: ${loanWorkflow.id}`);
// -------------------------------------------------------------------------
// Step 3: Provision and Configure the Agent
// -------------------------------------------------------------------------
const loanAgent = await bimpe.agents.create(
{
name: "Apex Loan Agent",
description: "Handles client inquiries regarding rates, eligibility, and loan status.",
workflow_id: loanWorkflow.id,
persona: "professional",
},
{ idempotencyKey: "create-apex-loan-agent-v1" },
);
console.log(`Agent provisioned with ID: ${loanAgent.id}`);
// -------------------------------------------------------------------------
// Step 4: Add a Knowledge Base (optional but recommended)
// -------------------------------------------------------------------------
await bimpe.agents.knowledgeBases.create(loanAgent.id, {
type: "text",
name: "2026 Loan Terms FAQ",
content:
"Personal Loan rates start at 5.5% APR. Minimum credit score required is 640. " +
"Mortgage down payments must be at least 3.5%. Maximum auto loan duration is 72 months.",
});
console.log("Knowledge base attached.");
// -------------------------------------------------------------------------
// Step 5: Connect a Custom API Tool & Integration (optional)
// -------------------------------------------------------------------------
const bankingApi = await bimpe.agents.integrations.customApi.configure(loanAgent.id, {
name: "Core Banking Dashboard",
base_url: BANKING_API_BASE_URL,
});
await bimpe.agents.integrations.customApi.tools.add(loanAgent.id, bankingApi.id, {
name: "Lookup Application Status",
http_method: "POST",
url_template: "/loans/status-lookup",
});
console.log("Custom API integration configured.");
// -------------------------------------------------------------------------
// Step 6: Transition to production — go live
// -------------------------------------------------------------------------
await bimpe.agents.updateLiveStatus(loanAgent.id, {
status: "live",
status_reason: "Deploying finalized loan desks helper agent to consumers.",
});
console.log("Agent is officially live!");
// -------------------------------------------------------------------------
// Step 7: Start testing / interaction (webchat)
// -------------------------------------------------------------------------
const channelUserId = randomUUID();
console.log("\nStarting webchat conversation with BimpeAI Agent...");
console.log("Type your message and hit Enter. Type 'exit' or 'quit' to end the chat.\n");
const initialGreet = await bimpe.conversations.send(loanAgent.id, {
message: "tell me about you?",
channel_type: "webchat",
channel_user_id: channelUserId,
});
console.log(`Agent response: ${initialGreet.message}`);
// Optional: extend to a full interactive REPL in the terminal.
// import * as readline from "node:readline/promises";
// const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
// while (true) {
// const userInput = (await rl.question("You: ")).trim();
// if (userInput.toLowerCase() === "exit" || userInput.toLowerCase() === "quit") break;
// const reply = await bimpe.conversations.send(loanAgent.id, {
// message: userInput,
// channel_type: "webchat",
// channel_user_id: channelUserId,
// });
// console.log(`Agent: ${reply.message}`);
// }
// rl.close();
// -------------------------------------------------------------------------
// Step 8: Link a phone number line and trigger a call
// -------------------------------------------------------------------------
const agentId = loanAgent.id;
console.log("\nChecking for pending phone number requests...");
let hasPendingRequest = false;
let requestsList: Array<Record<string, unknown>> = [];
try {
const requestsPage = await bimpe.phoneNumbers.requests.list();
requestsList = requestsPage.data as Array<Record<string, unknown>>;
for (const req of requestsPage.data) {
const status = (req as { status?: string }).status;
if (status === "pending_review") {
hasPendingRequest = true;
break;
}
}
} catch (error) {
if (error instanceof ValidationError) {
const parsed = parseValidationBody(error.body);
requestsList = parsed?.data ?? [];
for (const req of requestsList) {
if (req.status === "pending_review") {
hasPendingRequest = true;
break;
}
}
} else {
throw error;
}
}
if (!hasPendingRequest) {
console.log("No pending review requests found. Requesting a new phone number...");
await bimpe.phoneNumbers.requests.create({
business_name: "Apex Bank Loan Desk",
intended_use: "Handling customer loan inquiries via inbound and outbound lines",
region: "uk",
agent_count: 1,
outbound_minutes: 1000,
});
console.log("Provisioning request submitted successfully.");
} else {
console.log("An active request is currently 'pending_review'. Skipping new submission.");
}
console.log("\nChecking if there is an approved phone number ready...");
let phoneNumberId: string | undefined;
let approvedNumberE164: string | undefined;
try {
const numbersPage = await bimpe.phoneNumbers.list();
if (numbersPage.data[0]) {
phoneNumberId = numbersPage.data[0].id;
approvedNumberE164 = numbersPage.data[0].e164;
}
} catch (error) {
if (error instanceof ValidationError) {
for (const req of requestsList) {
if (req.e164) {
phoneNumberId = req.id as string;
approvedNumberE164 = req.e164 as string;
break;
}
}
} else {
throw error;
}
}
let isTest = false;
if (phoneNumberId) {
console.log(`Approved number found: ${approvedNumberE164}`);
console.log(`Linking phone line (ID: ${phoneNumberId}) to Agent (ID: ${agentId})...`);
await bimpe.phoneNumbers.update(phoneNumberId, {
agent_id: agentId,
label: "Loan Telephony Desk Line",
});
console.log("Line successfully connected!");
} else {
console.log("No fully approved phone numbers are active yet.");
console.log("Switching to test telephony mode.");
isTest = true;
}
console.log(`\nInitiating outbound call to ${DESTINATION_NUMBER} (is_test_call=${isTest})...`);
const callResult = await bimpe.calls.make(agentId, {
destination: DESTINATION_NUMBER,
is_test_call: isTest,
});
console.log(`Call dispatch status: ${callResult.status}`);
if (callResult.call_id) {
console.log(`Call tracking ID: ${callResult.call_id}`);
}
}
main().catch((error) => {
console.error(error);
process.exit(1);
});
/**
* BimpeAI SDK — Loan Inquiry Agent (end-to-end)
*
* Install:
* npm install @bimpeai/sdk
*
* Run:
* BIMPEAI_API_KEY=sk_... node notebooks/examples/bimpeai_sdk_usecase.mjs
* # or: node --env-file=.env notebooks/examples/bimpeai_sdk_usecase.mjs
*
* Environment variables:
* BIMPEAI_API_KEY (required) Your team API key (sk_...)
* BIMPE_DESTINATION_NUMBER (optional) E.164 number for outbound call demo
* BANKING_API_BASE_URL (optional) Base URL for the custom API integration
*/
import { randomUUID } from "node:crypto";
import { BimpeAI, ValidationError } from "@bimpeai/sdk";
const bimpe = new BimpeAI({ apiKey: process.env.BIMPEAI_API_KEY });
const BANKING_API_BASE_URL = process.env.BANKING_API_BASE_URL ?? "https://api.example.com/v1";
const DESTINATION_NUMBER = process.env.BIMPE_DESTINATION_NUMBER ?? "+15551234567";
function parseValidationBody(body) {
if (body == null) return null;
if (typeof body === "string") {
try {
return JSON.parse(body);
} catch {
return null;
}
}
if (typeof body === "object") {
return body;
}
return null;
}
async function main() {
// -------------------------------------------------------------------------
// Step 2: Create the Loan Agent Workflow
// A workflow encodes conversation logic and the system prompt.
// -------------------------------------------------------------------------
const loanWorkflow = await bimpe.workflows.create({
name: "Loan Inquiry Rules",
system_prompt:
"You are a professional banking virtual assistant specialized in loans. " +
"Your job is to gather initial information from customers regarding loan types " +
"(e.g., mortgage, personal, auto), explain basic interest rates, check " +
"eligibility rules, and assist them with their application status.",
});
console.log(`Workflow created with ID: ${loanWorkflow.id}`);
// -------------------------------------------------------------------------
// Step 3: Provision and Configure the Agent
// -------------------------------------------------------------------------
const loanAgent = await bimpe.agents.create(
{
name: "Apex Loan Agent",
description: "Handles client inquiries regarding rates, eligibility, and loan status.",
workflow_id: loanWorkflow.id,
persona: "professional",
},
{ idempotencyKey: "create-apex-loan-agent-v1" },
);
console.log(`Agent provisioned with ID: ${loanAgent.id}`);
// -------------------------------------------------------------------------
// Step 4: Add a Knowledge Base (optional but recommended)
// -------------------------------------------------------------------------
await bimpe.agents.knowledgeBases.create(loanAgent.id, {
type: "text",
name: "2026 Loan Terms FAQ",
content:
"Personal Loan rates start at 5.5% APR. Minimum credit score required is 640. " +
"Mortgage down payments must be at least 3.5%. Maximum auto loan duration is 72 months.",
});
console.log("Knowledge base attached.");
// -------------------------------------------------------------------------
// Step 5: Connect a Custom API Tool & Integration (optional)
// -------------------------------------------------------------------------
const bankingApi = await bimpe.agents.integrations.customApi.configure(loanAgent.id, {
name: "Core Banking Dashboard",
base_url: BANKING_API_BASE_URL,
});
await bimpe.agents.integrations.customApi.tools.add(loanAgent.id, bankingApi.id, {
name: "Lookup Application Status",
http_method: "POST",
url_template: "/loans/status-lookup",
});
console.log("Custom API integration configured.");
// -------------------------------------------------------------------------
// Step 6: Transition to production — go live
// -------------------------------------------------------------------------
await bimpe.agents.updateLiveStatus(loanAgent.id, {
status: "live",
status_reason: "Deploying finalized loan desks helper agent to consumers.",
});
console.log("Agent is officially live!");
// -------------------------------------------------------------------------
// Step 7: Start testing / interaction (webchat)
// -------------------------------------------------------------------------
const channelUserId = randomUUID();
console.log("\nStarting webchat conversation with BimpeAI Agent...");
console.log("Type your message and hit Enter. Type 'exit' or 'quit' to end the chat.\n");
const initialGreet = await bimpe.conversations.send(loanAgent.id, {
message: "tell me about you?",
channel_type: "webchat",
channel_user_id: channelUserId,
});
console.log(`Agent response: ${initialGreet.message}`);
// Optional: extend to a full interactive REPL in the terminal.
// import * as readline from "node:readline/promises";
// const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
// while (true) {
// const userInput = (await rl.question("You: ")).trim();
// if (userInput.toLowerCase() === "exit" || userInput.toLowerCase() === "quit") break;
// const reply = await bimpe.conversations.send(loanAgent.id, {
// message: userInput,
// channel_type: "webchat",
// channel_user_id: channelUserId,
// });
// console.log(`Agent: ${reply.message}`);
// }
// rl.close();
// -------------------------------------------------------------------------
// Step 8: Link a phone number line and trigger a call
// -------------------------------------------------------------------------
const agentId = loanAgent.id;
console.log("\nChecking for pending phone number requests...");
let hasPendingRequest = false;
let requestsList = [];
try {
const requestsPage = await bimpe.phoneNumbers.requests.list();
requestsList = requestsPage.data;
for (const req of requestsPage.data) {
if (req.status === "pending_review") {
hasPendingRequest = true;
break;
}
}
} catch (error) {
if (error instanceof ValidationError) {
const parsed = parseValidationBody(error.body);
requestsList = parsed?.data ?? [];
for (const req of requestsList) {
if (req.status === "pending_review") {
hasPendingRequest = true;
break;
}
}
} else {
throw error;
}
}
if (!hasPendingRequest) {
console.log("No pending review requests found. Requesting a new phone number...");
await bimpe.phoneNumbers.requests.create({
business_name: "Apex Bank Loan Desk",
intended_use: "Handling customer loan inquiries via inbound and outbound lines",
region: "uk",
agent_count: 1,
outbound_minutes: 1000,
});
console.log("Provisioning request submitted successfully.");
} else {
console.log("An active request is currently 'pending_review'. Skipping new submission.");
}
console.log("\nChecking if there is an approved phone number ready...");
let phoneNumberId;
let approvedNumberE164;
try {
const numbersPage = await bimpe.phoneNumbers.list();
if (numbersPage.data[0]) {
phoneNumberId = numbersPage.data[0].id;
approvedNumberE164 = numbersPage.data[0].e164;
}
} catch (error) {
if (error instanceof ValidationError) {
for (const req of requestsList) {
if (req.e164) {
phoneNumberId = req.id;
approvedNumberE164 = req.e164;
break;
}
}
} else {
throw error;
}
}
let isTest = false;
if (phoneNumberId) {
console.log(`Approved number found: ${approvedNumberE164}`);
console.log(`Linking phone line (ID: ${phoneNumberId}) to Agent (ID: ${agentId})...`);
await bimpe.phoneNumbers.update(phoneNumberId, {
agent_id: agentId,
label: "Loan Telephony Desk Line",
});
console.log("Line successfully connected!");
} else {
console.log("No fully approved phone numbers are active yet.");
console.log("Switching to test telephony mode.");
isTest = true;
}
console.log(`\nInitiating outbound call to ${DESTINATION_NUMBER} (is_test_call=${isTest})...`);
const callResult = await bimpe.calls.make(agentId, {
destination: DESTINATION_NUMBER,
is_test_call: isTest,
});
console.log(`Call dispatch status: ${callResult.status}`);
if (callResult.call_id) {
console.log(`Call tracking ID: ${callResult.call_id}`);
}
}
main().catch((error) => {
console.error(error);
process.exit(1);
});
The source Colab notebook is on GitHub.
Deploy and go live
Go-live checklist
- Store your API key in
BIMPEAI_API_KEY(server-side only). - Confirm Core banking custom API appears in
integrations.listor is connected in the dashboard. - Verify Web Chat is connected on the Deploy screen (
agents.channels.listshows it enabled). - Verify Telephony (outbound) is connected on the Deploy screen (
agents.channels.listshows it enabled). - Set Escalation Email under Settings → Agent if humans must take over.
- Switch the agent to
livewithupdateLiveStatus/update_live_status. - Monitor the Conversations screen (or stream via SDK) after launch.
- Set BANKING_API_BASE_URL to a reachable core banking API before go-live.
- Set BIMPE_DESTINATION_NUMBER (E.164) for outbound call demos.
- Use is_test_call: true on calls.make until an approved number is linked.
After go-live
Phone number provisioning is fulfilled by BimpeAI and may sit in pending_review for a period. Update the loan terms knowledge base via knowledgeBases.update when rates change. Monitor webchat and telephony conversations with conversations.list filtered by channel.
Variations
- Add separate knowledge base entries per loan product (mortgage, auto, personal) so the agent cites product-specific terms.
- Connect Paystack or Stripe if the agent should collect application fees or pre-qualification deposits.
- Flip to inbound telephony for a loan call-center line — link an approved number to the agent and route customer calls without
calls.make. - Use
conversations.sendwithis_test_channel: trueonwebchatortelephonyduring development before switchingupdateLiveStatustolive.
Hotel Reservation & Concierge
Let guests check room availability, make reservations, ask about amenities, and get local recommendations — across WhatsApp and web chat.
Lost or Stolen Card Support
Answer inbound calls from customers who have lost their card or had it stolen, capture the details, initiate a block, log the incident, and confirm a replacement is on the way.