Skip to content

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 size
for 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: boolean

Ask 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: string

Confirm 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: boolean

Select Input

Choose from predefined options. Maps to string (or string[] with multiple: true).

// Simple options
const { format } = await session.ask("Select output format:", {
inputs: [
{
type: "select",
name: "format",
options: ["json", "yaml", "toml"],
},
] as const,
});
// format: string
// Rich options with descriptions
const { 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 selection
const { 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 | null
console.log(recipient.externalId); // string | null
console.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 ClassCodeHTTP StatusDescription
JustackError(varies)(varies)Base error class
BadRequestErrorBAD_REQUEST400Invalid request parameters
UnauthorizedErrorUNAUTHORIZED401Missing or invalid credentials
ForbiddenErrorFORBIDDEN403Insufficient permissions
NotFoundErrorNOT_FOUND404Resource not found
PaymentRequiredErrorPAYMENT_REQUIRED402Usage limit reached
RateLimitedErrorRATE_LIMITED429Too many requests
NetworkErrorNETWORK_ERROR-Network connectivity issue
TimeoutErrorTIMEOUT-Request or ask timed out
WebSocketErrorWEBSOCKET_ERROR-WebSocket connection failure
SessionExpiredErrorSESSION_EXPIRED-Session has expired

All errors extend JustackError and include:

error.message; // Human-readable description
error.code; // ErrorCode string
error.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 results
for await (const session of client.sessions.list()) {
console.log(session.name);
}
// Collect all results into an array
import { collect } from "@justack/sdk";
const allSessions = await collect(client.sessions.list());
const allRecipients = await collect(client.recipients.list());

Next Steps