Backend Integration
Use agents from your server — create sandboxes, manage threads, run agents programmatically, and exchange tokens.
Install
pnpm add @21st-sdk/nodeClient setup
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)
}| Option | Description |
|---|---|
agent | Agent slug (required) |
messages | Array of { role, parts } messages (required) |
sandboxId | Existing sandbox to use. Creates one if omitted. |
threadId | Existing thread to continue. Creates one if omitted. |
options.model | Override the agent's default model |
options.maxTurns | Max reasoning steps for this run |
options.maxBudgetUsd | Cost cap in USD for this run |
options.permissionMode | Permission mode override |
options.disallowedTools | Tools 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.