SDK Reference
TypeScript SDK for programmatic access to the Atlas API.
The @useatlas/sdk package provides a typed client for the Atlas API. Use it to run queries, stream chat responses, and manage conversations from any TypeScript or JavaScript application.
Installation
bun add @useatlas/sdkCreate a Client
import { createAtlasClient } from "@useatlas/sdk";
// Simple API key auth — pass the key issued in the admin console
const atlas = createAtlasClient({
baseUrl: "https://api.example.com",
apiKey: "your-api-key",
});Bearer Token Auth
Use a bearer token for managed sessions (Better Auth) or bring-your-own-token (BYOT) JWTs:
import { createAtlasClient } from "@useatlas/sdk";
// Bearer token auth — pass a JWT or session token from your auth provider
const atlas = createAtlasClient({
baseUrl: "https://api.example.com",
bearerToken: "your-jwt-or-session-token",
});
// Both can be provided — apiKey takes precedence for the Authorization header
const atlas = createAtlasClient({
baseUrl: "https://api.example.com",
apiKey: "your-api-key", // Used for Authorization header
bearerToken: "your-jwt-token", // Ignored when apiKey is present
});Client options
type AtlasClientOptions = {
baseUrl: string; // Atlas API base URL
} & (
| { apiKey: string; bearerToken?: undefined } // Simple API key
| { bearerToken: string; apiKey?: undefined } // Bearer token (managed/BYOT)
| { apiKey: string; bearerToken: string } // Both — apiKey takes precedence
);When both apiKey and bearerToken are provided, the API key takes precedence.
Query
Run a natural language query and get a structured response.
const result = await atlas.query("What was last month's revenue?");
console.log(result.answer); // "Last month's revenue was $1.2M..."
console.log(result.sql); // ["SELECT SUM(total) FROM orders WHERE ..."]
console.log(result.data); // [{ columns: ["total"], rows: [{ total: 1200000 }] }]
console.log(result.steps); // 3
console.log(result.usage); // { totalTokens: 1523 }Query options
const result = await atlas.query("Revenue by region", {
conversationId: "existing-conversation-id", // Continue a conversation
});Response
interface QueryResponse {
answer: string;
sql: string[];
data: Array<{
columns: string[];
rows: Array<Record<string, unknown>>;
}>;
steps: number;
usage: { totalTokens: number };
conversationId?: string;
pendingActions?: Array<{
id: string;
type: string;
target: string;
summary: string;
approveUrl: string;
denyUrl: string;
}>;
}Chat
Stream a chat response using the AI SDK UI Message Stream Protocol.
// Send messages in AI SDK UIMessage format and get a streaming SSE response
const response = await atlas.chat([
{
id: "msg-1",
role: "user",
parts: [{ type: "text", text: "How many active customers?" }],
},
]);
// Response is a raw fetch Response — read the SSE stream manually
// or pass it to the AI SDK's useChat() for automatic parsing
const reader = response.body?.getReader();Chat options
const response = await atlas.chat(messages, {
conversationId: "existing-conversation-id",
});Stream Query
Stream a query as an async iterator of typed events. The agent's reasoning, tool calls, and results are yielded incrementally as StreamEvent objects.
Method Signature
async *streamQuery(
question: string,
opts?: StreamQueryOptions,
): AsyncGenerator<StreamEvent>Stream query options
interface StreamQueryOptions {
/** AbortSignal for cancelling the stream */
signal?: AbortSignal;
/** Resume an existing conversation */
conversationId?: string;
}Stream Events
Each yielded event is a discriminated union:
| Type | Fields | Description |
|---|---|---|
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 when executeSQL returns data (emitted alongside tool-result) |
error | message | Error during streaming |
parse-error | raw, error | Client-side: an SSE frame contained invalid JSON. The raw data is preserved for debugging. |
finish | reason | Stream completed ("stop", "length", "tool-calls", etc.) |
Stream event iteration
// Iterate over typed stream events as the agent works
for await (const event of atlas.streamQuery("Revenue by region last quarter")) {
switch (event.type) {
case "text":
// Agent's narrative response — stream to the terminal character by character
process.stdout.write(event.content);
break;
case "tool-call":
// Agent is invoking a tool (explore or executeSQL)
console.log(`Calling ${event.name}`, event.args);
break;
case "result":
// Convenience event: SQL query returned tabular data
console.table(event.rows);
break;
case "finish":
// Stream complete — common reasons: "stop", "length", "tool-calls", "error"
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 valid. The async generator delivers each event to your
for awaitloop before advancing, so anytext,result, ortool-resultevents you've already processed are safe to keep. - The generator throws an
AtlasErrorwith codenetwork_errorand a message starting with"Stream interrupted: ...". - There is no automatic reconnect — 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(""));
// Decide whether to retry or use the partial data you have
} else {
throw err;
}
}Cancellation
Pass an AbortSignal to cancel the stream mid-flight. 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.
// Create an AbortController to cancel the stream after a timeout
const controller = new AbortController();
setTimeout(() => controller.abort(), 10_000); // Cancel after 10 seconds
try {
for await (const event of atlas.streamQuery("...", {
signal: controller.signal,
})) {
if (event.type === "text") process.stdout.write(event.content);
}
} catch (err) {
// AbortError is thrown when the signal fires — handle gracefully
if (err instanceof Error && err.name === "AbortError") {
console.log("Stream cancelled");
}
}Conversations
List, retrieve, star, and delete conversations.
// List conversations with pagination and optional filters
const { conversations, total } = await atlas.conversations.list({
limit: 20, // Max results per page
offset: 0, // Skip N results for pagination
starred: true, // Only return starred conversations
});
// Fetch a single conversation including its full message history
const conversation = await atlas.conversations.get("conversation-id");
console.log(conversation.messages);
// Pin important conversations for quick access
await atlas.conversations.star("conversation-id");
await atlas.conversations.unstar("conversation-id");
// Permanently remove a conversation and all its messages
await atlas.conversations.delete("conversation-id");Pagination
Conversations use offset-based pagination. Pass limit and offset to page through results:
// Fetch the first page of conversations (20 per page)
const page1 = await atlas.conversations.list({ limit: 20, offset: 0 });
console.log(page1.conversations); // First 20 conversations
console.log(page1.total); // Total number of conversationsTo iterate through all conversations:
import type { Conversation } from "@useatlas/sdk";
// Collect all conversations by advancing the offset each page
const all: Conversation[] = [];
const pageSize = 50;
let offset = 0;
while (true) {
const { conversations, total } = await atlas.conversations.list({
limit: pageSize,
offset,
});
all.push(...conversations);
// Stop when we've fetched everything
if (all.length >= total) break;
offset += pageSize;
}
console.log(`Fetched ${all.length} conversations`);Types
interface Conversation {
id: string;
userId: string | null;
title: string | null;
surface: "web" | "api" | "mcp" | "slack";
connectionId: string | null;
starred: boolean;
createdAt: string;
updatedAt: string;
notebookState?: NotebookStateWire | null;
}
interface NotebookStateWire {
version: number;
cellOrder?: string[];
cellProps?: Record<string, { collapsed?: boolean }>;
branches?: ForkBranchWire[];
forkRootId?: string;
forkPointCellId?: string;
textCells?: Record<string, { content: string }>;
}
interface ForkBranchWire {
conversationId: string;
forkPointCellId: string;
label: string;
createdAt: string;
}
interface ConversationWithMessages extends Conversation {
messages: Message[];
}
interface Message {
id: string;
conversationId: string;
role: "user" | "assistant" | "system" | "tool";
content: unknown;
createdAt: string;
}Scheduled Tasks
Create and manage recurring queries. Requires ATLAS_SCHEDULER_ENABLED=true on the server.
// Create a recurring task — runs the question on a cron schedule
const task = await atlas.scheduledTasks.create({
name: "Daily revenue check",
question: "What was yesterday's total revenue?",
cronExpression: "0 9 * * *", // Every day at 9:00 AM
deliveryChannel: "email", // Deliver results via email
recipients: [{ type: "email", address: "team@example.com" }],
});
// List tasks — filter by enabled status
const { tasks, total } = await atlas.scheduledTasks.list({ enabled: true });
// Get task details including its most recent execution history
const taskDetail = await atlas.scheduledTasks.get(task.id);
console.log(taskDetail.recentRuns);
// Pause a task without deleting it
await atlas.scheduledTasks.update(task.id, { enabled: false });
// Run the task immediately (outside its normal schedule)
await atlas.scheduledTasks.trigger(task.id);
// Fetch execution history for debugging or monitoring
const { runs } = await atlas.scheduledTasks.listRuns(task.id, { limit: 10 });
// Soft-delete the task (sets enabled=false)
await atlas.scheduledTasks.delete(task.id);See Scheduled Tasks for full configuration details.
Validate SQL
Validate a SQL query without executing it. Runs the full validation pipeline (empty check, regex guard, AST parse, table whitelist) and returns the result.
const result = await atlas.validateSQL("SELECT id, name FROM users WHERE active = true");
if (result.valid) {
console.log("Tables referenced:", result.tables); // ["users"]
} else {
for (const err of result.errors) {
console.log(`[${err.layer}] ${err.message}`);
// e.g. [regex_guard] Forbidden SQL operation detected: ...
}
}With a specific connection
const result = await atlas.validateSQL(
"SELECT * FROM analytics.events",
"clickhouse-prod", // connectionId
);Response
type ValidationLayer =
| "empty_check"
| "connection"
| "regex_guard"
| "ast_parse"
| "table_whitelist";
type ValidateSQLResponse =
| { valid: true; errors: []; tables: string[] }
| { valid: false; errors: Array<{ layer: ValidationLayer; message: string }>; tables: [] };Validation layers
| Layer | Description |
|---|---|
empty_check | Query is empty or whitespace-only |
connection | Connection ID not registered or no datasource configured |
regex_guard | DML/DDL keyword detected (INSERT, UPDATE, DELETE, DROP, etc.) |
ast_parse | Query could not be parsed, is not a SELECT, or contains multiple statements |
table_whitelist | Query references a table not defined in the semantic layer |
Audit Log
Query the audit log with filters. Convenience method that wraps admin.audit() — requires admin role.
// Get recent failed queries
const { rows, total } = await atlas.getAuditLog({
success: false,
limit: 20,
});
for (const entry of rows) {
console.log(`${entry.timestamp} | ${entry.sql.slice(0, 60)} | ${entry.error}`);
}
// Filter by date range and user
const { rows: userQueries } = await atlas.getAuditLog({
user: "user-uuid",
from: "2026-03-01",
to: "2026-03-15",
limit: 100,
});Options
interface AuditLogOptions {
limit?: number; // Max rows per page (server clamps to 200, default 50)
offset?: number; // Pagination offset
user?: string; // Filter by user ID
success?: boolean; // Filter by success/failure
from?: string; // ISO 8601 date (e.g. "2026-01-01")
to?: string; // ISO 8601 date (e.g. "2026-03-15")
}Response
interface AuditLogResponse {
rows: AuditLogEntry[];
total: number;
limit: number;
offset: number;
}
interface AuditLogEntry {
id: string;
timestamp: string;
userId: string | null;
userLabel: string | null;
authMode: AuthMode;
sql: string;
durationMs: number;
rowCount: number | null;
success: boolean;
error: string | null;
sourceId: string | null;
sourceType: string | null;
targetHost: string | null;
}Admin
Access admin-only endpoints. Requires the admin role.
// Get deployment health at a glance — connections, entities, plugin status
const overview = await atlas.admin.overview();
// Browse the semantic layer — entities, metrics, glossary, catalog
const { entities } = await atlas.admin.semantic.entities();
const { entity } = await atlas.admin.semantic.entity("orders");
const { metrics } = await atlas.admin.semantic.metrics();
const { glossary } = await atlas.admin.semantic.glossary();
const { catalog } = await atlas.admin.semantic.catalog();
const stats = await atlas.admin.semantic.stats(); // Entity counts and coverage gap counts
// List datasource connections and test their health
const { connections } = await atlas.admin.connections();
const health = await atlas.admin.testConnection("default");
// Query the audit log — filter by date range and success/failure
const { rows, total } = await atlas.admin.audit({
limit: 50,
from: "2026-01-01", // ISO date string
success: false, // Only return failed queries
});
const auditStats = await atlas.admin.auditStats();
// Manage plugins — list installed plugins and run health probes
const { plugins } = await atlas.admin.plugins();
const pluginHealth = await atlas.admin.pluginHealth("clickhouse");See Admin Console for details on each endpoint.
Rollback
Trigger rollback of an executed action. The action must be reversible (have stored rollback_info) and in the executed or auto_approved status.
const result = await atlas.rollbackAction("action-uuid");
console.log(result.status); // "rolled_back"
console.log(result.action_type); // "jira:create"
// Check if the rollback handler reported an error
if (result.warning) {
console.warn(result.warning);
console.warn(result.error); // Handler error details
}Response
interface RollbackActionResponse {
id: string;
status: ActionStatus;
action_type: string;
target: string;
summary: string;
rollback_info: { method: string; params: Record<string, unknown> } | null;
error: string | null;
/** Present when the rollback handler reported an error — the side-effect may not have been reversed. */
warning?: string;
}See Actions Framework for the full rollback lifecycle and error cases.
Error Handling
All API errors are thrown as AtlasError instances. See Error Codes for the full catalog of every error code, HTTP status, retry guidance, and a complete retry-with-backoff example. See Rate Limiting & Retry for backoff patterns and handling 429 responses.
import { AtlasError } from "@useatlas/sdk";
try {
await atlas.query("...");
} catch (error) {
if (error instanceof AtlasError) {
console.log(error.code); // "rate_limited"
console.log(error.status); // 429
console.log(error.message); // "Too many requests"
console.log(error.retryable); // true
}
}AtlasError Properties
| Property | Type | Description |
|---|---|---|
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 the retryable flag 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 (error) {
if (!(error instanceof AtlasError) || !error.retryable) throw error;
// Use server delay if available, otherwise exponential backoff
const delay = error.retryAfterSeconds != null
? error.retryAfterSeconds * 1000
: 5000;
await new Promise((r) => setTimeout(r, delay));
await atlas.query("...");
}Error Codes
| Code | Status | Retryable | Description |
|---|---|---|---|
auth_error | 401 | No | Authentication failed |
session_expired | 401 | No | Session expired or invalidated (re-authenticate) |
forbidden | 403 | No | Insufficient permissions |
forbidden_role | 403 | No | Insufficient role (e.g. admin required) |
rate_limited | 429 | Yes | Too many requests (check retryAfterSeconds) |
configuration_error | 400 | No | Server misconfigured |
no_datasource | 400 | No | No datasource connected |
org_not_found | 400 | No | No active organization selected |
invalid_request | 400 | No | Invalid request body |
validation_error | 422 | No | Request validation failed (see details for field errors) |
not_found | 404 | No | Resource not found |
not_available | 404 | No | Feature not available (e.g., no internal DB) |
provider_model_not_found | 400 | No | Configured AI model does not exist |
provider_auth_error | 503 | No | LLM provider auth failed |
provider_rate_limit | 503 | Yes | LLM provider rate limited |
provider_timeout | 504 | Yes | LLM provider timed out |
provider_unreachable | 503 | Yes | Could not reach the AI provider |
provider_error | 502 | Yes | LLM provider error |
plan_limit_exceeded | 429 | No | Workspace exceeded plan query/token limit |
trial_expired | 403 | No | Trial has expired — upgrade to a paid plan |
billing_check_failed | 503 | Yes | Billing check failed — transient infrastructure issue |
workspace_check_failed | 503 | Yes | Workspace check failed — transient infrastructure issue |
workspace_throttled | 429 | Yes | Workspace throttled by abuse detection — retry after delay |
workspace_suspended | 403 | No | Workspace suspended by administrator |
workspace_deleted | 404 | No | Workspace has been permanently deleted |
internal_error | 500 | Yes | Server error (includes requestId for log correlation) |
network_error | 0 | Yes | Could not reach the API (SDK-only) |
invalid_response | (varies) | No | Response could not be parsed (SDK-only) |
unknown_error | (varies) | No | Unrecognized error (SDK-only) |
All error responses include a requestId field (UUID) for correlating client errors with
server logs. Quote this value when reporting issues.
The retryable boolean on AtlasError tells you whether retrying the same request may succeed.
Use it to build generic retry logic without hard-coding error codes:
Handling Specific Error Codes
Use error.retryable to decide whether to retry, then refine behavior for specific codes:
import { AtlasError, type AtlasClient } from "@useatlas/sdk";
// Helper: sleep for a given number of milliseconds
function sleep(ms: number): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, ms));
}
async function queryWithRetry(atlas: AtlasClient, question: string) {
try {
return await atlas.query(question);
} catch (error) {
if (!(error instanceof AtlasError)) throw error;
// Permanent error — no point retrying
if (!error.retryable) throw error;
// Rate limited — use the server-provided delay
if (error.code === "rate_limited") {
const delay = (error.retryAfterSeconds ?? 10) * 1000;
console.log(`Rate limited — retrying in ${delay / 1000}s`);
await sleep(delay);
return atlas.query(question);
}
// Other transient errors — retry with a fixed backoff
console.log(`Transient error (${error.code}) — retrying in 5s`);
await sleep(5000);
return atlas.query(question);
}
}Batch Patterns
Run multiple queries in parallel using Promise.all. Each call is independent, so they execute concurrently:
// Run three independent queries at the same time
const [revenue, users, topProducts] = await Promise.all([
atlas.query("What was last month's revenue?"),
atlas.query("How many active users this week?"),
atlas.query("Top 5 products by sales volume"),
]);
// Each result has the standard QueryResponse shape
console.log(revenue.answer);
console.log(users.data);
console.log(topProducts.sql);For large batches, limit concurrency to avoid rate limits:
import type { QueryResponse } from "@useatlas/sdk";
// Process questions in chunks to stay within rate limits
const questions = [
"Revenue by region",
"Customer churn rate",
"Average order value",
"Support ticket volume",
"Monthly active users",
"Top referral sources",
];
const chunkSize = 3; // Max concurrent requests
const results: QueryResponse[] = [];
for (let i = 0; i < questions.length; i += chunkSize) {
// Slice the next chunk and run in parallel
const chunk = questions.slice(i, i + chunkSize);
const chunkResults = await Promise.all(
chunk.map((q) => atlas.query(q))
);
results.push(...chunkResults);
}
console.log(`Completed ${results.length} queries`);Type Exports
The SDK re-exports all types from the client module. Import them directly:
import type {
// Client
AtlasClient,
AtlasClientOptions,
AtlasErrorCode,
// Query
QueryOptions,
QueryResponse,
// Chat
ChatMessage,
ChatOptions,
// Streaming
StreamEvent,
StreamQueryOptions,
StreamFinishReason,
// Conversations
Conversation,
Message,
ConversationWithMessages,
ListConversationsResponse,
ListConversationsOptions,
ShareConversationResponse,
// Scheduled Tasks
DeliveryChannel,
Recipient,
ScheduledTaskRecipient, // @deprecated — use Recipient instead
ScheduledTask,
ScheduledTaskWithRuns,
ScheduledTaskRun,
ListScheduledTasksResponse,
ListScheduledTasksOptions,
CreateScheduledTaskInput,
UpdateScheduledTaskInput,
RunStatus,
ActionApprovalMode,
// Admin
DBType,
HealthStatus,
PluginType,
PluginStatus,
AuthMode,
AdminOverview,
EntitySummary,
SemanticStats,
ConnectionHealth,
ConnectionHealthCheck, // @deprecated — alias for ConnectionHealth
ConnectionInfo,
ConnectionDetail,
AuditLogEntry,
AuditLogResponse,
AuditLogOptions,
AuditStats,
PluginInfo,
PluginHealthCheckResponse,
// Validate SQL
ValidateSQLResponse,
ValidationLayer,
// Actions
ActionStatus,
RollbackActionResponse,
} from "@useatlas/sdk";See Also
- API Overview — HTTP endpoints the SDK wraps
- Error Codes — Full catalog of every error code, retryable classification, and fix guidance
- React Hooks Reference — React hooks that use the same API (for browser UIs)
- Rate Limiting & Retry — Server-side rate limit configuration and client-side retry patterns
- Embedding Widget — Drop-in chat widget (alternative to building a custom UI with the SDK)
Last updated on