How to Build a KYC verification Agent Using LlamaIndex in TypeScript for fintech

By Cyprian AaronsUpdated 2026-04-21
kyc-verificationllamaindextypescriptfintech

A KYC verification agent automates the boring but high-stakes parts of customer due diligence: collecting identity data, checking it against policy, pulling evidence from documents, and producing a decision trail an analyst can review. For fintech, this matters because onboarding speed, compliance quality, and auditability all have to coexist in the same workflow.

Architecture

  • Document ingestion layer

    • Accepts passports, utility bills, incorporation docs, and sanctions-screening outputs.
    • Normalizes PDFs, images, and text into a searchable knowledge base.
  • LlamaIndex retrieval layer

    • Uses VectorStoreIndex to index policy documents, KYC playbooks, and case history.
    • Uses QueryEngine or RetrieverQueryEngine to answer verification questions with citations.
  • Policy reasoning layer

    • Encodes rules like “proof of address must be under 90 days old” or “UBO information required for corporate accounts.”
    • Produces structured decisions: approve, reject, or escalate.
  • Human review layer

    • Routes ambiguous cases to compliance analysts.
    • Captures reviewer notes and final disposition for audit.
  • Audit and logging layer

    • Stores every retrieval result, prompt, model response, and decision.
    • Supports regulator requests and internal model governance.
  • Data controls layer

    • Enforces PII redaction, tenant isolation, and data residency constraints.
    • Prevents raw sensitive data from being sent where it should not go.

Implementation

1) Install the TypeScript packages

Use the TypeScript SDK and a real LLM provider. This example uses OpenAI through LlamaIndex.

npm install llamaindex zod dotenv

Set your environment variables:

export OPENAI_API_KEY="your-key"

2) Load KYC policy docs into a vector index

This is where you put your internal KYC handbook, escalation rules, jurisdiction-specific requirements, and onboarding checklists. The agent will retrieve from this corpus instead of guessing.

import "dotenv/config";
import {
  Document,
  Settings,
  VectorStoreIndex,
} from "llamaindex";
import { OpenAI } from "llamaindex/llm";

Settings.llm = new OpenAI({
  model: "gpt-4o-mini",
});

async function buildIndex() {
  const docs = [
    new Document({
      text: `
        KYC Policy:
        - Individual customers require government ID and proof of address.
        - Proof of address must be issued within the last 90 days.
        - Corporate customers require incorporation documents and beneficial ownership details.
        - High-risk jurisdictions require enhanced due diligence.
      `,
      metadata: { source: "kyc-policy-v1" },
    }),
    new Document({
      text: `
        Escalation Rules:
        - Missing proof of address => manual review.
        - Mismatch between name on ID and application => reject or escalate.
        - Sanctions hit => immediate hold pending compliance review.
      `,
      metadata: { source: "escalation-playbook-v2" },
    }),
  ];

  const index = await VectorStoreIndex.fromDocuments(docs);
  return index;
}

3) Query the index with a KYC verification prompt

This pattern turns policy retrieval into an explainable verification step. The agent returns an answer grounded in your own controls instead of free-form model output.

async function verifyKycCase() {
  const index = await buildIndex();
  const queryEngine = index.asQueryEngine();

  const result = await queryEngine.query({
    query: `
      Customer profile:
      - Individual
      - Submitted passport
      - Submitted utility bill dated 120 days ago
      Question: Does this pass KYC according to policy? Explain briefly.
    `,
  });

  console.log(String(result));
}

verifyKycCase().catch(console.error);

4) Wrap the response in a decision object for downstream systems

In production you do not want raw prose driving onboarding. You want a structured decision that your workflow engine can consume.

import { z } from "zod";

const KycDecisionSchema = z.object({
  decision: z.enum(["approve", "reject", "manual_review"]),
  reason: z.string(),
});

async function decideKycCase() {
  const index = await buildIndex();
  const queryEngine = index.asQueryEngine();

  const result = await queryEngine.query({
    query: `
      Return only one of:
      approve | reject | manual_review

      Policy context:
      proof of address must be under 90 days old

      Case:
      utility bill is dated 120 days ago
    `,
  });

  const rawText = String(result).toLowerCase();

  const parsed =
    rawText.includes("manual") ? { decision: "manual_review", reason: rawText } :
    rawText.includes("reject") ? { decision: "reject", reason: rawText } :
    { decision: "approve", reason: rawText };

  const decision = KycDecisionSchema.parse(parsed);
  console.log(decision);
}

decideKycCase().catch(console.error);

Production Considerations

  • Keep PII out of prompts where possible

    • Redact passport numbers, account numbers, and full addresses before indexing or querying.
    • Store original documents in a controlled system; pass only minimal fields into the agent.
  • Log every decision path

    • Persist retrieved chunks, prompt text, model version, timestamps, and final disposition.
    • Regulators will ask why a case was approved or escalated. You need an answer trail.
  • Enforce region-aware storage

    • Keep EU customer data in EU-hosted infrastructure if your regulatory scope requires it.
    • Make sure vector stores, logs, backups, and observability pipelines follow the same residency rules.
  • Add deterministic guardrails around the model

    • Use hard rules for sanctions hits, age thresholds, expired documents, and missing mandatory fields.
    • Let LlamaIndex assist with retrieval and explanation; do not let it override compliance policy.

Common Pitfalls

  • Using the model as the final authority

    The agent should recommend a decision based on policy. Final approval logic should still live in deterministic code or compliance review workflows.

  • Indexing raw sensitive documents without controls

    If you dump full identity documents into an unrestricted vector store, you create a data protection problem fast. Redact first, isolate tenants, encrypt at rest, and restrict access by role.

  • Letting prompts drift away from policy

    If your prompt says one thing and your compliance handbook says another, the system becomes untrustworthy. Version your policy docs and tie each decision to a specific document revision.

  • Skipping human escalation paths

    Edge cases happen constantly in fintech: poor image quality, transliterated names, foreign addresses, corporate ownership chains. Build manual_review into the flow from day one instead of treating it as an exception later.


Keep learning

By Cyprian Aarons, AI Consultant at Topiax.

Want the complete 8-step roadmap?

Grab the free AI Agent Starter Kit — architecture templates, compliance checklists, and a 7-email deep-dive course.

Get the Starter Kit

Related Guides