Atlas
Reference

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/sdk

Create 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 }        // BothapiKey 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:

TypeFieldsDescription
textcontentText chunk from the agent
tool-calltoolCallId, name, argsAgent is calling a tool
tool-resulttoolCallId, name, resultTool returned a result
resultcolumns, rowsConvenience event when executeSQL returns data (emitted alongside tool-result)
errormessageError during streaming
parse-errorraw, errorClient-side: an SSE frame contained invalid JSON. The raw data is preserved for debugging.
finishreasonStream 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 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 — 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 conversations

To 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

LayerDescription
empty_checkQuery is empty or whitespace-only
connectionConnection ID not registered or no datasource configured
regex_guardDML/DDL keyword detected (INSERT, UPDATE, DELETE, DROP, etc.)
ast_parseQuery could not be parsed, is not a SELECT, or contains multiple statements
table_whitelistQuery 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

PropertyTypeDescription
codeAtlasErrorCodeError code (e.g. rate_limited, network_error)
statusnumberHTTP status code (0 for client-side errors like network_error)
messagestringHuman-readable description
retryablebooleanWhether retrying the same request may succeed
retryAfterSecondsnumber | undefinedServer-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

CodeStatusRetryableDescription
auth_error401NoAuthentication failed
session_expired401NoSession expired or invalidated (re-authenticate)
forbidden403NoInsufficient permissions
forbidden_role403NoInsufficient role (e.g. admin required)
rate_limited429YesToo many requests (check retryAfterSeconds)
configuration_error400NoServer misconfigured
no_datasource400NoNo datasource connected
org_not_found400NoNo active organization selected
invalid_request400NoInvalid request body
validation_error422NoRequest validation failed (see details for field errors)
not_found404NoResource not found
not_available404NoFeature not available (e.g., no internal DB)
provider_model_not_found400NoConfigured AI model does not exist
provider_auth_error503NoLLM provider auth failed
provider_rate_limit503YesLLM provider rate limited
provider_timeout504YesLLM provider timed out
provider_unreachable503YesCould not reach the AI provider
provider_error502YesLLM provider error
plan_limit_exceeded429NoWorkspace exceeded plan query/token limit
trial_expired403NoTrial has expired — upgrade to a paid plan
billing_check_failed503YesBilling check failed — transient infrastructure issue
workspace_check_failed503YesWorkspace check failed — transient infrastructure issue
workspace_throttled429YesWorkspace throttled by abuse detection — retry after delay
workspace_suspended403NoWorkspace suspended by administrator
workspace_deleted404NoWorkspace has been permanently deleted
internal_error500YesServer error (includes requestId for log correlation)
network_error0YesCould not reach the API (SDK-only)
invalid_response(varies)NoResponse could not be parsed (SDK-only)
unknown_error(varies)NoUnrecognized 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

Edit on GitHub

Last updated on

On this page