@perstack/api-client
v0.0.55
Published
Perstack API Client
Readme
@perstack/api-client
Official TypeScript API client for the Perstack platform.
Installation
npm install @perstack/api-clientQuick Start
import { createApiClient } from "@perstack/api-client"
const client = createApiClient({
apiKey: "your-api-key",
})
// List applications
const result = await client.applications.list()
if (result.ok) {
console.log(result.data.data.applications)
} else {
console.error(result.error.message)
}Configuration
The createApiClient function accepts a configuration object:
interface ApiClientConfig {
apiKey: string // Required: Your Perstack API key
baseUrl?: string // Optional: API base URL (default: "https://api.perstack.ai")
timeout?: number // Optional: Request timeout in milliseconds (default: 30000)
}API Reference
The client provides access to four main API modules:
client.applications- Application managementclient.env- Environment configuration (secrets and variables)client.jobs- Job execution and monitoringclient.experts- Expert definitions and versioning
Result Type
All API methods return a discriminated union type for type-safe error handling:
type ApiResult<T> = { ok: true; data: T } | { ok: false; error: ApiError }
interface ApiError {
code: number // HTTP status code (0 for network/validation errors)
message: string // Error message
reason?: unknown // Additional error details
aborted?: boolean // True if request was aborted
}Request Options
All API methods accept an optional RequestOptions object:
interface RequestOptions {
signal?: AbortSignal // AbortController signal for cancellation
}For streaming endpoints, an extended StreamRequestOptions is available:
interface StreamRequestOptions extends RequestOptions {
streamIdleTimeout?: number // Idle timeout in ms between chunks (default: client timeout)
}Applications API
Manage applications within your organization.
client.applications.list(params?, options?)
List all applications with optional filtering and pagination.
const result = await client.applications.list({
name: "my-app", // Filter by name
sort: "createdAt", // Sort by: "name" | "createdAt" | "updatedAt"
order: "desc", // Order: "asc" | "desc"
take: 10, // Number of results
skip: 0, // Offset for pagination
})client.applications.get(id, options?)
Get a single application by ID.
const result = await client.applications.get("app-id")
if (result.ok) {
console.log(result.data.data.application)
}client.applications.create(input, options?)
Create a new application.
const result = await client.applications.create({
name: "My Application",
applicationGroupId: "group-id", // Optional
})client.applications.update(id, input, options?)
Update an existing application.
const result = await client.applications.update("app-id", {
name: "Updated Name",
status: "active", // "active" | "inactive"
})client.applications.delete(id, options?)
Delete an application.
const result = await client.applications.delete("app-id")Environment API
Manage secrets and environment variables.
Secrets
client.env.secrets.list(options?)
List all secrets.
const result = await client.env.secrets.list()
if (result.ok) {
for (const secret of result.data.data.secrets) {
console.log(secret.name) // Secret values are not returned
}
}client.env.secrets.get(name, options?)
Get a secret by name.
const result = await client.env.secrets.get("API_KEY")client.env.secrets.create(input, options?)
Create a new secret.
const result = await client.env.secrets.create({
name: "API_KEY",
value: "secret-value",
})client.env.secrets.update(name, input, options?)
Update an existing secret.
const result = await client.env.secrets.update("API_KEY", {
value: "new-secret-value",
})client.env.secrets.delete(name, options?)
Delete a secret.
const result = await client.env.secrets.delete("API_KEY")Variables
client.env.variables.list(options?)
List all environment variables.
const result = await client.env.variables.list()client.env.variables.get(name, options?)
Get a variable by name.
const result = await client.env.variables.get("DATABASE_URL")client.env.variables.create(input, options?)
Create a new variable.
const result = await client.env.variables.create({
name: "DATABASE_URL",
value: "postgres://...",
})client.env.variables.update(name, input, options?)
Update an existing variable.
const result = await client.env.variables.update("DATABASE_URL", {
value: "postgres://new-url...",
})client.env.variables.delete(name, options?)
Delete a variable.
const result = await client.env.variables.delete("DATABASE_URL")Jobs API
Execute and monitor expert jobs.
client.jobs.list(params?, options?)
List jobs with optional filtering and pagination.
const result = await client.jobs.list({
take: 20,
skip: 0,
sort: "createdAt",
order: "desc",
filter: "status:running",
})client.jobs.get(id, options?)
Get a job by ID.
const result = await client.jobs.get("job-id")
if (result.ok) {
console.log(result.data.data.job.status)
}client.jobs.start(input, options?)
Start a new job.
const result = await client.jobs.start({
applicationId: "app-id",
expertKey: "@org/[email protected]",
query: "Help me with this task",
files: ["file1.txt", "file2.txt"], // Optional
provider: "anthropic",
model: "claude-sonnet-4-20250514", // Optional
reasoningBudget: "medium", // Optional: "low" | "medium" | "high"
maxSteps: 50, // Optional
maxRetries: 3, // Optional
})client.jobs.update(id, input, options?)
Update a job's status.
const result = await client.jobs.update("job-id", {
status: "paused",
})client.jobs.continue(id, input, options?)
Continue a paused job with additional input.
const result = await client.jobs.continue("job-id", {
query: "Continue with this additional context",
files: ["additional-file.txt"],
interactiveToolCallResult: true, // Optional: for tool call responses
provider: "anthropic", // Optional: override provider
model: "claude-sonnet-4-20250514", // Optional: override model
maxSteps: 10, // Optional: additional steps limit
})client.jobs.cancel(id, options?)
Cancel a running job.
const result = await client.jobs.cancel("job-id")Checkpoints
Track job progress through checkpoints.
client.jobs.checkpoints.list(jobId, params?, options?)
List checkpoints for a job.
const result = await client.jobs.checkpoints.list("job-id", {
take: 50,
skip: 0,
sort: "createdAt",
order: "asc",
})client.jobs.checkpoints.get(jobId, checkpointId, options?)
Get a specific checkpoint.
const result = await client.jobs.checkpoints.get("job-id", "checkpoint-id")client.jobs.checkpoints.stream(jobId, options?)
Stream checkpoints in real-time using Server-Sent Events (SSE).
for await (const checkpoint of client.jobs.checkpoints.stream("job-id")) {
console.log("Activity:", checkpoint.activity)
}Experts API
Manage expert definitions and versions.
client.experts.list(params?, options?)
List available experts.
const result = await client.experts.list({
filter: "search term",
category: "coding", // "general" | "coding" | "research" | "writing" | "data" | "automation"
includeDrafts: false,
limit: 20,
offset: 0,
})client.experts.get(key, options?)
Get an expert definition by key.
// Get latest version
const result = await client.experts.get("@org/expert")
// Get specific version
const result = await client.experts.get("@org/[email protected]")
// Get by tag
const result = await client.experts.get("@org/expert@latest")client.experts.getMeta(key, options?)
Get expert metadata without the full definition.
const result = await client.experts.getMeta("@org/[email protected]")
if (result.ok) {
console.log("Scope:", result.data.data.scope)
console.log("Version:", result.data.data.version)
}client.experts.publish(scopeName, options?)
Publish an expert scope (make it publicly visible).
const result = await client.experts.publish("@org/expert")client.experts.unpublish(scopeName, options?)
Unpublish an expert scope (make it private).
const result = await client.experts.unpublish("@org/expert")client.experts.yank(key, options?)
Yank (deprecate) a specific version.
const result = await client.experts.yank("@org/[email protected]")
if (result.ok) {
console.log("Yanked:", result.data.data.yanked)
console.log("Latest tag updated:", result.data.data.latestTagUpdated)
}Drafts
Manage expert draft versions before publishing.
client.experts.drafts.list(scopeName, params?, options?)
List drafts for a scope.
const result = await client.experts.drafts.list("@org/expert", {
limit: 10,
offset: 0,
})client.experts.drafts.get(scopeName, draftRef, options?)
Get a specific draft.
const result = await client.experts.drafts.get("@org/expert", "draft-ref")client.experts.drafts.create(scopeName, input, options?)
Create a new draft.
const result = await client.experts.drafts.create("@org/expert", {
applicationId: "app-id",
experts: [
{
key: "main",
name: "Main Expert",
description: "A helpful assistant",
instruction: "You are a helpful assistant...",
skills: {},
delegates: [],
},
],
})client.experts.drafts.update(scopeName, draftRef, input, options?)
Update an existing draft.
const result = await client.experts.drafts.update("@org/expert", "draft-ref", {
experts: [
{
key: "main",
name: "Updated Expert",
instruction: "Updated instructions...",
},
],
})client.experts.drafts.delete(scopeName, draftRef, options?)
Delete a draft.
const result = await client.experts.drafts.delete("@org/expert", "draft-ref")client.experts.drafts.assignVersion(scopeName, draftRef, input, options?)
Promote a draft to a versioned release.
const result = await client.experts.drafts.assignVersion("@org/expert", "draft-ref", {
version: "1.0.0",
tag: "latest", // Optional: assign a tag
})Versions
List expert versions.
client.experts.versions.list(scopeName, options?)
List all versions for a scope.
const result = await client.experts.versions.list("@org/expert")
if (result.ok) {
for (const version of result.data.data.versions) {
console.log(`${version.version}: ${version.tag || "(no tag)"}`)
}
}Error Handling
The client uses a result type pattern for predictable error handling:
const result = await client.applications.get("app-id")
if (!result.ok) {
switch (result.error.code) {
case 401:
console.error("Authentication failed")
break
case 404:
console.error("Application not found")
break
case 0:
// Network error, timeout, or validation error
if (result.error.aborted) {
console.error("Request was cancelled")
} else {
console.error("Network error:", result.error.message)
}
break
default:
console.error(`Error ${result.error.code}: ${result.error.message}`)
}
return
}
// TypeScript knows result.data exists here
console.log(result.data.data.application)Request Cancellation
Use AbortController to cancel requests:
const controller = new AbortController()
// Cancel after 5 seconds
setTimeout(() => controller.abort(), 5000)
const result = await client.jobs.start(
{ applicationId: "app-id", expertKey: "@org/expert", provider: "anthropic" },
{ signal: controller.signal }
)
if (!result.ok && result.error.aborted) {
console.log("Request was cancelled")
}SSE Streaming
For real-time checkpoint streaming, the client provides an async generator:
import { createApiClient } from "@perstack/api-client"
const client = createApiClient({ apiKey: "your-api-key" })
// Start a job
const jobResult = await client.jobs.start({
applicationId: "app-id",
expertKey: "@org/expert",
provider: "anthropic",
})
if (!jobResult.ok) {
throw new Error(jobResult.error.message)
}
const jobId = jobResult.data.data.job.id
// Stream checkpoints
for await (const result of client.jobs.checkpoints.stream(jobId)) {
if (!result.ok) {
if (result.error.aborted) {
console.error("Stream timed out or was cancelled")
} else {
console.error("Stream error:", result.error.message)
}
break
}
console.log("Checkpoint:", result.data.id)
}Stream Timeout Configuration
The streaming endpoints support an idle timeout that aborts the stream if no data is received within the specified period:
for await (const result of client.jobs.checkpoints.stream(jobId, {
streamIdleTimeout: 60000, // 60 second idle timeout
})) {
if (!result.ok) {
if (result.error.aborted) {
console.error("Stream timed out or was cancelled")
}
break
}
console.log("Checkpoint:", result.data.id)
}By default, the stream idle timeout uses the client's configured timeout value (30 seconds if not specified).
Advanced SSE Parsing
For custom SSE parsing, the package exports utility functions:
import { parseSSE, parseCheckpointSSE, parseSSEWithSchema } from "@perstack/api-client"
import { z } from "zod"
// Generic SSE parser
const reader = response.body.getReader()
for await (const event of parseSSE<MyEventType>(reader)) {
console.log(event)
}
// Checkpoint-specific parser with validation
for await (const event of parseCheckpointSSE(reader)) {
if (event.type === "checkpoint") {
console.log("Checkpoint:", event.data)
} else if (event.type === "done") {
console.log("Stream complete:", event.data.status)
} else if (event.type === "error") {
console.error("Parse error:", event.data.message)
}
}
// Custom schema validation
const mySchema = z.object({ id: z.string(), value: z.number() })
for await (const event of parseSSEWithSchema(reader, mySchema)) {
console.log(event.id, event.value)
}TypeScript Support
The package is written in TypeScript and exports all types:
import type {
// Client types
ApiClient,
ApiClientConfig,
ApiResult,
ApiError,
RequestOptions,
StreamRequestOptions,
// Application types
Application,
ApplicationStatus,
CreateApplicationInput,
UpdateApplicationInput,
ListApplicationsParams,
// Job types
Job,
JobStatus,
StartJobInput,
UpdateJobInput,
ContinueJobInput,
Checkpoint,
CheckpointStatus,
// Expert types
Expert,
ExpertDefinition,
ExpertVersion,
ExpertScope,
} from "@perstack/api-client"License
MIT
