npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@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-client

Quick 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 management
  • client.env - Environment configuration (secrets and variables)
  • client.jobs - Job execution and monitoring
  • client.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