Quick Start
This guide gets you from installation to your first typed LLM call.
Prerequisites
Section titled “Prerequisites”- Node.js 18 or later
- Zod 4 (Nuabase uses Zod 4’s built-in JSON Schema conversion)
- One of:
- A coding agent CLI installed and logged in: Claude Code, Codex, or Pi
- An API key for Groq, Cerebras, Gemini, OpenRouter, or DeepSeek
Installation
Section titled “Installation”npm install nuabase zodCreate a client
Section titled “Create a client”With the Headless Coding Agent API, no API key is needed. findLocalAgent() picks the first agent that is installed and logged in, trying Claude Code first:
import { Nua, z } from 'nuabase';import { findLocalAgent } from 'nuabase/local-agent';
const nua = Nua.direct({ localAgent: await findLocalAgent() });Or, with an LLM provider API key, using the LLM Provider APIs:
import { Nua, z } from 'nuabase';
const nua = Nua.direct({ providers: { openrouter: { apiKey: process.env.OPENROUTER_API_KEY! } },});Everything below works the same with either client.
Your first request
Section titled “Your first request”Pass only a prompt to get a plain string back:
const result = await nua.get('What is the capital of France?');
if (result.success) { console.log(result.data); // "Paris"} else { console.error(result.error);}This is the whole result from a run on Claude Code:
{ "success": true, "data": "Paris", "usage": { "promptTokens": 1571, "completionTokens": 53, "totalTokens": 1624, "promptCacheReadTokens": 0, "promptCacheWriteTokens": 1569 }, "model": "claude-code:claude-opus-5-5", "latencyMs": 3332, "source": "direct", "meta": { "engine": "local-agent", "agent": "claude-code", "model": "claude-opus-5-5", "costUsdEstimate": 0.01362, "schemaEnforcement": "native" }}Each call is one agent process, so expect a few seconds per call rather than milliseconds. The default logger also prints a line when each call starts and completes:
[INFO] LLM call started { type: 'llm_call_start', span_id: '7e42fedf-760f-46b9-bcaa-017a061c3748', service: 'claude-code', method: 'native', binaryPath: '/Users/you/.local/bin/claude', model: 'default'}[INFO] LLM call completed { type: 'llm_call_complete', span_id: '7e42fedf-760f-46b9-bcaa-017a061c3748', service: 'claude-code', method: 'native', exitCode: 0, usage: { promptTokens: 1571, completionTokens: 53, totalTokens: 1624, promptCacheReadTokens: 0, promptCacheWriteTokens: 1569 }, duration_ms: 3331}Pass your own logger to localAgent() to route or silence these.
Typed outputs
Section titled “Typed outputs”Pass input data and a Zod schema to get a typed value, parsed by your schema:
const AddressSchema = z.object({ street: z.string(), city: z.string(), state: z.string(), zip: z.string(),});
const result = await nua.get('Extract the shipping address', { input: 'Please ship the order to 123 Main St, Springfield, IL 62701', output: { name: 'address', schema: AddressSchema },});
if (result.success) { // result.data is typed as z.infer<typeof AddressSchema> console.log(result.data); // { street: '123 Main St', city: 'Springfield', state: 'IL', zip: '62701' }}The name field ('address') names the output for the model. Choose a name that describes the value.
Processing arrays
Section titled “Processing arrays”nua.list() processes an array of records in a single request:
const LeadInsights = z.object({ company_name: z.string(), industry: z.string(), company_size: z.enum(['SMB', 'Mid-market', 'Enterprise']), recommended_follow_up: z.enum(['Call', 'Email', 'Event']),});
const leads = [ { leadId: 'lead-101', name: 'Acme Analytics', notes: 'Growth-stage SaaS, 80 employees. Wants a live demo next week.' }, { leadId: 'lead-102', name: 'Bright Logistics', notes: 'Regional freight operator asking for pricing via email.' },];
const result = await nua.list( 'Extract company_name, industry, company_size, and recommended_follow_up from each lead.', { input: leads, primaryKey: 'leadId', output: { name: 'leadInsights', schema: LeadInsights }, },);
if (!result.success) throw new Error(result.error);console.log(result.data);Output:
[ { "leadId": "lead-101", "leadInsights": { "company_name": "Acme Analytics", "industry": "SaaS / Analytics", "company_size": "SMB", "recommended_follow_up": "Call" } }, { "leadId": "lead-102", "leadInsights": { "company_name": "Bright Logistics", "industry": "Logistics / Freight", "company_size": "Mid-market", "recommended_follow_up": "Email" } }]Each returned row carries the primaryKey value (leadId) next to the output field (leadInsights). The model may return rows in a different order than the input, so match rows by key, not by array index.
Error handling
Section titled “Error handling”Calls don’t throw for LLM failures. Check result.success:
const result = await nua.get('Summarize this in one sentence', { input: text, output: { name: 'summary', schema: z.string() },});
if (!result.success) { console.error(`Failed after ${result.attempts} attempts: ${result.reason}`); return;}
console.log(result.data);console.log(`${result.usage.totalTokens} tokens via ${result.model}`);On success:
The Q3 release moved exports to a background queue, which cut median export time from 40 seconds to 6, but large workspaces now get exports finishing out of order.1743 tokens via claude-code:claude-opus-5-5On failure, here with a model name the user’s Claude Code account doesn’t have:
Failed after 3 attempts: There's an issue with the selected model (claude-nonexistent-9). It may not exist or you may not have access to it. Run --model to pick a different model.reason says why the call failed, in the provider’s or agent’s own words. When the answer never fit your schema, reason is the validation error followed by the model’s raw response, so show your own message for those. error is a one-line summary for logs. A call throws only when its arguments are invalid, before anything is sent. See API Overview for the full list.
Hand a bigger job to the agent
Section titled “Hand a bigger job to the agent”When a job needs the agent to work in the user’s project rather than return one answer, open an Interactive Coding Agent Session with a first prompt your app writes:
import { startAgentSession } from 'nuabase/local-agent';
const session = await startAgentSession({ agent: 'claude-code', cwd: projectDir, prompt: 'Add tests for the CSV importer in src/import/csv.ts. Reply with your plan first and wait for me to say go.', dir: `${projectDir}/tmp/agent-sessions`,});
if (session.kind === 'command') { console.log(`Run in a terminal: ${session.files.command}`);}On macOS this opens a new terminal window running Claude Code in the project. See Interactive Coding Agent Session for every result and option.
What’s next?
Section titled “What’s next?”- Headless Coding Agent API: choose an agent, model, and effort, and see how calls are sandboxed
- Interactive Coding Agent Session: hand a task to the user’s coding agent in a terminal
- LLM Provider APIs: providers, model aliases, and edge runtimes
- Examples: complete, real-world examples