API Reference
Sandbox/Thread runtime with AI SDK compatible streaming.
Sandboxes
A sandbox is a persistent E2B environment with its own filesystem, git state, and sessions. All endpoints require Authorization: Bearer <api_key> header.
Create Sandbox
Required: agent (slug). Optional: files, envs, setup — these override/extend the agent's deployed sandbox config.
// POST /v1/sandboxes
{
"agent": "my-agent",
"files": { "/home/user/config.json": "{\"key\": \"value\"}" },
"envs": { "API_TOKEN": "sk-..." },
"setup": ["npm install -g prettier"]
}
// Response
{
"id": "sb_abc123",
"sandboxId": "e2b-sandbox-id",
"status": "active",
"createdAt": "2026-02-26T12:00:00Z"
}Get Sandbox
// GET /v1/sandboxes/:id
// Response
{
"id": "sb_abc123",
"sandboxId": "e2b-sandbox-id",
"status": "active",
"error": null,
"agent": { "slug": "my-agent", "name": "My Agent" },
"threads": [
{ "id": "th_xyz789", "name": "Chat 1", "status": "completed" }
],
"createdAt": "2026-02-26T12:00:00Z",
"updatedAt": "2026-02-26T12:05:00Z"
}Delete Sandbox
// DELETE /v1/sandboxes/:id
// Response: 204 No ContentKills the E2B sandbox and cascades deletion to all threads within it.
Sandbox Operations
Run commands, read/write files, and clone repos inside a sandbox.
Execute Command
// POST /v1/sandboxes/:id/exec
{
"command": "ls -la /home/user",
"cwd": "/home/user/repo",
"envs": { "NODE_ENV": "production" },
"timeoutMs": 30000
}
// Response
{
"stdout": "total 24\ndrwxr-xr-x 5 user user 4096 ...",
"stderr": "",
"exitCode": 0
}Write Files
// POST /v1/sandboxes/:id/files
{
"files": [
{ "path": "/home/user/hello.txt", "content": "Hello world!" },
{ "path": "/home/user/config.json", "content": "{\"key\": \"value\"}" }
]
}
// Response: 200 OKRead File
// GET /v1/sandboxes/:id/files?path=/home/user/hello.txt
// Response
{
"path": "/home/user/hello.txt",
"content": "Hello world!"
}Clone Repository
// POST /v1/sandboxes/:id/git/clone
{
"url": "https://github.com/org/repo.git",
"path": "/home/user/repo",
"token": "ghp_...",
"depth": 1
}
// Response
{
"path": "/home/user/repo",
"branch": "default"
}For private repos, pass a token (e.g. GitHub PAT). Use depth: 1 for faster shallow clones.
Threads
A thread is a conversation within a sandbox. Each thread has its own message history, status, and cost tracking.
List Threads
// GET /v1/sandboxes/:id/threads
// Response
[
{
"id": "th_xyz789",
"name": "Chat 1",
"status": "completed",
"createdAt": "2026-02-26T12:00:00Z",
"updatedAt": "2026-02-26T12:05:00Z"
}
]Create Thread
// POST /v1/sandboxes/:id/threads
{
"name": "Chat 1"
}
// Response
{
"id": "th_xyz789",
"name": "Chat 1",
"status": "active",
"createdAt": "2026-02-26T12:00:00Z"
}Get Thread
// GET /v1/sandboxes/:id/threads/:threadId
// Response
{
"id": "th_xyz789",
"name": "Chat 1",
"status": "completed",
"messages": [
{ "role": "user", "content": "Hello" },
{ "role": "assistant", "content": "Hi there!" }
],
"createdAt": "2026-02-26T12:00:00Z",
"updatedAt": "2026-02-26T12:05:00Z"
}Delete Thread
// DELETE /v1/sandboxes/:id/threads/:threadId
// Response: 204 No ContentChat Endpoint
Body `sandboxId` + `threadId`: continue an existing thread in a sandbox.
Body `sandboxId` only: creates a new thread in the existing sandbox.
No sandbox/thread: creates a new sandbox and a new thread automatically.
`threadId` without `sandboxId`: returns a 400 error.
Resuming & Cancelling
GET https://relay.an.dev/v1/chat/:slug/:sandboxId/stream
DELETE https://relay.an.dev/v1/chat/:slug/:sandboxId/streamUse the sandbox ID in the URL to resume or cancel an active stream. Use DELETE .../stream for deterministic server-side cancel.
Request Body
{
"sandboxId": "uuid-of-existing-sandbox",
"threadId": "uuid-of-existing-thread",
"messages": [
{
"id": "msg-1",
"role": "user",
"parts": [{ "type": "text", "text": "Your message" }]
}
]
}SSE Response
data: {"type":"start"}
data: {"type":"text-start","id":"..."}
data: {"type":"text-delta","id":"...","delta":"Hello world"}
data: {"type":"text-end","id":"..."}
data: {"type":"message-metadata","messageMetadata":{
"sessionId":"...",
"totalCostUsd":0.034,
"inputTokens":3,
"outputTokens":5,
"durationMs":2487
}}
data: {"type":"finish"}
data: [DONE]cURL
curl -N -X POST https://relay.an.dev/v1/chat/YOUR_AGENT_SLUG \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"sandboxId": "uuid-of-existing-sandbox",
"threadId": "uuid-of-existing-thread",
"messages": [{
"id": "msg-1",
"role": "user",
"parts": [{ "type": "text", "text": "Hello" }]
}]
}'React (SDK)
@an-sdk/nextjs with server-side token exchange, see the Get Started guide.Node.js SDK
Use @an-sdk/node for server-side sandbox and thread management.
import { AnClient } from "@an-sdk/node"
const an = new AnClient({ apiKey: process.env.AN_API_KEY! })
// Create a sandbox (with optional overrides)
const sandbox = await an.sandboxes.create({
agent: "my-agent",
envs: { API_TOKEN: "sk-..." },
})
// Clone a repo into the sandbox
await an.sandboxes.git.clone({
sandboxId: sandbox.id,
url: "https://github.com/org/repo.git",
depth: 1,
})
// Execute a command
const result = await an.sandboxes.exec({
sandboxId: sandbox.id,
command: "ls /home/user/repo",
})
// Write and read files
await an.sandboxes.files.write({
sandboxId: sandbox.id,
files: [{ path: "/home/user/note.txt", content: "Hello!" }],
})
const file = await an.sandboxes.files.read({
sandboxId: sandbox.id,
path: "/home/user/note.txt",
})
// Threads & tokens
const thread = await an.threads.create({
sandboxId: sandbox.id,
name: "Chat 1",
})
const token = await an.tokens.create({ agent: "my-agent" })
// Cleanup
await an.sandboxes.delete(sandbox.id)React (raw AI SDK)
Direct usage with AI SDK — useful for custom transports or non-Next.js frameworks.
import { Chat, DefaultChatTransport } from "ai"
import { useChat } from "@ai-sdk/react"
const chat = new Chat({
transport: new DefaultChatTransport({
api: "https://relay.an.dev/v1/chat/YOUR_AGENT_SLUG",
headers: async () => ({
Authorization: "Bearer YOUR_API_KEY",
}),
body: {
sandboxId: "uuid-of-existing-sandbox", // optional
threadId: "uuid-of-existing-thread", // optional
},
}),
})
export function AgentChat() {
const { messages, input, handleInputChange, handleSubmit } = useChat({ chat })
return (
<form onSubmit={handleSubmit}>
{messages.map((m) => <div key={m.id}>{m.content}</div>)}
<input value={input} onChange={handleInputChange} />
</form>
)
}