Skip to main content

What Are Thread API Endpoints?

Thread API endpoints are custom routes that operate on specific conversation threads. They allow you to:
  • Access thread data: Query messages, logs, and metadata
  • Perform operations: Export conversations, generate summaries, update state
  • Extend the API: Add domain-specific functionality beyond built-in routes
Thread API endpoints are file-based and auto-discovered. Create a TypeScript file in agents/api/ and export your endpoint definition—no manual registration needed.

Quick Start

Create your first endpoint in agents/api/status.ts:
import { defineThreadEndpoint } from '@standardagents/spec';

export default defineThreadEndpoint(async (req, state) => {
  const { messages, total } = await state.getMessages();

  return Response.json({
    threadId: state.threadId,
    agentId: state.agentId,
    messageCount: total,
  });
});
Your endpoint is now available at GET /api/threads/:id/status. The framework auto-discovers it from the agents/api/ directory.

File-Based Routing

API files map directly to routes based on their filename:
FileHTTP MethodRoute
status.tsGET/api/threads/:id/status
status.get.tsGET/api/threads/:id/status
export.post.tsPOST/api/threads/:id/export
metadata.put.tsPUT/api/threads/:id/metadata
archive.delete.tsDELETE/api/threads/:id/archive

Method Suffixes

Append the HTTP method to the filename to specify which method the endpoint handles:
  • .get.ts — GET requests (default if no suffix)
  • .post.ts — POST requests
  • .put.ts — PUT requests
  • .delete.ts — DELETE requests
  • .patch.ts — PATCH requests
Files without a method suffix default to GET.

Nested Routes

Create subdirectories for nested route paths:
agents/api/
├── status.ts                    # GET /api/threads/:id/status
├── export.post.ts               # POST /api/threads/:id/export
└── messages/
    └── count.ts                 # GET /api/threads/:id/messages/count

Handler Function

The defineThreadEndpoint function wraps your handler and automatically:
  1. Extracts the thread ID from the URL
  2. Looks up the thread and creates a ThreadState
  3. Handles errors (400 for missing ID, 404 for not found, 500 for exceptions)

Signature

defineThreadEndpoint(
  handler: (req: Request, state: ThreadState) => Response | Promise<Response>
)

Parameters

req — Standard Web API Request object state — ThreadState providing access to thread identity, messages, file system, and more. Note: state.execution is always null in endpoints since the thread is at rest.

ThreadState in Endpoints

The state parameter provides access to the thread’s data:

Identity

Access thread identity directly:
state.threadId     // Thread UUID
state.agentId      // Agent name
state.userId       // Optional user identifier (or null)
state.createdAt    // Creation timestamp

getMessages

Retrieve message history from the thread:
const { messages, total, hasMore } = await state.getMessages({
  limit: 100,         // Max messages
  offset: 0,          // Skip count
  order: 'desc',      // 'asc' or 'desc'
  includeSilent: false, // Include UI-only messages
  maxDepth: 0         // Max nesting depth (0 = top-level only)
});

File System

Access the thread’s file system:
// Read/write files
const data = await state.readFile('/data/config.json');
await state.writeFile('/output/result.json', JSON.stringify(data), 'application/json');

// List and search
const { entries } = await state.readdirFile('/data');
const { paths } = await state.findFiles('**/*.json');
const matches = await state.grepFiles('error');

Resource Loading

Load agent, prompt, or model definitions:
const agent = await state.loadAgent('my-agent');
const prompt = await state.loadPrompt('my-prompt');
const model = await state.loadModel('gpt-4o');

Request Handling

Reading Request Body

export default defineThreadEndpoint(async (req, state) => {
  // JSON
  const body = await req.json();

  // Form data
  const formData = await req.formData();

  // Plain text
  const text = await req.text();

  // ...
});

Query Parameters

export default defineThreadEndpoint(async (req, state) => {
  const url = new URL(req.url);
  const limit = parseInt(url.searchParams.get('limit') || '10');
  const format = url.searchParams.get('format') || 'json';

  const { messages } = await state.getMessages({ limit });
  // ...
});

Headers

export default defineThreadEndpoint(async (req, state) => {
  const authHeader = req.headers.get('Authorization');

  if (!authHeader) {
    return Response.json({ error: 'Unauthorized' }, { status: 401 });
  }

  // ...
});

Error Handling

Wrap operations in try/catch for robust error handling:
export default defineThreadEndpoint(async (req, state) => {
  try {
    const { messages } = await state.getMessages();

    // Process messages...

    return Response.json({ success: true, data: result });
  } catch (error) {
    console.error('Endpoint error:', error);

    return Response.json(
      { error: error.message || 'Internal server error' },
      { status: 500 }
    );
  }
});
The framework automatically handles common errors like missing thread IDs (400) and threads not found (404). Your handler only needs to handle domain-specific errors.

Best Practices

API handlers run in the request path. Keep operations quick:
// Good - paginated query
const { messages } = await state.getMessages({ limit: 50 });

// Avoid - loading entire history for large threads
const { messages } = await state.getMessages({ limit: 10000 });
Always wrap risky operations:
try {
  const data = await req.json();
  // Process data...
} catch (error) {
  return Response.json(
    { error: 'Invalid JSON body' },
    { status: 400 }
  );
}
Check request data before processing:
const { format, limit } = await req.json();

if (!['json', 'markdown', 'csv'].includes(format)) {
  return Response.json(
    { error: 'Invalid format. Use: json, markdown, or csv' },
    { status: 400 }
  );
}

if (limit && (limit < 1 || limit > 1000)) {
  return Response.json(
    { error: 'Limit must be between 1 and 1000' },
    { status: 400 }
  );
}
Name files clearly to indicate their purpose:
agents/api/
├── summary.get.ts      # Clear: GET summary
├── export.post.ts      # Clear: POST to export
├── analytics.get.ts    # Clear: GET analytics
└── data.ts             # Unclear: what does this do?

Next Steps

Thread

Add custom methods to the Thread class

Tools

Create custom tools that endpoints can leverage

Hooks

Intercept and modify data at lifecycle points

REST API

Explore the built-in API endpoints