Small finance teams using NetSuite struggle to run custom financial models and what-if analyses because they lack safe, isolated execution environments and must manually pull data from NetSuite, often resorting to error-prone spreadsheet macros.
A complete, working implementation of this recipe — downloadable as a zip or browsable file by file. Generated by our build pipeline; tested with full coverage before publishing.
This recipe builds a Hono-hosted API that turns plain-English financial questions into safe, budget-controlled Python scripts executed against live NetSuite data. You type “show me Q3 revenue by region,” and the system generates a Databricks LLM-powered Python script, repairs syntax issues, classifies it for safety, runs it in an E2B sandbox against your NetSuite records, and returns the result — all while tracking costs through a budget engine.
This tutorial walks you through every file in the project, from Zod schemas to the Hono server bootstrap. By the end you’ll have a working API that you can deploy as a standalone Node.js server.
Prerequisites
Node.js >= 22 with pnpm@10 installed
A Databricks workspace with model serving enabled (the recipe uses the databricks-dbrx endpoint)
A NetSuite base URL in the form https://{accountId}.suitetalk.api.netsuite.com
Familiarity with TypeScript, basic REST API concepts, and Hono
Step 1: Scaffold the project and install dependencies
The scaffold agent has already set up a Next.js 16+ project with Hono for API serving and all dependencies pinned to exact versions. Open package.json and verify the key packages:
Expected output: No errors. pnpm-lock.yaml is created or updated.
Step 2: Set environment variables
Open .env.example — it contains a header comment followed by every variable the application reads at startup:
env
# Env vars required by the recipe. Keep placeholders only.NODE_ENV=developmentDATABRICKS_HOST=<https://your-workspace.cloud.databricks.com>DATABRICKS_TOKEN=<dapi-your-databricks-token>E2B_API_KEY=<e2b-your-api-key>NETSUITE_ACCOUNT_ID=<your-netsuite-account-id>NETSUITE_CONSUMER_KEY=<your-consumer-key>NETSUITE_CONSUMER_SECRET=<your-consumer-secret>NETSUITE_TOKEN_ID=<your-token-id>NETSUITE_TOKEN_SECRET=<your-token-secret>NETSUITE_BASE_URL=<https://{accountId}.suitetalk.api.netsuite.com>PORT=3001
Copy it to .env and fill in your real credentials:
terminal
cp .env.example .env
The PORT defaults to 3001 if unset. The NODE_ENV check prevents the server bootstrap from running inside the test suite.
Step 3: Define the request and response types with Zod
Create src/types/analysis.ts. This file defines the shapes that flow through the API — the incoming AnalysisRequest, the outgoing AnalysisResponse, the FinancialModelConfig, and a Zod schema for request validation:
Expected output: A valid TypeScript module with no type errors. The z.string().min(1) ensures neither field can be empty.
Step 4: Define NetSuite-specific types and configuration schema
Create src/types/netsuite.ts. This file models the data you pull from NetSuite: saved search queries, records, and the connection configuration validated by Zod:
Expected output: A clean type module. The schema is used later in the orchestrator to validate that all six NetSuite env vars are set before making any API calls.
Step 5: Build the NetSuite REST client with OAuth 1.0
Create src/lib/netsuite.ts. This is the most complex service — it implements OAuth 1.0 signing, retries on throttling, and caps concurrent requests:
ts
import pRetry from "p-retry";import pLimit from "p-limit";import { createHmac, randomUUID } from "node:crypto";import type { NetSuiteConfig, NetSuiteRecord } from "../types/netsuite.js";export class NetSuiteError extends Error { statusCode: number; code: string; constructor(message: string, statusCode: number, code: string) { super(message); this.statusCode = statusCode; this.code = code; this.name = "NetSuiteError"
Expected output: A typed HTTP client that signs requests with OAuth 1.0 HMAC-SHA1. The buildOAuthHeader method generates the seven required OAuth parameters. request() retries up to 3 times on 429 and 503 errors. All outbound calls go through a pLimit(4) concurrency limiter to avoid overwhelming the NetSuite API.
Step 6: Create the Databricks LLM service
Create src/lib/databricks.ts. This service calls the Databricks Foundation Model API (OpenAI-compatible) to generate a Python financial modeling script from a natural-language query:
Expected output: A service that constructs a prompt with up to 50 NetSuite records as context, sends it to the Databricks serving endpoint, and returns the generated code plus token and cost data. The temperature: 0.2 keeps the output deterministic. calculateCostFromTokens converts the total token count at $30 per million tokens.
Step 7: Add code repair with structured-repair-core
Create src/lib/repair.ts. LLMs don’t always return clean JSON — they embed it in markdown fences, add trailing commas, or wrap it in conversational prose. @reaatech/structured-repair-core handles all of that:
Expected output: Two functions. repairGeneratedCode first tries repair() to extract a valid { code, imports, description } object from the raw LLM output. If that fails, it calls repairOutput() for detailed error messages. validateGeneratedCode performs a heuristic safety scan — it checks for 12 dangerous patterns like shell execution, filesystem destruction, and dynamic code evaluation.
Step 8: Set up script safety classification with confidence-router
Create src/lib/confidence.ts. This uses @reaatech/confidence-router to classify the repaired script as safe or unsafe based on keyword analysis:
Expected output: A router configured with routeThreshold: 0.85 and fallbackThreshold: 0.2. Scripts heavy on safety keywords (pandas, DataFrame, describe) score high and are routed through. Scripts containing shell or destructive patterns score low and are blocked. The clarificationEnabled: false setting means ambiguous scripts are blocked rather than deferred.
Step 9: Integrate the budget engine
Create src/lib/budget.ts. This ties together @reaatech/agent-budget-engine with its companion packages @reaatech/agent-budget-spend-tracker and @reaatech/agent-budget-types:
Expected output: Five budget functions. createBudgetController() wires a SpendStore into a BudgetController and subscribes to the hard-stop event. defineTenantBudget sets a daily limit with an 80% soft cap and 100% hard cap. checkBudget runs a pre-flight check before each LLM call. recordSpend logs the cost after execution. getBudgetState retrieves current spend and budget state.
Step 10: Set up cost telemetry
Create src/lib/telemetry.ts. This uses @reaatech/llm-cost-telemetry to create structured cost spans for every LLM invocation:
Expected output: A telemetry module that generates a unique span ID via generateId(), computes the dollar cost at $30/1M tokens, and logs every span as structured JSON. The trackLlmCall convenience function combines create + record in one call.
Step 11: Build the E2B sandbox execution service
Create src/lib/sandbox.ts. This wraps @e2b/code-interpreter to run Python scripts in an isolated sandbox with a 30-second timeout:
Expected output: A sandbox service that serializes the NetSuite records into a Python netsuite_records variable, then executes the script in an E2B sandbox. If the script takes longer than 30 seconds, the Promise.race with setTimeout rejects with SandboxExecutionTimeout. The sandbox is always killed in the finally block.
Step 12: Wire the orchestrator — the main analysis pipeline
Create src/api/analyze.ts. This is the central orchestration function that connects every service in a 12-step pipeline:
ts
import { generateId } from "@reaatech/llm-cost-telemetry";import { NetSuiteClient, NetSuiteError } from "../lib/netsuite.js";import { DatabricksService, LlmError } from "../lib/databricks.js";import { repairGeneratedCode } from "../lib/repair.js";import { SandboxService, SandboxError } from "../lib/sandbox.js";import { createBudgetController, defineTenantBudget, checkBudget, recordSpend,} from "../lib/budget.js";import { createScriptRouter, classifyScript } from "../lib/confidence.js";import { trackLlmCall } from "../lib/telemetry.js";import { NetSuiteConfigSchema } from "../types/netsuite.js";
Expected output: The complete pipeline executes in this order:
Parse and validate the request with Zod
Initialize the budget controller and define a $10 daily tenant budget
Run a pre-flight budget check (estimated $0.50)
Validate NetSuite env vars against NetSuiteConfigSchema
Fetch live NetSuite transaction records with the search query
Generate Python code from Databricks LLM with the records as context
Repair the generated code with @reaatech/structured-repair-core
Classify script safety with @reaatech/confidence-router
Execute the script in an E2B sandbox
Record the spend in the budget engine
Track the LLM cost telemetry
Return the result, generated script, execution ID, and cost
Every step has its own try/catch with domain-specific error messages. Spend recording and telemetry failures are non-fatal — they don’t block the response.
Step 13: Wire the Hono API server
Create src/index.ts. This Hono server exposes the analyze endpoint, a health check, and global error handling:
Expected output: A Hono app with four routes. POST /api/analyze handles JSON parsing, Zod validation, delegates to handleAnalyzeRequest, and maps success/error to 200/400 status codes. GET /api/health returns { status: "ok" }. A global onError catches any unhandled exceptions and returns a 500 with the error message. The notFound handler returns a structured 404. The server bootstrap only starts when NODE_ENV is not "test".
Step 14: Run the checks and review the project layout
At this point your project should look like this:
code
src/
types/
analysis.ts AnalysisRequest, AnalysisResponse, and Zod schema
netsuite.ts NetSuite types and config schema
lib/
netsuite.ts OAuth 1.0 NetSuite REST client
databricks.ts Databricks LLM service
repair.ts Code repair with structured-repair-core
confidence.ts Script safety classifier
budget.ts Budget engine integration
telemetry.ts LLM cost telemetry
sandbox.ts E2B sandbox execution service
api/
analyze.ts Main analysis orchestrator
index.ts Hono API server
tests/
setup.ts MSW test server setup
handlers.ts HTTP mock handlers
helpers.ts Test data factories
netsuite.test.ts NetSuite client tests
databricks.test.ts Databricks service tests
repair.test.ts Code repair tests
confidence.test.ts Confidence router tests
budget.test.ts Budget engine tests
telemetry.test.ts Telemetry tests
sandbox.test.ts Sandbox execution tests
analyze.test.ts Orchestrator integration tests
server.test.ts Hono server route tests
Now run the full validation suite:
terminal
pnpm typecheck
All TypeScript files should compile with zero errors.
terminal
pnpm lint
ESLint should pass with no violations. The ESLint config bans @ts-ignore, @ts-expect-error, eslint-disable, and : any.
terminal
pnpm test
Expected output: All tests pass, coverage is at least 90% on lines, branches, functions, and statements. The test suite covers every service: NetSuite client OAuth signing and retries, Databricks LLM prompt construction, code repair, confidence classification, budget enforcement, telemetry, sandbox execution, the orchestrator pipeline, and the Hono server routes.
terminal
curl -X POST http://localhost:3001/api/analyze \ -H "Content-Type: application/json" \ -d '{"query": "show me Q3 revenue by region", "tenantId": "acme-corp"}'
Expected output: A JSON response with structure like { "success": true, "result": "...", "script": "...", "executionId": "...", "cost": 0.0045 } — the result of the generated Python script running against your NetSuite data.
Next steps
Add per-tenant budget persistence — replace the in-memory SpendStore with a database-backed store so budgets survive server restarts.
Integrate with Slack or Teams — turn this API into a chatbot that finance teams can query directly from their messaging platform.
Add multi-model support — let users choose between Databricks DBRX, Llama, or GPT models by passing a modelId field in the request.
Extend the safety scanner — add AST-level Python parsing with tree-sitter instead of keyword matching for more accurate vulnerability detection.
Add a caching layer — cache the NetSuite data fetch so repeated queries against the same records reuse the LLM output.