Skip to main content

TypeScript SDK

@naturali/sdk is a typed client for the whole API, generated from the OpenAPI specs. Every operation, every schema — nothing is hand-written per endpoint, so the SDK cannot lag the contract.

pnpm add @naturali/sdk

Quick start

import { NaturaliClient } from '@naturali/sdk';

const naturali = new NaturaliClient({
token: process.env.NATURALI_TOKEN,
});

const { data, error } = await naturali.agents.listAgents({
path: { project_id: 'proj_V1StGXR8Z5jdHi6B' },
query: { limit: 10 },
});

if (error) {
console.error(error.error.code); // 'access_denied', 'budget_exceeded', …
} else {
console.log(data.data);
}

A method never throws on a non-2xx response. It resolves to { data, error }, so the error envelope and its machine-readable code arrive as typed data — the fail-closed codes are values you branch on, not exceptions you catch.

Client options

The client always talks to https://api.naturali.ai — the only naturali.ai API origin, and not configurable.

OptionPurpose
tokenSent as Authorization: Bearer …. A nat_sk_… API key or a session access JWT from Auth.
headersMerged last, so it overrides the header above or adds your own.

There is no project option. A project-scoped operation names its project in the path (project_id), and that path parameter is the only thing the API authorizes against — there is no header notion of a "current project" to keep in sync with it.

Create one client and reuse it. Two clients never share state, so a request-scoped client per user session is safe.

Call shape

Arguments follow the spec's structure — path, query, body — using the same snake_case field names as the wire:

await naturali.sessions.addSessionMessage({
path: { project_id, agent_id, session_id },
body: { message: 'What is the capital of France?' },
});

await naturali.knowledge.queryKnowledgeCollection({
path: { project_id, collection_id },
body: { query: 'refund policy', limit: 5 },
});

Method names are the spec's operationIds, so the API Reference page for an operation names the method that calls it.

Resources

One property per resource, each mirroring a module:

PropertyModule
agentsAgents
apiKeysAPI Keys
authAuth
channelsChannels
generationsGenerations
knowledgeKnowledge
modelsModels
projectsProjects
providersProviders
sessionsSessions
toolsTools
tracesTraces

naturali.http exposes the underlying HTTP client for interceptors or a one-off request such as naturali.http.get({ url: '/health' }).

Types

Every schema in the specs is exported:

import type { Agent, AgentCreate, ErrorResponse } from '@naturali/sdk';

created_at and updated_at are handed over as Date objects; every other field matches the wire shape exactly.

Pagination

List operations are cursor-based. Follow next_cursor until it comes back null:

let cursor: string | undefined;

do {
const { data } = await naturali.agents.listAgents({
path: { project_id },
query: { limit: 100, cursor },
});

for (const agent of data?.data ?? []) {
console.log(agent.id);
}

cursor = data?.next_cursor ?? undefined;
} while (cursor);

Browser use

Pass a session JWT, never an API key — a nat_sk_… secret does not belong in a browser (see Auth for the session flow).

Versioning

@naturali/sdk shares its version with the API and with @naturali/cli — one number, released together. The SDK is generated from the API's OpenAPI specs, so @naturali/sdk@0.28.0 is the client for API 0.28.0, and installing it never leaves you guessing which contract you are holding.

A consequence worth knowing: a bump can mean the API gained a resource, not that anything in the SDK's own surface changed. Releases are automatic on merge, so there is no release cadence to follow — pin what you need, or track the latest and read the changelog.