Agents SDK

Backend Integration

Use agents from your server — create sandboxes, manage threads, run agents programmatically, and exchange tokens.

Install

pnpm add @21st-sdk/node

Client setup

server.ts
import { AgentClient } from "@21st-sdk/node"

const client = new AgentClient({
  apiKey: process.env.API_KEY_21ST!,
})

The client authenticates with your API key. All requests go through the relay at relay.an.dev by default.

Sandboxes

A sandbox is an isolated E2B environment where your agent runs. Create one with pre-loaded files, environment variables, and setup commands.

// Create a sandbox for an agent
const sandbox = await client.sandboxes.create({
  agent: "my-agent",
  files: {
    "data/input.json": JSON.stringify({ items: [1, 2, 3] }),
  },
  envs: {
    DATABASE_URL: "postgres://...",
  },
  setup: ["npm install"],
})

console.log(sandbox.id) // "sbx_abc123"
console.log(sandbox.status) // "running"

Sandbox operations

Execute commands, read/write files, and clone git repos inside a sandbox.

// Get sandbox details
const detail = await client.sandboxes.get("sbx_abc123")
// { id, status, agent, threads, createdAt, updatedAt }

// Execute a command
const result = await client.sandboxes.exec({
  sandboxId: "sbx_abc123",
  command: "ls -la /home/user/repo",
  timeoutMs: 30_000,
})
console.log(result.stdout)

// Write files
await client.sandboxes.files.write({
  sandboxId: "sbx_abc123",
  files: {
    "src/config.json": JSON.stringify({ debug: true }),
  },
})

// Read a file
const file = await client.sandboxes.files.read({
  sandboxId: "sbx_abc123",
  path: "src/config.json",
})
console.log(file.content)

// Clone a git repo
await client.sandboxes.git.clone({
  sandboxId: "sbx_abc123",
  url: "https://github.com/org/repo.git",
  token: process.env.GITHUB_TOKEN, // for private repos
  depth: 1,
})

// Delete sandbox
await client.sandboxes.delete("sbx_abc123")

Threads

Threads are conversations within a sandbox. Multiple threads share the same filesystem but maintain separate message histories.

// Create a thread
const thread = await client.threads.create({
  sandboxId: "sbx_abc123",
  name: "Code Review",
})

// List threads in a sandbox
const threads = await client.threads.list({
  sandboxId: "sbx_abc123",
})
// [{ id, name, status, createdAt }, ...]

// Get thread details
const detail = await client.threads.get({
  sandboxId: "sbx_abc123",
  threadId: thread.id,
})

// Delete thread
await client.threads.delete({
  sandboxId: "sbx_abc123",
  threadId: thread.id,
})

Running agents

Use client.threads.run() to send messages to an agent and get a streaming SSE response. If you don't provide a sandbox or thread ID, the SDK creates them automatically.

// Run agent in a thread (streaming)
const result = await client.threads.run({
  agent: "my-agent",
  messages: [
    {
      role: "user",
      parts: [{ type: "text", text: "Analyze the code in /src and find bugs" }],
    },
  ],
  sandboxId: "sbx_abc123",     // optional — creates one if omitted
  threadId: "thr_xyz789",      // optional — creates one if omitted
  options: {
    model: "claude-sonnet-4-6",
    maxTurns: 25,
    maxBudgetUsd: 1.0,
    permissionMode: "bypassPermissions",
  },
})

// result.response is a streaming Response (SSE)
// result.sandboxId, result.threadId — IDs of the sandbox/thread used
// result.resumeUrl — URL to resume the conversation

const reader = result.response.body?.getReader()
const decoder = new TextDecoder()

while (true) {
  const { done, value } = await reader!.read()
  if (done) break
  const chunk = decoder.decode(value)
  process.stdout.write(chunk)
}
OptionDescription
agentAgent slug (required)
messagesArray of { role, parts } messages (required)
sandboxIdExisting sandbox to use. Creates one if omitted.
threadIdExisting thread to continue. Creates one if omitted.
options.modelOverride the agent's default model
options.maxTurnsMax reasoning steps for this run
options.maxBudgetUsdCost cap in USD for this run
options.permissionModePermission mode override
options.disallowedToolsTools to disable for this run

Token exchange

Create short-lived tokens for the frontend. The token is scoped to a specific agent and user so the client can talk to the relay directly.

// Create a short-lived token for the frontend
const { token, expiresAt } = await client.tokens.create({
  agent: "my-agent",       // scope to specific agent
  userId: "user_123",      // identify the user
  expiresIn: "1h",         // default: 1 hour
})

Next.js API routes

Common pattern: create API routes that your frontend calls to manage sandboxes and threads.

// app/api/agent/sandbox/route.ts
import { NextResponse } from "next/server"
import { AgentClient } from "@21st-sdk/node"

const client = new AgentClient({ apiKey: process.env.API_KEY_21ST! })

export async function POST() {
  const sandbox = await client.sandboxes.create({ agent: "my-agent" })
  return NextResponse.json({ sandboxId: sandbox.id })
}

// app/api/agent/threads/route.ts
export async function GET(req: Request) {
  const { searchParams } = new URL(req.url)
  const sandboxId = searchParams.get("sandboxId")!
  const threads = await client.threads.list({ sandboxId })
  return NextResponse.json({ threads })
}

export async function POST(req: Request) {
  const { sandboxId, name } = await req.json()
  const thread = await client.threads.create({ sandboxId, name })
  return NextResponse.json(thread)
}

The frontend integration page shows how to call these routes from your React components.

What's next