TypeScript SDK
Complete reference for the @justack/sdk package. See Quickstart for installation and setup.
Client Configuration
Constructor Options
interface JustackClientOptions { // Required: Your API key (prefix: jstk_) apiKey: string;
// Optional: API base URL (default: "https://api.justack.dev/v1") baseUrl?: string;
// Optional: Request timeout in ms (default: 30000) timeout?: number;
// Optional: Custom fetch implementation fetch?: typeof fetch;}Example
const client = new JustackClient({ apiKey: process.env.JUSTACK_API_KEY, baseUrl: "https://api.staging.justack.dev/v1", timeout: 60000,});Sessions
Create a Session
Creates a new session and returns an active Session object for real-time interaction via WebSocket.
const session = await client.sessions.create({ // Required: Display name name: "Deployment Approval",
// Optional: Recipients (email, externalId, or objects) recipients: [ "approver@example.com", { email: "backup@example.com" }, { externalId: "slack-user-123" }, ],
// Optional: Send email notifications (default: false) notify: true,
// Optional: Retention period in months (1-12, default: 1) retentionMonths: 3,
// Optional: Callback URL for async notifications callbackUrl: "https://example.com/webhook",});Resume a Session
Resumes an existing session by ID, returning an active Session object.
const session = await client.sessions.resume("01HXYZ...");await session.log("Resuming work...");Get Session Data
Returns raw session data without interaction methods. Use resume() if you need to interact.
const data = await client.sessions.get("01HXYZ...");
console.log(data.sessionId);console.log(data.name);console.log(data.expiresAt);console.log(data.retentionMonths);Delete a Session
await client.sessions.delete("01HXYZ...");List Sessions
Returns an AsyncIterable that auto-paginates through all sessions.
for await (const session of client.sessions.list()) { console.log(`${session.sessionId}: ${session.name}`);}
// Or with a custom page sizefor await (const session of client.sessions.list({ limit: 50 })) { console.log(session.name);}Session Interaction
Once you have a Session object (from create() or resume()), you can log messages and ask questions.
Log a Message
Send a one-way informational message. Fire-and-forget, does not wait for a response.
await session.log("Processing step 1 of 3...");
// Ephemeral log (not persisted)await session.log("Processing file 1 of 10", { persist: false });Ask a Question
Send a question with typed inputs and wait for a response. The response type is automatically inferred from the input definitions.
const { env, notify } = await session.ask("Configure deployment", { inputs: [ { type: "select", name: "env", options: ["staging", "production"] }, { type: "confirm", name: "notify" }, ] as const,});// env: string, notify: booleanAsk with Timeout
import { TimeoutError } from "@justack/sdk";
try { const response = await session.ask("Urgent: Approve?", { inputs: [{ type: "confirm", name: "approved" }] as const, timeout: 300000, // 5 minutes (default) });} catch (error) { if (error instanceof TimeoutError) { console.log("No response within timeout"); }}Get Messages
List all messages in the session with auto-pagination.
for await (const message of session.messages()) { console.log(`[${message.type}] ${message.content}`); if (message.responseContent) { console.log(` Response: ${message.responseContent}`); }}Close a Session
Close the WebSocket connection and clean up resources. Call this when done interacting.
session.close();Input Types
The SDK provides three input types for session.ask(). Use as const on the inputs array to enable automatic TypeScript type inference on the response.
Text Input
Free-form text response. Maps to string in the response.
const { filename } = await session.ask("Enter the filename:", { inputs: [ { type: "text", name: "filename", label: "Filename", placeholder: "e.g., config.json", required: true, multiline: false, maxLength: 255, }, ] as const,});// filename: stringConfirm Input
Boolean yes/no response. Maps to boolean in the response.
const { overwrite } = await session.ask("File already exists.", { inputs: [ { type: "confirm", name: "overwrite", label: "Overwrite existing file?", defaultValue: false, }, ] as const,});// overwrite: booleanSelect Input
Choose from predefined options. Maps to string (or string[] with multiple: true).
// Simple optionsconst { format } = await session.ask("Select output format:", { inputs: [ { type: "select", name: "format", options: ["json", "yaml", "toml"], }, ] as const,});// format: string
// Rich options with descriptionsconst { env } = await session.ask("Select environment:", { inputs: [ { type: "select", name: "env", options: [ { value: "staging", label: "Staging", description: "Test environment" }, { value: "production", label: "Production", description: "Live environment" }, ], }, ] as const,});
// Multiple selectionconst { features } = await session.ask("Enable features:", { inputs: [ { type: "select", name: "features", options: ["logging", "caching", "metrics"], multiple: true, }, ] as const,});// features: string[]Combined Inputs
Combine multiple input types in a single question:
const response = await session.ask("Configure deployment", { inputs: [ { type: "text", name: "version", label: "Version" }, { type: "select", name: "env", options: ["staging", "production"] }, { type: "confirm", name: "notify", label: "Send notifications?" }, ] as const,});// response: { version: string; env: string; notify: boolean }Recipients
Create a Recipient
const recipient = await client.recipients.create({ name: "John Doe", email: "john@example.com",});
// Or with external ID (at least one of email or externalId required)const recipient = await client.recipients.create({ name: "Slack User", externalId: "slack-U12345",});Get a Recipient
const recipient = await client.recipients.get("01HXYZ...");
console.log(recipient.recipientId);console.log(recipient.name);console.log(recipient.email); // string | nullconsole.log(recipient.externalId); // string | nullconsole.log(recipient.createdAt);Delete a Recipient
await client.recipients.delete("01HXYZ...");List Recipients
for await (const recipient of client.recipients.list()) { console.log(`${recipient.name} (${recipient.email})`);}Send an Invite
Send a magic link email to a recipient (requires the recipient to have an email address).
const result = await client.recipients.sendInvite("01HXYZ...");if (result.success) { console.log("Invite sent!");}Get an Invite URL
Get a magic link URL for custom delivery (useful for recipients without email).
const { url, expiresAt } = await client.recipients.getInviteUrl("01HXYZ...");console.log(`Invite URL: ${url}`);console.log(`Expires: ${expiresAt}`);Types
The SDK exports all TypeScript types:
import type { // Client JustackClientOptions,
// Sessions SessionData, CreateSessionParams, AskOptions, LogOptions, Message, MessageRole, MessageType,
// Recipients Recipient, CreateRecipientParams, RecipientInput, InviteResult, InviteUrlResult,
// Inputs Input, TextInput, ConfirmInput, SelectInput, SelectOption, ResponseFromInputs,
// Pagination PaginatedResponse,
// Errors ErrorCode,} from "@justack/sdk";SessionData
interface SessionData { sessionId: string; name: string; retentionMonths: number; createdAt: string; expiresAt: string; lastMessageAt: string | null; recipients?: Recipient[];}Message
interface Message { id: string; role: "agent" | "recipient"; type: "log" | "ask"; content: string; inputs: Input[] | null; senderId: string | null; responseContent: string | null; respondedAt: string | null; respondedBy: string | null; persist: boolean; createdAt: string;}Recipient
interface Recipient { recipientId: string; name: string; email: string | null; externalId: string | null; createdAt: string;}Error Handling
The SDK throws typed errors for different failure scenarios:
import { JustackError, BadRequestError, UnauthorizedError, NotFoundError, TimeoutError, SessionExpiredError,} from "@justack/sdk";
try { const response = await session.ask("Deploy?", { inputs: [{ type: "confirm", name: "ok" }] as const, });} catch (error) { if (error instanceof TimeoutError) { console.log("No response within timeout"); } else if (error instanceof SessionExpiredError) { console.log("Session has expired"); } else if (error instanceof UnauthorizedError) { console.log("Invalid API key"); }}Error Types
| Error Class | Code | HTTP Status | Description |
|---|---|---|---|
JustackError | (varies) | (varies) | Base error class |
BadRequestError | BAD_REQUEST | 400 | Invalid request parameters |
UnauthorizedError | UNAUTHORIZED | 401 | Missing or invalid credentials |
ForbiddenError | FORBIDDEN | 403 | Insufficient permissions |
NotFoundError | NOT_FOUND | 404 | Resource not found |
PaymentRequiredError | PAYMENT_REQUIRED | 402 | Usage limit reached |
RateLimitedError | RATE_LIMITED | 429 | Too many requests |
NetworkError | NETWORK_ERROR | - | Network connectivity issue |
TimeoutError | TIMEOUT | - | Request or ask timed out |
WebSocketError | WEBSOCKET_ERROR | - | WebSocket connection failure |
SessionExpiredError | SESSION_EXPIRED | - | Session has expired |
All errors extend JustackError and include:
error.message; // Human-readable descriptionerror.code; // ErrorCode stringerror.status; // HTTP status (if applicable)error.cause; // Original error (for network/websocket errors)Pagination
All list methods return AsyncIterable objects that handle pagination automatically:
// Iterate through all resultsfor await (const session of client.sessions.list()) { console.log(session.name);}
// Collect all results into an arrayimport { collect } from "@justack/sdk";
const allSessions = await collect(client.sessions.list());const allRecipients = await collect(client.recipients.list());Next Steps
- Quickstart - Get started quickly
- Sessions Guide - Deep dive into sessions
- API Reference - REST API documentation