@useatlas/sdk
TypeScript SDK for the Atlas text-to-SQL agent API.
Install
bun add @useatlas/sdk
Quick Start
import { createAtlasClient } from "@useatlas/sdk";
const atlas = createAtlasClient({
baseUrl: "https://api.example.com",
apiKey: "my-key",
});
const result = await atlas.query("How many users signed up last week?");
console.log(result.answer);
console.log(result.sql);
console.log(result.data);
Authentication
Pass either apiKey (simple key auth) or bearerToken (BYOT / managed auth):
const atlas = createAtlasClient({ baseUrl: "...", apiKey: "my-key" });
const atlas = createAtlasClient({ baseUrl: "...", bearerToken: "eyJ..." });
API Reference
Query
atlas.query(question, opts?) | Run a synchronous query. Returns { answer, sql, data, steps, usage } |
atlas.streamQuery(question, opts?) | Stream a query as an async iterator of typed StreamEvents |
atlas.chat(messages, opts?) | Start a streaming chat session. Returns a raw Response with SSE body (AI SDK UI Message Stream Protocol) |
Schema
atlas.listTables() | List all queryable tables with column details. Returns { tables: TableInfo[] } |
const { tables } = await atlas.listTables();
for (const t of tables) {
console.log(`${t.table}: ${t.description}`);
for (const col of t.columns) {
console.log(` ${col.name} (${col.type}) — ${col.description}`);
}
}
Conversations
atlas.conversations.list(opts?) | List conversations (paginated) |
atlas.conversations.get(id) | Get a conversation with messages |
atlas.conversations.delete(id) | Delete a conversation |
atlas.conversations.star(id) | Star a conversation |
atlas.conversations.unstar(id) | Unstar a conversation |
Scheduled Tasks
atlas.scheduledTasks.list(opts?) | List scheduled tasks (paginated) |
atlas.scheduledTasks.get(id) | Get a task with recent runs |
atlas.scheduledTasks.create(input) | Create a scheduled task |
atlas.scheduledTasks.update(id, input) | Update a scheduled task |
atlas.scheduledTasks.delete(id) | Delete a scheduled task |
atlas.scheduledTasks.trigger(id) | Trigger immediate execution |
atlas.scheduledTasks.listRuns(id, opts?) | List past runs |
Admin (requires admin role)
atlas.admin.overview() | Dashboard overview data |
atlas.admin.semantic.entities() | List semantic layer entities |
atlas.admin.semantic.entity(name) | Get entity detail |
atlas.admin.semantic.metrics() | List metrics |
atlas.admin.semantic.glossary() | Get glossary |
atlas.admin.semantic.catalog() | Get catalog |
atlas.admin.semantic.stats() | Aggregate semantic stats |
atlas.admin.connections() | List datasource connections |
atlas.admin.testConnection(id) | Test connection health |
atlas.admin.audit(opts?) | Query audit log |
atlas.admin.auditStats() | Aggregate audit stats |
atlas.admin.plugins() | List installed plugins |
atlas.admin.pluginHealth(id) | Check plugin health |
Streaming
Use streamQuery() to receive typed events as the agent works:
import type { StreamEvent } from "@useatlas/sdk";
for await (const event of atlas.streamQuery("How many users signed up last week?")) {
switch (event.type) {
case "text":
process.stdout.write(event.content);
break;
case "tool-call":
console.log(`Calling ${event.name}`, event.args);
break;
case "tool-result":
console.log(`${event.name} returned`, event.result);
break;
case "result":
console.table(event.rows);
break;
case "error":
console.error(event.code ?? "error", event.message);
if (event.retryable && event.retryAfterSeconds !== undefined) {
console.warn(`Server suggests retrying in ${event.retryAfterSeconds}s`);
}
break;
case "parse-error":
console.warn("Malformed SSE frame", event.raw);
break;
case "finish":
console.log(`\nDone (${event.reason})`);
break;
}
}
Connection Drops & Partial Results
streamQuery() uses a one-shot SSE connection over fetch. If the connection drops mid-stream:
- Events already yielded are still valid — the async generator delivers each event to your
for await loop before advancing, so any text, result, or tool-result events you've already processed are safe to keep.
- The generator throws an
AtlasError with code network_error and a message starting with "Stream interrupted: ...".
- There is no automatic reconnect or resume — start a new
streamQuery() call to retry.
import { AtlasError } from "@useatlas/sdk";
const collected: string[] = [];
try {
for await (const event of atlas.streamQuery("Revenue by region")) {
if (event.type === "text") collected.push(event.content);
if (event.type === "result") console.table(event.rows);
}
} catch (err) {
if (err instanceof AtlasError && err.code === "network_error") {
console.warn("Stream dropped — partial text:", collected.join(""));
} else {
throw err;
}
}
Cancellation
Pass an AbortSignal to cancel the stream. The signal aborts the underlying reader, and an AbortError is thrown from the for await loop. Events yielded before cancellation are already consumed by your code.
const controller = new AbortController();
setTimeout(() => controller.abort(), 10_000);
try {
for await (const event of atlas.streamQuery("...", { signal: controller.signal })) {
if (event.type === "text") process.stdout.write(event.content);
}
} catch (err) {
if (err instanceof Error && err.name === "AbortError") {
console.log("Stream cancelled");
}
}
Stream Event Types
text | content | Text chunk from the agent |
tool-call | toolCallId, name, args | Agent is calling a tool |
tool-result | toolCallId, name, result | Tool returned a result |
result | columns, rows | Convenience event extracted from tool-result when executeSQL returns data. Both tool-result and result are emitted. |
error | message, code?, retryable?, retryAfterSeconds?, requestId? | Mid-stream error. When the server sends a structured ChatErrorInfo body (provider_rate_limit, provider_timeout, etc.) the typed code and upstream Retry-After delta travel alongside the human-readable message. Older servers populate only message. |
parse-error | raw, error | Client-side: an SSE frame contained invalid JSON. The raw data is preserved for debugging. |
finish | reason | Stream completed |
Context Warnings (Degraded Answer Signal)
The chat route emits data-context-warning frames whenever the agent ran
past a non-fatal degradation — the org semantic layer or learned-patterns
lookup failed, or the workspace is approaching its plan budget — and the
run was allowed to proceed against reduced context. These are not stream
events from streamQuery(); they arrive on the AI-SDK UI Message Stream
channel that chat() returns. If you are routing a raw Response from
atlas.chat() through your own SSE consumer, parse each
data-context-warning frame body with parseContextWarning from
@useatlas/types:
import { parseContextWarning, type ChatContextWarning } from "@useatlas/types";
if (frame.type === "data-context-warning") {
const warning: ChatContextWarning | null = parseContextWarning(frame.data);
if (warning) {
console.warn(`[${warning.code}]`, warning.title, warning.detail ?? "");
} else {
console.warn("[atlas-sdk] dropped malformed data-context-warning frame", frame.data);
}
}
The frame's severity: "warning" discriminator separates these from
data-error frames, so a single SSE consumer can route both without
misclassifying a degraded answer as a failure.
Error Handling
All methods throw AtlasError on failure. See the full Error Codes Reference for every error code, HTTP status, retry guidance, and a complete retry-with-backoff example.
import { AtlasError } from "@useatlas/sdk";
try {
await atlas.query("...");
} catch (err) {
if (err instanceof AtlasError) {
console.error(err.code);
console.error(err.status);
console.error(err.message);
console.error(err.retryable);
}
}
AtlasError Properties
code | AtlasErrorCode | Error code (e.g. rate_limited, network_error) |
status | number | HTTP status code (0 for client-side errors like network_error) |
message | string | Human-readable description |
retryable | boolean | Whether retrying the same request may succeed |
retryAfterSeconds | number | undefined | Server-suggested delay — only populated when the server includes retryAfterSeconds in the error response body (currently only for rate_limited). Always guard against undefined |
Retry with Backoff
Use retryable for generic retry logic. For rate_limited, prefer the server-suggested delay when available, falling back to exponential backoff:
import { AtlasError } from "@useatlas/sdk";
try {
await atlas.query("...");
} catch (err) {
if (!(err instanceof AtlasError) || !err.retryable) throw err;
const delay = err.retryAfterSeconds != null
? err.retryAfterSeconds * 1000
: 5000;
await new Promise((r) => setTimeout(r, delay));
await atlas.query("...");
}
License
MIT